From 84ff13ec47c74d2ed06dec926a8e6dfb160e987d Mon Sep 17 00:00:00 2001 From: yhirose Date: Thu, 8 Oct 2026 00:38:32 -0400 Subject: [PATCH] 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. --- httplib.h | 10 ++++++---- test/test.cc | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/httplib.h b/httplib.h index 24179706..d12a3f6c 100644 --- a/httplib.h +++ b/httplib.h @@ -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(n)) { + if (payload_max_length > 0 && + (r > payload_max_length || + payload_max_length - r < static_cast(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(n)) { + if (payload_max_length > 0 && + (total_len > payload_max_length || + payload_max_length - total_len < static_cast(n))) { return ReadContentResult::PayloadTooLarge; } diff --git a/test/test.cc b/test/test.cc index b3af1fda..0c3c6cd6 100644 --- a/test/test.cc +++ b/test/test.cc @@ -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_);