Hi!I have prepared an upload fixing three CVEs for the cpp-httplib package, originally targeting unstable/testing/trixie. I was asked by the release team to coordinate with you instead, and to perform a security update.
You can find a full diff about the version in trixie and the update at <https://salsa.debian.org/debian/cpp-httplib/-/compare/archive%2Fdebian%2F0.18.7-1...debian%2Ftrixie?from_project_id=65963>. I've also attached a debdiff here.
For some more context on the impact of the changes, please see the Cc'd bug and the unblock bug #1110393.
Let me know how to proceed! Bye :)
diff -Nru cpp-httplib-0.18.7/debian/changelog cpp-httplib-0.18.7/debian/changelog --- cpp-httplib-0.18.7/debian/changelog 2025-03-11 18:18:06.000000000 +0100 +++ cpp-httplib-0.18.7/debian/changelog 2025-08-07 00:19:58.000000000 +0200 @@ -1,3 +1,28 @@ +cpp-httplib (0.18.7-1+deb13u1) trixie-security; urgency=medium + + * fix CVE-2025-46728 (DoS via unbounded request line length). + While this patch intended to enforce request body size limits for + chunked Transfer-Encoding, it actually adds size limits for a unique + lines read from HTTP requests, solving another kind of DoS. + See the GHSA-px83-72rx-v57c GitHub advisory for more details. + Thanks to Yang Wang for the patch! + Closes: #1104926 + + * fix CVE-2025-52887 (Unlimited number of HTTP headers causes memory leak). + This patch adds a limit to the number of headers which + can be passed in an HTTP request, mitigating a possible DoS due to memory + exhaustion. + See bug #1109340 and the GHSA-xjhg-gf59-p92h GitHub advisory for more + details. + + * fix CVE-2025-53629 (Unbounded Memory Allocation in Chunked Requests). + This patch complements the fix for CVE-2025-46728, actually solving + memory exhaustion attacks via chucked HTTP requests. + See bug #1109340 and the GHSA-qjmq-h3cc-qv6w GitHub advisory for more + details. + + -- Andrea Pappacoda <[email protected]> Thu, 07 Aug 2025 00:19:58 +0200 + cpp-httplib (0.18.7-1) unstable; urgency=medium * Update to new upstream version 0.18.7. diff -Nru cpp-httplib-0.18.7/debian/gbp.conf cpp-httplib-0.18.7/debian/gbp.conf --- cpp-httplib-0.18.7/debian/gbp.conf 2025-03-11 18:18:06.000000000 +0100 +++ cpp-httplib-0.18.7/debian/gbp.conf 2025-08-07 00:16:55.000000000 +0200 @@ -1,7 +1,7 @@ [DEFAULT] dist = DEP14 -debian-branch = debian/latest +debian-branch = debian/trixie upstream-branch = upstream/latest pristine-tar = True pristine-tar-commit = True diff -Nru cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch --- cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch 2025-08-07 00:19:58.000000000 +0200 @@ -0,0 +1,90 @@ +From: Ville Vesilehto <[email protected]> +Date: Sat, 3 May 2025 11:39:01 +0300 +Subject: httplib.h: fix CVE-2025-46728 (DoS via unbounded request line + lenght) + +While this patch intended to enforce request body size limits for +chunked Transfer-Encoding, it actually adds size limits for a unique +lines read from HTTP requests, solving another kind of DoS. + +Author: Ville Vesilehto <[email protected]> +Origin: upstream, https://github.com/yhirose/cpp-httplib/commit/7b752106ac42bd5b907793950d9125a0972c8e8e +Bug-Debian: https://bugs.debian.org/1104926 +Bug: https://github.com/yhirose/cpp-httplib/security/advisories/GHSA-px83-72rx-v57c +Forwarded: not-needed +Reviewed-By: Yang Wang <[email protected]> +Reviewed-By: Andrea Pappacoda <[email protected]> + +Closes: #1104926 +--- + httplib.h | 9 +++++++++ + test/test.cc | 15 +++++++++++++++ + 2 files changed, 24 insertions(+) + +diff --git a/httplib.h b/httplib.h +index eb592d2..ec81a75 100644 +--- a/httplib.h ++++ b/httplib.h +@@ -141,6 +141,10 @@ + #define CPPHTTPLIB_LISTEN_BACKLOG 5 + #endif + ++#ifndef CPPHTTPLIB_MAX_LINE_LENGTH ++#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 ++#endif ++ + /* + * Headers + */ +@@ -2961,6 +2965,11 @@ inline bool stream_line_reader::getline() { + #endif + + for (size_t i = 0;; i++) { ++ if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) { ++ // Treat exceptionally long lines as an error to ++ // prevent infinite loops/memory exhaustion ++ return false; ++ } + char byte; + auto n = strm_.read(&byte, 1); + +diff --git a/test/test.cc b/test/test.cc +index b69be5c..fe7151e 100644 +--- a/test/test.cc ++++ b/test/test.cc +@@ -42,6 +42,9 @@ const int PORT = 1234; + const string LONG_QUERY_VALUE = string(25000, '@'); + const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE; + ++const string TOO_LONG_QUERY_VALUE = string(35000, '@'); ++const string TOO_LONG_QUERY_URL = "/too-long-query-value?key=" + TOO_LONG_QUERY_VALUE; ++ + const std::string JSON_DATA = "{\"hello\":\"world\"}"; + + const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB +@@ -2837,6 +2840,11 @@ protected: + EXPECT_EQ(LONG_QUERY_URL, req.target); + EXPECT_EQ(LONG_QUERY_VALUE, req.get_param_value("key")); + }) ++ .Get("/too-long-query-value", ++ [&](const Request &req, Response & /*res*/) { ++ EXPECT_EQ(TOO_LONG_QUERY_URL, req.target); ++ EXPECT_EQ(TOO_LONG_QUERY_VALUE, req.get_param_value("key")); ++ }) + .Get("/array-param", + [&](const Request &req, Response & /*res*/) { + EXPECT_EQ(3u, req.get_param_value_count("array")); +@@ -3611,6 +3619,13 @@ TEST_F(ServerTest, LongQueryValue) { + EXPECT_EQ(StatusCode::UriTooLong_414, res->status); + } + ++TEST_F(ServerTest, TooLongQueryValue) { ++ auto res = cli_.Get(TOO_LONG_QUERY_URL.c_str()); ++ ++ ASSERT_FALSE(res); ++ EXPECT_EQ(Error::Read, res.error()); ++} ++ + TEST_F(ServerTest, TooLongHeader) { + Request req; + req.method = "GET"; diff -Nru cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch --- cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch 2025-08-07 00:19:58.000000000 +0200 @@ -0,0 +1,185 @@ +From: yhirose <[email protected]> +Date: Tue, 24 Jun 2025 07:56:00 -0400 +Subject: httplib.h: fix CVE-2025-52887 (Unlimited number of http header + fields causes memory leak) + +This patch adds a limit to the number of headers which can be passed in +an HTTP request, mitigating a possible DoS due to memory exhaustion. + +Author: Yuji Hirose <[email protected]> +Origin: upstream, https://github.com/yhirose/cpp-httplib/commit/28dcf379e82a2cdb544d812696a7fd46067eb7f9 +Bug-Debian: https://bugs.debian.org/1109340 +Bug: https://github.com/yhirose/cpp-httplib/security/advisories/GHSA-xjhg-gf59-p92h +Forwarded: not-needed +Reviewed-By: Andrea Pappacoda <[email protected]> +--- + httplib.h | 17 ++++++++++++++ + test/test.cc | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 91 insertions(+) + +diff --git a/httplib.h b/httplib.h +index ec81a75..0a5c6fc 100644 +--- a/httplib.h ++++ b/httplib.h +@@ -86,6 +86,10 @@ + #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 + #endif + ++#ifndef CPPHTTPLIB_HEADER_MAX_COUNT ++#define CPPHTTPLIB_HEADER_MAX_COUNT 100 ++#endif ++ + #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT + #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 + #endif +@@ -4249,6 +4253,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + ++ size_t header_count = 0; ++ + for (;;) { + if (!line_reader.getline()) { return false; } + +@@ -4269,6 +4275,9 @@ inline bool read_headers(Stream &strm, Headers &headers) { + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + ++ // Check header count limit ++ if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } ++ + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + +@@ -4278,6 +4287,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { + })) { + return false; + } ++ ++ header_count++; + } + + return true; +@@ -4379,9 +4390,13 @@ inline bool read_content_chunked(Stream &strm, T &x, + // chuncked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return true; } + ++ size_t trailer_header_count = 0; + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + ++ // Check trailer header count limit ++ if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } ++ + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; +@@ -4391,6 +4406,8 @@ inline bool read_content_chunked(Stream &strm, T &x, + x.headers.emplace(key, val); + }); + ++ trailer_header_count++; ++ + if (!line_reader.getline()) { return false; } + } + +diff --git a/test/test.cc b/test/test.cc +index fe7151e..e29be7d 100644 +--- a/test/test.cc ++++ b/test/test.cc +@@ -3,7 +3,11 @@ + #include <signal.h> + + #ifndef _WIN32 ++#include <arpa/inet.h> + #include <curl/curl.h> ++#include <netinet/in.h> ++#include <sys/socket.h> ++#include <unistd.h> + #endif + #include <gtest/gtest.h> + +@@ -3680,6 +3684,50 @@ TEST_F(ServerTest, TooLongHeader) { + EXPECT_EQ(StatusCode::OK_200, res->status); + } + ++TEST_F(ServerTest, HeaderCountAtLimit) { ++ // Test with headers just under the 100 limit ++ httplib::Headers headers; ++ ++ // Add 95 custom headers (the client will add Host, User-Agent, Accept, etc.) ++ // This should keep us just under the 100 header limit ++ for (int i = 0; i < 95; i++) { ++ std::string name = "X-Test-Header-" + std::to_string(i); ++ std::string value = "value" + std::to_string(i); ++ headers.emplace(name, value); ++ } ++ ++ // This should work fine as we're under the limit ++ auto res = cli_.Get("/hi", headers); ++ EXPECT_TRUE(res); ++ if (res) { ++ EXPECT_EQ(StatusCode::OK_200, res->status); ++ } ++} ++ ++TEST_F(ServerTest, HeaderCountExceedsLimit) { ++ // Test with many headers to exceed the 100 limit ++ httplib::Headers headers; ++ ++ // Add 150 headers to definitely exceed the 100 limit ++ for (int i = 0; i < 150; i++) { ++ std::string name = "X-Test-Header-" + std::to_string(i); ++ std::string value = "value" + std::to_string(i); ++ headers.emplace(name, value); ++ } ++ ++ // This should fail due to exceeding header count limit ++ auto res = cli_.Get("/hi", headers); ++ ++ // The request should either fail or return 400 Bad Request ++ if (res) { ++ // If we get a response, it should be 400 Bad Request ++ EXPECT_EQ(StatusCode::BadRequest_400, res->status); ++ } else { ++ // Or the request should fail entirely ++ EXPECT_FALSE(res); ++ } ++} ++ + TEST_F(ServerTest, PercentEncoding) { + auto res = cli_.Get("/e%6edwith%"); + ASSERT_TRUE(res); +@@ -3717,6 +3765,32 @@ TEST_F(ServerTest, PlusSignEncoding) { + EXPECT_EQ("a +b", res->body); + } + ++TEST_F(ServerTest, HeaderCountSecurityTest) { ++ // This test simulates a potential DoS attack using many headers ++ // to verify our security fix prevents memory exhaustion ++ ++ httplib::Headers attack_headers; ++ ++ // Attempt to add many headers like an attacker would (200 headers to far exceed limit) ++ for (int i = 0; i < 200; i++) { ++ std::string name = "X-Attack-Header-" + std::to_string(i); ++ std::string value = "attack_payload_" + std::to_string(i); ++ attack_headers.emplace(name, value); ++ } ++ ++ // Try to POST with excessive headers ++ auto res = cli_.Post("/", attack_headers, "test_data", "text/plain"); ++ ++ // Should either fail or return 400 Bad Request due to security limit ++ if (res) { ++ // If we get a response, it should be 400 Bad Request ++ EXPECT_EQ(StatusCode::BadRequest_400, res->status); ++ } else { ++ // Request failed, which is the expected behavior for DoS protection ++ EXPECT_FALSE(res); ++ } ++} ++ + TEST_F(ServerTest, MultipartFormData) { + MultipartFormDataItems items = { + {"text1", "text default", "", ""}, diff -Nru cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch --- cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch 2025-08-07 00:19:58.000000000 +0200 @@ -0,0 +1,450 @@ +From: yhirose <[email protected]> +Date: Tue, 8 Jul 2025 17:11:13 -0400 +Subject: httplib.h: fix CVE-2025-53629 (Unbounded Memory Allocation in + Chunked/No-Length Requests) + +This patch complements the fix for CVE-2025-46728, actually solving +memory exhaustion attacks via chucked HTTP requests. + +Origin: backport, https://github.com/yhirose/cpp-httplib/commit/082acacd4581d10e05fccbe9cb336aa7822c4ea2 +Bug-Debian: https://bugs.debian.org/1109340 +Bug: https://github.com/yhirose/cpp-httplib/security/advisories/GHSA-qjmq-h3cc-qv6w +Forwarded: not-needed +Reviewed-By: Andrea Pappacoda <[email protected]> +--- + httplib.h | 95 ++++++++++++++++------ + test/test.cc | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 327 insertions(+), 23 deletions(-) + +diff --git a/httplib.h b/httplib.h +index 0a5c6fc..bc887ea 100644 +--- a/httplib.h ++++ b/httplib.h +@@ -4327,51 +4327,79 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) { + } + } + +-inline bool read_content_without_length(Stream &strm, +- ContentReceiverWithProgress out) { ++enum class ReadContentResult { ++ Success, // Successfully read the content ++ PayloadTooLarge, // The content exceeds the specified payload limit ++ Error // An error occurred while reading the content ++}; ++ ++inline ReadContentResult ++read_content_without_length(Stream &strm, size_t payload_max_length, ++ ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); +- if (n <= 0) { return true; } ++ if (n == 0) { return ReadContentResult::Success; } ++ 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<uint64_t>(n)) { ++ return ReadContentResult::PayloadTooLarge; ++ } + +- if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; } ++ if (!out(buf, static_cast<size_t>(n), r, 0)) { ++ return ReadContentResult::Error; ++ } + r += static_cast<uint64_t>(n); + } + +- return true; ++ return ReadContentResult::Success; + } + + template <typename T> +-inline bool read_content_chunked(Stream &strm, T &x, +- ContentReceiverWithProgress out) { ++inline ReadContentResult read_content_chunked(Stream &strm, T &x, ++ size_t payload_max_length, ++ ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + + unsigned long chunk_len; ++ uint64_t total_len = 0; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + +- if (end_ptr == line_reader.ptr()) { return false; } +- if (chunk_len == ULONG_MAX) { return false; } ++ if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; } ++ if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; } + + if (chunk_len == 0) { break; } + ++ // Check if adding this chunk would exceed the payload limit ++ if (total_len > payload_max_length || ++ payload_max_length - total_len < chunk_len) { ++ return ReadContentResult::PayloadTooLarge; ++ } ++ ++ total_len += chunk_len; ++ + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { +- return false; ++ return ReadContentResult::Error; + } + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + +- if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } ++ if (strcmp(line_reader.ptr(), "\r\n") != 0) { ++ return ReadContentResult::Error; ++ } + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + } + + assert(chunk_len == 0); +@@ -4386,16 +4414,20 @@ inline bool read_content_chunked(Stream &strm, T &x, + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // +- // According to the reference code in RFC 9112, cpp-htpplib now allows +- // chuncked transfer coding data without the final CRLF. +- if (!line_reader.getline()) { return true; } ++ // According to the reference code in RFC 9112, cpp-httplib now allows ++ // chunked transfer coding data without the final CRLF. ++ if (!line_reader.getline()) { return ReadContentResult::Success; } + + size_t trailer_header_count = 0; + while (strcmp(line_reader.ptr(), "\r\n") != 0) { +- if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } ++ if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { ++ return ReadContentResult::Error; ++ } + + // Check trailer header count limit +- if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } ++ if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { ++ return ReadContentResult::Error; ++ } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; +@@ -4408,10 +4440,10 @@ inline bool read_content_chunked(Stream &strm, T &x, + + trailer_header_count++; + +- if (!line_reader.getline()) { return false; } ++ if (!line_reader.getline()) { return ReadContentResult::Error; } + } + +- return true; ++ return ReadContentResult::Success; + } + + inline bool is_chunked_transfer_encoding(const Headers &headers) { +@@ -4478,9 +4510,26 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { +- ret = read_content_chunked(strm, x, out); ++ auto result = read_content_chunked(strm, x, payload_max_length, out); ++ if (result == ReadContentResult::Success) { ++ ret = true; ++ } else if (result == ReadContentResult::PayloadTooLarge) { ++ exceed_payload_max_length = true; ++ ret = false; ++ } else { ++ ret = false; ++ } + } else if (!has_header(x.headers, "Content-Length")) { +- ret = read_content_without_length(strm, out); ++ auto result = ++ read_content_without_length(strm, payload_max_length, out); ++ if (result == ReadContentResult::Success) { ++ ret = true; ++ } else if (result == ReadContentResult::PayloadTooLarge) { ++ exceed_payload_max_length = true; ++ ret = false; ++ } else { ++ ret = false; ++ } + } else { + auto is_invalid_value = false; + auto len = get_header_value_u64( +diff --git a/test/test.cc b/test/test.cc +index e29be7d..4d4a742 100644 +--- a/test/test.cc ++++ b/test/test.cc +@@ -6134,6 +6134,261 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) { + EXPECT_EQ(StatusCode::OK_200, res->status); + } + ++TEST_F(PayloadMaxLengthTest, ChunkedEncodingSecurityTest) { ++ // Test chunked encoding with payload exceeding the 8-byte limit ++ std::string large_chunked_data(16, 'A'); // 16 bytes, exceeds 8-byte limit ++ ++ auto res = cli_.Post("/test", large_chunked_data, "text/plain"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status); ++} ++ ++TEST_F(PayloadMaxLengthTest, ChunkedEncodingWithinLimit) { ++ // Test chunked encoding with payload within the 8-byte limit ++ std::string small_chunked_data(4, 'B'); // 4 bytes, within 8-byte limit ++ ++ auto res = cli_.Post("/test", small_chunked_data, "text/plain"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::OK_200, res->status); ++} ++ ++TEST_F(PayloadMaxLengthTest, RawSocketChunkedTest) { ++ // Test using send_request to send chunked data exceeding payload limit ++ std::string chunked_request = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + std::to_string(PORT) + ++ "\r\n" ++ "Transfer-Encoding: chunked\r\n" ++ "Connection: close\r\n" ++ "\r\n" ++ "a\r\n" // 10 bytes chunk (exceeds 8-byte limit) ++ "0123456789\r\n" ++ "0\r\n" // End chunk ++ "\r\n"; ++ ++ std::string response; ++ bool result = send_request(1, chunked_request, &response); ++ ++ if (!result) { ++ // If send_request fails, it might be because the server closed the ++ // connection due to payload limit enforcement, which is acceptable ++ SUCCEED() ++ << "Server rejected oversized chunked request (connection closed)"; ++ } else { ++ // If we got a response, check if it's an error response or connection was ++ // closed early Short response length indicates connection was closed due to ++ // payload limit ++ if (response.length() <= 10) { ++ SUCCEED() << "Server closed connection for oversized chunked request"; ++ } else { ++ // Check for error status codes ++ EXPECT_TRUE(response.find("413") != std::string::npos || ++ response.find("Payload Too Large") != std::string::npos || ++ response.find("400") != std::string::npos); ++ } ++ } ++} ++ ++TEST_F(PayloadMaxLengthTest, NoContentLengthPayloadLimit) { ++ // Test request without Content-Length header exceeding payload limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add payload exceeding the 8-byte limit ++ std::string large_payload(16, 'X'); // 16 bytes, exceeds 8-byte limit ++ request_without_content_length += large_payload; ++ ++ std::string response; ++ bool result = send_request(1, request_without_content_length, &response); ++ ++ if (!result) { ++ // If send_request fails, server likely closed connection due to payload ++ // limit ++ SUCCEED() << "Server rejected oversized request without Content-Length " ++ "(connection closed)"; ++ } else { ++ // Check if server responded with error or closed connection early ++ if (response.length() <= 10) { ++ SUCCEED() << "Server closed connection for oversized request without " ++ "Content-Length"; ++ } else { ++ // Check for error status codes ++ EXPECT_TRUE(response.find("413") != std::string::npos || ++ response.find("Payload Too Large") != std::string::npos || ++ response.find("400") != std::string::npos); ++ } ++ } ++} ++ ++TEST_F(PayloadMaxLengthTest, NoContentLengthWithinLimit) { ++ // Test request without Content-Length header within payload limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add payload within the 8-byte limit ++ std::string small_payload(4, 'Y'); // 4 bytes, within 8-byte limit ++ request_without_content_length += small_payload; ++ ++ std::string response; ++ bool result = send_request(1, request_without_content_length, &response); ++ ++ // For requests without Content-Length, the server may have different behavior ++ // The key is that it should not reject due to payload limit for small ++ // payloads ++ if (result) { ++ // Check for any HTTP response (success or error, but not connection closed) ++ if (response.length() > 10) { ++ SUCCEED() ++ << "Server processed request without Content-Length within limit"; ++ } else { ++ // Short response might indicate connection closed, which is acceptable ++ SUCCEED() << "Server closed connection for request without " ++ "Content-Length (acceptable behavior)"; ++ } ++ } else { ++ // Connection failure might be due to protocol requirements ++ SUCCEED() << "Connection issue with request without Content-Length " ++ "(environment-specific)"; ++ } ++} ++ ++class LargePayloadMaxLengthTest : public ::testing::Test { ++protected: ++ LargePayloadMaxLengthTest() ++ : cli_(HOST, PORT) ++#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ++ , ++ svr_(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE) ++#endif ++ { ++#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ++ cli_.enable_server_certificate_verification(false); ++#endif ++ } ++ ++ virtual void SetUp() { ++ // Set 10MB payload limit ++ const size_t LARGE_PAYLOAD_LIMIT = 10 * 1024 * 1024; // 10MB ++ svr_.set_payload_max_length(LARGE_PAYLOAD_LIMIT); ++ ++ svr_.Post("/test", [&](const Request & /*req*/, Response &res) { ++ res.set_content("Large payload test", "text/plain"); ++ }); ++ ++ t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); }); ++ svr_.wait_until_ready(); ++ } ++ ++ virtual void TearDown() { ++ svr_.stop(); ++ t_.join(); ++ } ++ ++#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ++ SSLClient cli_; ++ SSLServer svr_; ++#else ++ Client cli_; ++ Server svr_; ++#endif ++ thread t_; ++}; ++ ++TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingWithin10MB) { ++ // Test chunked encoding with payload within 10MB limit ++ std::string medium_payload(5 * 1024 * 1024, ++ 'A'); // 5MB payload, within 10MB limit ++ ++ auto res = cli_.Post("/test", medium_payload, "application/octet-stream"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::OK_200, res->status); ++} ++ ++TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingExceeds10MB) { ++ // Test chunked encoding with payload exceeding 10MB limit ++ std::string large_payload(12 * 1024 * 1024, ++ 'B'); // 12MB payload, exceeds 10MB limit ++ ++ auto res = cli_.Post("/test", large_payload, "application/octet-stream"); ++ ASSERT_TRUE(res); ++ EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status); ++} ++ ++TEST_F(LargePayloadMaxLengthTest, NoContentLengthWithin10MB) { ++ // Test request without Content-Length header within 10MB limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add 1MB payload (within 10MB limit) ++ std::string medium_payload(1024 * 1024, 'C'); // 1MB payload ++ request_without_content_length += medium_payload; ++ ++ std::string response; ++ bool result = send_request(5, request_without_content_length, &response); ++ ++ if (result) { ++ // Should get a proper HTTP response for payloads within limit ++ if (response.length() > 10) { ++ SUCCEED() << "Server processed 1MB request without Content-Length within " ++ "10MB limit"; ++ } else { ++ SUCCEED() << "Server closed connection (acceptable behavior for no " ++ "Content-Length)"; ++ } ++ } else { ++ SUCCEED() << "Connection issue with 1MB payload (environment-specific)"; ++ } ++} ++ ++TEST_F(LargePayloadMaxLengthTest, NoContentLengthExceeds10MB) { ++ // Test request without Content-Length header exceeding 10MB limit ++ std::string request_without_content_length = "POST /test HTTP/1.1\r\n" ++ "Host: " + ++ std::string(HOST) + ":" + ++ std::to_string(PORT) + ++ "\r\n" ++ "Connection: close\r\n" ++ "\r\n"; ++ ++ // Add 12MB payload (exceeds 10MB limit) ++ std::string large_payload(12 * 1024 * 1024, 'D'); // 12MB payload ++ request_without_content_length += large_payload; ++ ++ std::string response; ++ bool result = send_request(10, request_without_content_length, &response); ++ ++ if (!result) { ++ // Server should close connection due to payload limit ++ SUCCEED() << "Server rejected 12MB request without Content-Length " ++ "(connection closed)"; ++ } else { ++ // Check for error response ++ if (response.length() <= 10) { ++ SUCCEED() ++ << "Server closed connection for 12MB request exceeding 10MB limit"; ++ } else { ++ EXPECT_TRUE(response.find("413") != std::string::npos || ++ response.find("Payload Too Large") != std::string::npos || ++ response.find("400") != std::string::npos); ++ } ++ } ++} ++ + TEST(HostAndPortPropertiesTest, NoSSL) { + httplib::Client cli("www.google.com", 1234); + ASSERT_EQ("www.google.com", cli.host()); diff -Nru cpp-httplib-0.18.7/debian/patches/series cpp-httplib-0.18.7/debian/patches/series --- cpp-httplib-0.18.7/debian/patches/series 1970-01-01 01:00:00.000000000 +0100 +++ cpp-httplib-0.18.7/debian/patches/series 2025-08-07 00:19:58.000000000 +0200 @@ -0,0 +1,3 @@ +0001-httplib.h-fix-CVE-2025-46728-DoS-via-unbounded-reque.patch +0002-httplib.h-fix-CVE-2025-52887-Unlimited-number-of-htt.patch +0003-httplib.h-fix-CVE-2025-53629-Unbounded-Memory-Alloca.patch
signature.asc
Description: PGP signature

