Treat a payload max length of 0 as unlimited on every read path

set_payload_max_length(0) is documented to disable the limit, but the
readers for a chunked body and for a body delimited by the connection
closing compared the size against the limit without checking for 0.
A server configured that way answered a chunked request with 413, and
a client failed to read a chunked or unframed response. A body with
Content-Length was already unlimited.

Give both readers the same `payload_max_length > 0` guard the other
payload checks use.
This commit is contained in:
yhirose
2026-10-08 20:39:13 -04:00
parent 8f094fe292
commit 84ff13ec47
2 changed files with 55 additions and 4 deletions
+6 -4
View File
@@ -8412,8 +8412,9 @@ read_content_without_length(Stream &strm, size_t payload_max_length,
if (n < 0) { return ReadContentResult::Error; }
// Check if adding this data would exceed the payload limit
if (r > payload_max_length ||
payload_max_length - r < static_cast<size_t>(n)) {
if (payload_max_length > 0 &&
(r > payload_max_length ||
payload_max_length - r < static_cast<size_t>(n))) {
return ReadContentResult::PayloadTooLarge;
}
@@ -8448,8 +8449,9 @@ inline ReadContentResult read_content_chunked(Stream &strm, T &x,
return ReadContentResult::Success;
}
if (total_len > payload_max_length ||
payload_max_length - total_len < static_cast<size_t>(n)) {
if (payload_max_length > 0 &&
(total_len > payload_max_length ||
payload_max_length - total_len < static_cast<size_t>(n))) {
return ReadContentResult::PayloadTooLarge;
}
+49
View File
@@ -21191,6 +21191,55 @@ TEST(ClientResponseSmugglingTest, BodylessResponseNotRejected) {
}
}
// A payload max length of 0 means no limit, however the body is framed.
TEST(PayloadMaxLengthZeroTest, ClientReadsChunkedAndUnframedResponse) {
for (const char *response : {"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\nhello\r\n0\r\n\r\n",
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"\r\n"
"hello"}) {
with_single_response(response, [&](Client &cli) {
cli.set_payload_max_length(0);
auto res = cli.Get("/");
ASSERT_TRUE(res) << response;
EXPECT_EQ("hello", res->body) << response;
});
}
}
TEST(PayloadMaxLengthZeroTest, ServerReadsChunkedRequest) {
Server svr;
svr.set_payload_max_length(0);
svr.Post("/echo", [](const Request &req, Response &res) {
res.set_content(req.body, "text/plain");
});
auto port = svr.bind_to_any_port(HOST);
thread t = thread([&] { svr.listen_after_bind(); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
Client cli(HOST, port);
auto res = cli.Post(
"/echo",
[](size_t /*offset*/, DataSink &sink) {
sink.write("hello", 5);
sink.done();
return true;
},
"text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("hello", res->body);
}
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
TEST_F(OpenStreamTest, Gzip) {
Client cli("127.0.0.1", port_);