Skip to content

Commit fbd3e2c

Browse files
committed
Answer 501 to a request with a transfer coding besides chunked
A request whose Transfer-Encoding ended in chunked but listed more (another coding before chunked, or chunked twice) was read with only the chunked framing removed, so the handler got a body still in the other coding. RFC 9112 6.1 lets a server answer 501 to a transfer coding it does not implement. The server now accepts only a single Transfer-Encoding field whose value is chunked, as Go and nginx do, and answers any other list ending in chunked with 501 and closes the connection. A list whose final coding is not chunked still gets 400.
1 parent 32a0ca8 commit fbd3e2c

2 files changed

Lines changed: 39 additions & 22 deletions

File tree

‎httplib.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15135,6 +15135,16 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
1513515135
return write_response(strm, close_connection, req, res);
1513615136
}
1513715137

15138+
// RFC 9112 6.1: a lone "chunked" is the only transfer coding implemented
15139+
if (req.has_header("Transfer-Encoding") &&
15140+
(req.get_header_value_count("Transfer-Encoding") != 1 ||
15141+
!detail::case_ignore::equal(req.get_header_value("Transfer-Encoding"),
15142+
"chunked"))) {
15143+
connection_closed = true;
15144+
res.status = StatusCode::NotImplemented_501;
15145+
return write_response(strm, close_connection, req, res);
15146+
}
15147+
1513815148
// RFC 9112 §6.1: a Transfer-Encoding alongside a Content-Length, or in an
1513915149
// HTTP/1.0 request, is honoured, but the connection closes after the
1514015150
// response.

‎test/test.cc‎

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30108,32 +30108,39 @@ TEST(RequestSmugglingTest, NonFinalChunkedTransferEncodingRejected) {
3010830108
EXPECT_EQ("HTTP/1.1 400 Bad Request",
3010930109
response.substr(0, response.find("\r\n")));
3011030110
}
30111+
}
3011130112

30112-
// A sequence ending in chunked stays valid.
30113-
auto req = "POST /test HTTP/1.1\r\n"
30114-
"Host: localhost\r\n"
30115-
"Transfer-Encoding: gzip, chunked\r\n"
30116-
"Connection: close\r\n"
30117-
"\r\n"
30118-
"0\r\n\r\n";
30113+
TEST(RequestSmugglingTest, TransferEncodingOtherThanChunkedNotImplemented) {
30114+
Server svr;
30115+
svr.Post("/test", [&](const Request &, Response &res) {
30116+
res.set_content("ok", "text/plain");
30117+
});
3011930118

30120-
std::string response;
30121-
ASSERT_TRUE(send_request(1, req, &response));
30122-
EXPECT_EQ("HTTP/1.1 200 OK", response.substr(0, response.find("\r\n")));
30119+
auto port = svr.bind_to_any_port(HOST);
30120+
ASSERT_GT(port, 0);
30121+
thread t = thread([&] { svr.listen_after_bind(); });
30122+
auto se = detail::scope_exit([&] {
30123+
svr.stop();
30124+
t.join();
30125+
ASSERT_FALSE(svr.is_running());
30126+
});
30127+
svr.wait_until_ready();
3012330128

30124-
// ...including when it is spread over several lines.
30125-
auto split_req = "POST /test HTTP/1.1\r\n"
30126-
"Host: localhost\r\n"
30127-
"Transfer-Encoding: gzip\r\n"
30128-
"Transfer-Encoding: chunked\r\n"
30129-
"Connection: close\r\n"
30130-
"\r\n"
30131-
"0\r\n\r\n";
30129+
for (auto transfer_encoding :
30130+
{"Transfer-Encoding: gzip, chunked\r\n",
30131+
"Transfer-Encoding: gzip\r\nTransfer-Encoding: chunked\r\n",
30132+
"Transfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n"}) {
30133+
auto req = std::string("POST /test HTTP/1.1\r\nHost: localhost\r\n") +
30134+
transfer_encoding + "\r\n0\r\n\r\n";
3013230135

30133-
std::string split_response;
30134-
ASSERT_TRUE(send_request(1, split_req, &split_response));
30135-
EXPECT_EQ("HTTP/1.1 200 OK",
30136-
split_response.substr(0, split_response.find("\r\n")));
30136+
std::string response;
30137+
ASSERT_TRUE(send_request(1, req, &response, port)) << transfer_encoding;
30138+
EXPECT_EQ("HTTP/1.1 501 Not Implemented",
30139+
response.substr(0, response.find("\r\n")))
30140+
<< transfer_encoding;
30141+
EXPECT_NE(std::string::npos, response.find("Connection: close"))
30142+
<< transfer_encoding;
30143+
}
3013730144
}
3013830145

3013930146
// Regression for issue #2450: a DELETE without Content-Length on a

0 commit comments

Comments
 (0)