Loading .gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ build/ build-warn/ build-release/ build-scratch/ obj-*-linux-gnu/* obj-x86_64-linux-gnu/* out/ Loading src/httpd.cpp +19 −22 Original line number Diff line number Diff line Loading @@ -594,13 +594,13 @@ void libhttppp::HttpEvent::_finishH2Dispatch(HttpRequest &cureq, if (key == ":res-valid" || key == ":res-status" || key == ":res-content-type" || key == ":res-content-length") continue; std::string realKey = key.substr(5); std::string joined; // See the twin loop below (H3 path) for why this must be one HPACK header // field per value rather than one "; "-joined field -- multiple values are // multiple separate header lines (e.g. two Set-Cookie headers), and joining // them corrupts any response that sets more than one cookie. for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) extra.push_back({realKey, joined}); } // HEADERS frame Loading Loading @@ -696,15 +696,15 @@ void libhttppp::HttpEvent::_finishH2Dispatch(HttpRequest &cureq, if (key == ":res-valid" || key == ":res-status" || key == ":res-content-type" || key == ":res-content-length") continue; std::string realKey = key.substr(5); // Join all Values with "; " to reconstruct the full // header value (e.g. set-cookie attributes). std::string joined; // Each Values entry is already one complete header instance (e.g. a full // "name=value; Path=/; HttpOnly" Set-Cookie line) -- multiple values on the same // key mean multiple *separate* header lines (e.g. two Set-Cookie headers), not // attribute fragments of one line. Emit one HPACK header field per value: joining // them with "; " produced one line no client could parse back apart, silently // breaking any response that sets more than one cookie. for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) extra.push_back({realKey, joined}); } // Build HPACK-encoded HEADERS frame Loading Loading @@ -1638,13 +1638,12 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, if (ekey == ":res-valid" || ekey == ":res-status" || ekey == ":res-content-type" || ekey == ":res-content-length") continue; std::string realKey = ekey.substr(5); std::string joined; // One QPACK header field per value, not one "; "-joined field -- see the H2 // HPACK path's identical fix for why (multiple values are multiple separate // header lines, e.g. two Set-Cookie headers, not attribute fragments). for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); hdr_extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) hdr_extra.push_back({realKey, joined}); } // Build and send H3 HEADERS frame immediately Loading Loading @@ -1778,13 +1777,11 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, if (ekey == ":res-valid" || ekey == ":res-status" || ekey == ":res-content-type" || ekey == ":res-content-length") continue; std::string realKey = ekey.substr(5); std::string joined; // One QPACK header field per value -- see the streaming H3 path above / H2 HPACK // path for why joining multiple values with "; " corrupts multi-cookie responses. for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) extra.push_back({realKey, joined}); } std::vector<uint8_t> response = h3BuildResponse( Loading Loading
.gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ build/ build-warn/ build-release/ build-scratch/ obj-*-linux-gnu/* obj-x86_64-linux-gnu/* out/ Loading
src/httpd.cpp +19 −22 Original line number Diff line number Diff line Loading @@ -594,13 +594,13 @@ void libhttppp::HttpEvent::_finishH2Dispatch(HttpRequest &cureq, if (key == ":res-valid" || key == ":res-status" || key == ":res-content-type" || key == ":res-content-length") continue; std::string realKey = key.substr(5); std::string joined; // See the twin loop below (H3 path) for why this must be one HPACK header // field per value rather than one "; "-joined field -- multiple values are // multiple separate header lines (e.g. two Set-Cookie headers), and joining // them corrupts any response that sets more than one cookie. for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) extra.push_back({realKey, joined}); } // HEADERS frame Loading Loading @@ -696,15 +696,15 @@ void libhttppp::HttpEvent::_finishH2Dispatch(HttpRequest &cureq, if (key == ":res-valid" || key == ":res-status" || key == ":res-content-type" || key == ":res-content-length") continue; std::string realKey = key.substr(5); // Join all Values with "; " to reconstruct the full // header value (e.g. set-cookie attributes). std::string joined; // Each Values entry is already one complete header instance (e.g. a full // "name=value; Path=/; HttpOnly" Set-Cookie line) -- multiple values on the same // key mean multiple *separate* header lines (e.g. two Set-Cookie headers), not // attribute fragments of one line. Emit one HPACK header field per value: joining // them with "; " produced one line no client could parse back apart, silently // breaking any response that sets more than one cookie. for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) extra.push_back({realKey, joined}); } // Build HPACK-encoded HEADERS frame Loading Loading @@ -1638,13 +1638,12 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, if (ekey == ":res-valid" || ekey == ":res-status" || ekey == ":res-content-type" || ekey == ":res-content-length") continue; std::string realKey = ekey.substr(5); std::string joined; // One QPACK header field per value, not one "; "-joined field -- see the H2 // HPACK path's identical fix for why (multiple values are multiple separate // header lines, e.g. two Set-Cookie headers, not attribute fragments). for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); hdr_extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) hdr_extra.push_back({realKey, joined}); } // Build and send H3 HEADERS frame immediately Loading Loading @@ -1778,13 +1777,11 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, if (ekey == ":res-valid" || ekey == ":res-status" || ekey == ":res-content-type" || ekey == ":res-content-length") continue; std::string realKey = ekey.substr(5); std::string joined; // One QPACK header field per value -- see the streaming H3 path above / H2 HPACK // path for why joining multiple values with "; " corrupts multi-cookie responses. for (auto *v = hd->getfirstValue(); v; v = v->nextvalue()) { if (!joined.empty()) joined += "; "; joined += v->getvalue(); extra.push_back({realKey, v->getvalue()}); } if (!joined.empty()) extra.push_back({realKey, joined}); } std::vector<uint8_t> response = h3BuildResponse( Loading