Loading test/http_useragent_header_test.cpp +48 −0 Original line number Diff line number Diff line Loading @@ -261,6 +261,53 @@ void testPrintHeaderSingleUserAgentLine() { "outgoing user-agent: line carries the exact original value, unsplit"); } // A second request on the SAME keep-alive connection must not see the first // request's User-Agent lingering alongside its own -- HttpRequest::parseH1() // deliberately does getHeaderData() before setHeaderData() per-line (see its // own comment) so a header genuinely repeated *within one request* (multiple // Cookie: lines) appends rather than clobbers, but that lookup can't tell // "repeated within this request" apart from "left over from the request // before it" on a reused connection unless something clears state in // between. Real browsers reuse one connection for many sequential requests; // curl-style one-request-per-connection testing (like the two tests above) // never exercises this at all -- which is exactly why it went unnoticed // until a strict upstream (observed live: Home Assistant's aiohttp) started // rejecting the accumulated duplicate outright. void testKeepAliveDoesNotAccumulateHeaders(int port) { auto sock = std::make_unique<netplus::tcp>(); rawtcp::connectLocal(*sock, port); auto doRequest = [&](const std::string &ua) { std::ostringstream req; req << "GET /ua-echo HTTP/1.1\r\n" << "Host: 127.0.0.1:" << port << "\r\n" << "User-Agent: " << ua << "\r\n" << "Connection: keep-alive\r\n" << "\r\n"; rawtcp::sendAllOrThrow(*sock, req.str()); HttpResponse res; rawReadHeadersAndParse(*sock, res); return res; }; HttpResponse first = doRequest("FirstAgent/1.0"); check(headerValue(first, "x-ua-count") == "1", "keep-alive request #1 -- server sees exactly one Value"); check(headerValue(first, "x-ua-echo") == "FirstAgent/1.0", "keep-alive request #1 -- value matches what was sent"); HttpResponse second = doRequest("SecondAgent/2.0"); sock->close(); check(headerValue(second, "x-ua-count") == "1", "keep-alive request #2 on the SAME connection -- still exactly one Value, " "not two (request #1's stale value must not linger)"); check(headerValue(second, "x-ua-echo") == "SecondAgent/2.0", "keep-alive request #2 -- value is request #2's own, not request #1's " "leftover joined with it"); } } // namespace int main() { Loading @@ -274,6 +321,7 @@ int main() { testSemicolonHeavyUserAgentNotSplit(port); testPrintHeaderSingleUserAgentLine(); testKeepAliveDoesNotAccumulateHeaders(port); } catch (HTTPException &e) { std::cerr << "HTTPException: " << e.what() << std::endl; Loading Loading
test/http_useragent_header_test.cpp +48 −0 Original line number Diff line number Diff line Loading @@ -261,6 +261,53 @@ void testPrintHeaderSingleUserAgentLine() { "outgoing user-agent: line carries the exact original value, unsplit"); } // A second request on the SAME keep-alive connection must not see the first // request's User-Agent lingering alongside its own -- HttpRequest::parseH1() // deliberately does getHeaderData() before setHeaderData() per-line (see its // own comment) so a header genuinely repeated *within one request* (multiple // Cookie: lines) appends rather than clobbers, but that lookup can't tell // "repeated within this request" apart from "left over from the request // before it" on a reused connection unless something clears state in // between. Real browsers reuse one connection for many sequential requests; // curl-style one-request-per-connection testing (like the two tests above) // never exercises this at all -- which is exactly why it went unnoticed // until a strict upstream (observed live: Home Assistant's aiohttp) started // rejecting the accumulated duplicate outright. void testKeepAliveDoesNotAccumulateHeaders(int port) { auto sock = std::make_unique<netplus::tcp>(); rawtcp::connectLocal(*sock, port); auto doRequest = [&](const std::string &ua) { std::ostringstream req; req << "GET /ua-echo HTTP/1.1\r\n" << "Host: 127.0.0.1:" << port << "\r\n" << "User-Agent: " << ua << "\r\n" << "Connection: keep-alive\r\n" << "\r\n"; rawtcp::sendAllOrThrow(*sock, req.str()); HttpResponse res; rawReadHeadersAndParse(*sock, res); return res; }; HttpResponse first = doRequest("FirstAgent/1.0"); check(headerValue(first, "x-ua-count") == "1", "keep-alive request #1 -- server sees exactly one Value"); check(headerValue(first, "x-ua-echo") == "FirstAgent/1.0", "keep-alive request #1 -- value matches what was sent"); HttpResponse second = doRequest("SecondAgent/2.0"); sock->close(); check(headerValue(second, "x-ua-count") == "1", "keep-alive request #2 on the SAME connection -- still exactly one Value, " "not two (request #1's stale value must not linger)"); check(headerValue(second, "x-ua-echo") == "SecondAgent/2.0", "keep-alive request #2 -- value is request #2's own, not request #1's " "leftover joined with it"); } } // namespace int main() { Loading @@ -274,6 +321,7 @@ int main() { testSemicolonHeavyUserAgentNotSplit(port); testPrintHeaderSingleUserAgentLine(); testKeepAliveDoesNotAccumulateHeaders(port); } catch (HTTPException &e) { std::cerr << "HTTPException: " << e.what() << std::endl; Loading