Close the connection after an ambiguous Transfer-Encoding

RFC 9112 6.1 has a server close the connection after answering a
request with both Transfer-Encoding and Content-Length, or with
Transfer-Encoding in HTTP/1.0. Close it, and send Connection: close
whenever the connection closes after a response.
This commit is contained in:
yhirose committed 2026-10-16 16:39:00 -04:00
1 parent 64bebd3b2b
commit 58957ae4da
2 files changed
+36 -4

No files matched your search

+13 -4
View File
@@ -14761,6 +14761,13 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
return write_response(strm, close_connection, req, res);
}
// RFC 9112 §6.1: answer a request that also has a Content-Length, or an
// HTTP/1.0 one, by its Transfer-Encoding, but close the connection after.
if (req.has_header("Transfer-Encoding") &&
(req.has_header("Content-Length") || req.version == "HTTP/1.0")) {
connection_closed = true;
}
// RFC 9112 §3.2: Reject more than one Host field line or an invalid Host
// value, so that every recipient of the request sees the same authority.
if (req.get_header_value_count("Host") > 1 ||
@@ -14990,6 +14997,8 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
}
}
#endif
// The response says so when this connection closes after it.
auto close = close_connection || connection_closed;
auto ret = false;
if (routed) {
if (res.status == -1) {
@@ -15023,17 +15032,17 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
}
if (file_open_error) {
ret = write_response(strm, close_connection, req, res);
ret = write_response(strm, close, req, res);
} else if (detail::range_error(req, res)) {
detail::clear_content(res);
res.status = StatusCode::RangeNotSatisfiable_416;
ret = write_response(strm, close_connection, req, res);
ret = write_response(strm, close, req, res);
} else {
ret = write_response_with_content(strm, close_connection, req, res);
ret = write_response_with_content(strm, close, req, res);
}
} else {
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
ret = write_response(strm, close_connection, req, res);
ret = write_response(strm, close, req, res);
}
// Drain any unconsumed framed body to prevent request smuggling on
+23
View File
@@ -10836,6 +10836,29 @@ TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparsableHeaderLine) {
"\r\n");
}
TEST(ServerRequestParsingTest, AmbiguousTransferEncodingClosesConnection) {
// RFC 9112 §6.1
const char *requests[] = {
"PUT /put_hi HTTP/1.1\r\n"
"Content-Length: 0\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"0\r\n\r\n",
"PUT /put_hi HTTP/1.0\r\n"
"Connection: keep-alive\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"0\r\n\r\n",
};
for (auto request : requests) {
std::string out;
test_raw_request(request, &out);
EXPECT_EQ("HTTP/1.1 200 OK", out.substr(0, 15)) << request;
EXPECT_NE(std::string::npos, out.find("Connection: close")) << request;
}
}
TEST(ServerRequestParsingTest, InvalidFirstChunkLengthInRequest) {
std::string out;