From 672255619b7d983f1bba532bfdf523aabf5540ad Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 16 Oct 2026 16:39:00 -0400 Subject: [PATCH] Do not reserve a response body by its claimed length The client reserved what Content-Length claimed, up to payload_max_length, before any of the body arrived, and without a limit nothing bounded it. Reserve at most 1 MiB up front and let a longer body grow as it is read. --- httplib.h | 14 +++++++------- test/test.cc | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/httplib.h b/httplib.h index d5c2f52c..9eb175c2 100644 --- a/httplib.h +++ b/httplib.h @@ -16599,13 +16599,13 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, output_error_log(error, &req); return false; } - // Cap the reservation by payload_max_length_ to avoid OOM when a - // hostile or malformed server sends an enormous Content-Length. - // The actual body read below is bounded by payload_max_length_, - // so reserving more than that is never useful. - auto reserve_len = static_cast(len); - if (payload_max_length_ > 0 && reserve_len > payload_max_length_) { - reserve_len = payload_max_length_; + // Content-Length is only the server's claim, so reserve no more than + // payload_max_length_ allows, nor more than 1 MiB before any of the + // body has arrived. A longer body grows as it is read. + auto reserve_len = + (std::min)(static_cast(len), size_t(1024 * 1024)); + if (payload_max_length_ > 0) { + reserve_len = (std::min)(reserve_len, payload_max_length_); } res.body.reserve(reserve_len); } diff --git a/test/test.cc b/test/test.cc index ad46afc0..f8fa065f 100644 --- a/test/test.cc +++ b/test/test.cc @@ -13382,6 +13382,30 @@ TEST_F(LargePayloadMaxLengthTest, NoContentLengthExceeds10MB) { } } +TEST(ClientPayloadTest, DoesNotReserveTheClaimedLength) { + Server svr; + svr.Get("/", [](const Request &, Response &res) { + // Claims far more than it sends, then drops the connection. + res.set_content_provider(size_t(1) << 41, "text/plain", + [](size_t, size_t, DataSink &sink) { + sink.write("abc", 3); + return false; + }); + }); + + auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + thread.join(); + ASSERT_FALSE(svr.is_running()); + }); + svr.wait_until_ready(); + + Client cli(HOST, PORT); + cli.set_payload_max_length(0); + EXPECT_FALSE(cli.Get("/")); +} + #ifdef CPPHTTPLIB_ZLIB_SUPPORT // `payload_max_length` is not enforced on decompressed body in ContentReader // path.