Loading src/http.cpp +43 −6 Original line number Diff line number Diff line Loading @@ -3914,10 +3914,23 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) { key[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(key[i]))); } HeaderData* ncontent = setHeaderData(key.c_str()); // Use getHeaderData first so that repeated header lines (e.g. multiple // Set-Cookie: lines) APPEND values instead of clobbering earlier ones. HeaderData* ncontent = getHeaderData(key); if (!ncontent) ncontent = setHeaderData(key.c_str()); if (!ncontent) continue; if (key == "set-cookie") { // Set-Cookie must never be comma-split: its own value legitimately // contains commas (e.g. "Expires=Wed, 21 Oct 2026 07:28:00 GMT"), // and RFC 7230 Sec3.2.2 singles it out as the header that cannot // be combined/split as a comma list. Each physical line is exactly // one cookie. ncontent->push_back(value); continue; } // Split value on commas (HTTP list headers: TE, Accept, etc.) size_t oldiv = 0; size_t iv = 0; Loading Loading @@ -4394,7 +4407,11 @@ size_t libhttppp::HttpRequest::parseH1() { } // store header: split value on ';' into tokens (preserving case) HeaderData* ncontent = setHeaderData(key); // Use getHeaderData first so that duplicate header lines (e.g. repeated // Cookie: lines) APPEND values instead of clobbering earlier ones -- // matches parseH2/parseH3 behaviour. HeaderData* ncontent = getHeaderData(key); if (!ncontent) ncontent = setHeaderData(key); if (!ncontent) continue; size_t oldiv = 0, iv = 0; Loading Loading @@ -4461,15 +4478,35 @@ void libhttppp::HttpRequest::printHeader(std::string &buffer){ buffer.append(_cachedRequestVersion); buffer.append("\r\n"); // One "Key: value\r\n" line per value -- see HttpResponse::printHeader's twin above for why // folding multiple values of the same header onto one "; "-joined line is wrong (breaks any // multi-instance header whose values may contain ";"/",", not just Set-Cookie, which never // appears on a request but the same serialization is shared logic). // One "Key: value\r\n" line per value for most headers -- a request has no // Set-Cookie, so there's no comma/semicolon-in-value hazard to protect // against by keeping instances on separate lines (see HttpResponse::printHeader). // Cookie is the one request header that must NOT be split across multiple // lines: RFC 6265 Sec5.4 requires a user agent to send exactly one Cookie // header field, with pairs joined by "; ". parseH2/parseH3 split an incoming // multi-cookie header into separate Values on ';', so re-serializing those as // separate "Cookie:" lines to an H1 upstream breaks the round trip -- the // upstream's own H1 parser then keeps only the last line's cookie. for(HeaderData *curdat=getfirstHeaderData(); curdat; curdat=curdat->nextHeaderData()){ // Skip pseudo-headers and response pseudo-headers in H1 output const std::string &key = curdat->getkey(); if (!key.empty() && key[0] == ':') continue; if (key == "cookie") { std::string joined; for(HeaderData::Values *cval=curdat->getfirstValue(); cval; cval=cval->nextvalue()){ if (!joined.empty()) joined.append("; "); joined.append(cval->getvalue()); } if (!joined.empty()) { buffer.append(key); buffer.append(": "); buffer.append(joined); buffer.append("\r\n"); } continue; } for(HeaderData::Values *cval=curdat->getfirstValue(); cval; cval=cval->nextvalue()){ buffer.append(key); buffer.append(": "); Loading Loading
src/http.cpp +43 −6 Original line number Diff line number Diff line Loading @@ -3914,10 +3914,23 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) { key[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(key[i]))); } HeaderData* ncontent = setHeaderData(key.c_str()); // Use getHeaderData first so that repeated header lines (e.g. multiple // Set-Cookie: lines) APPEND values instead of clobbering earlier ones. HeaderData* ncontent = getHeaderData(key); if (!ncontent) ncontent = setHeaderData(key.c_str()); if (!ncontent) continue; if (key == "set-cookie") { // Set-Cookie must never be comma-split: its own value legitimately // contains commas (e.g. "Expires=Wed, 21 Oct 2026 07:28:00 GMT"), // and RFC 7230 Sec3.2.2 singles it out as the header that cannot // be combined/split as a comma list. Each physical line is exactly // one cookie. ncontent->push_back(value); continue; } // Split value on commas (HTTP list headers: TE, Accept, etc.) size_t oldiv = 0; size_t iv = 0; Loading Loading @@ -4394,7 +4407,11 @@ size_t libhttppp::HttpRequest::parseH1() { } // store header: split value on ';' into tokens (preserving case) HeaderData* ncontent = setHeaderData(key); // Use getHeaderData first so that duplicate header lines (e.g. repeated // Cookie: lines) APPEND values instead of clobbering earlier ones -- // matches parseH2/parseH3 behaviour. HeaderData* ncontent = getHeaderData(key); if (!ncontent) ncontent = setHeaderData(key); if (!ncontent) continue; size_t oldiv = 0, iv = 0; Loading Loading @@ -4461,15 +4478,35 @@ void libhttppp::HttpRequest::printHeader(std::string &buffer){ buffer.append(_cachedRequestVersion); buffer.append("\r\n"); // One "Key: value\r\n" line per value -- see HttpResponse::printHeader's twin above for why // folding multiple values of the same header onto one "; "-joined line is wrong (breaks any // multi-instance header whose values may contain ";"/",", not just Set-Cookie, which never // appears on a request but the same serialization is shared logic). // One "Key: value\r\n" line per value for most headers -- a request has no // Set-Cookie, so there's no comma/semicolon-in-value hazard to protect // against by keeping instances on separate lines (see HttpResponse::printHeader). // Cookie is the one request header that must NOT be split across multiple // lines: RFC 6265 Sec5.4 requires a user agent to send exactly one Cookie // header field, with pairs joined by "; ". parseH2/parseH3 split an incoming // multi-cookie header into separate Values on ';', so re-serializing those as // separate "Cookie:" lines to an H1 upstream breaks the round trip -- the // upstream's own H1 parser then keeps only the last line's cookie. for(HeaderData *curdat=getfirstHeaderData(); curdat; curdat=curdat->nextHeaderData()){ // Skip pseudo-headers and response pseudo-headers in H1 output const std::string &key = curdat->getkey(); if (!key.empty() && key[0] == ':') continue; if (key == "cookie") { std::string joined; for(HeaderData::Values *cval=curdat->getfirstValue(); cval; cval=cval->nextvalue()){ if (!joined.empty()) joined.append("; "); joined.append(cval->getvalue()); } if (!joined.empty()) { buffer.append(key); buffer.append(": "); buffer.append(joined); buffer.append("\r\n"); } continue; } for(HeaderData::Values *cval=curdat->getfirstValue(); cval; cval=cval->nextvalue()){ buffer.append(key); buffer.append(": "); Loading