From 2e2e47bab1ae6a853476eecbc4bf279dd1fef792 Mon Sep 17 00:00:00 2001
From: yhirose <yhirose@users.noreply.github.com>
Date: Fri, 9 Jan 2026 21:09:07 -0500
Subject: [PATCH] Merge commit from fork

* Ensure payload_max_length_ is respected for compressed payloads

* Fix Denial of service (DOS) using zip bomb

---------

Co-authored-by: Hritik Vijay <hey@hritik.sh>
---
 httplib.h    | 13 +++++--------
 test/test.cc | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+), 8 deletions(-)

Index: cpp-httplib-0.22.0/httplib.h
===================================================================
--- cpp-httplib-0.22.0.orig/httplib.h
+++ cpp-httplib-0.22.0/httplib.h
@@ -7014,7 +7014,13 @@ inline bool Server::read_content(Stream
           strm, req, res,
           // Regular
           [&](const char *buf, size_t n) {
-            if (req.body.size() + n > req.body.max_size()) { return false; }
+            // Limit decompressed body size to payload_max_length_ to protect
+            // against "zip bomb" attacks where a small compressed payload
+            // decompresses to a massive size.
+            if (req.body.size() + n > payload_max_length_ ||
+                req.body.size() + n > req.body.max_size()) {
+              return false;
+            }
             req.body.append(buf, n);
             return true;
           },
Index: cpp-httplib-0.22.0/test/test.cc
===================================================================
--- cpp-httplib-0.22.0.orig/test/test.cc
+++ cpp-httplib-0.22.0/test/test.cc
@@ -9441,3 +9441,52 @@ TEST(HeaderSmugglingTest, ChunkedTrailer
   std::string res;
   ASSERT_TRUE(send_request(1, req, &res));
 }
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+TEST(ZipBombProtectionTest, DecompressedSizeExceedsLimit) {
+  Server svr;
+
+  // Set a small payload limit (1KB)
+  svr.set_payload_max_length(1024);
+
+  svr.Post("/test", [&](const Request &req, Response &res) {
+    res.set_content("Body size: " + std::to_string(req.body.size()),
+                    "text/plain");
+  });
+
+  auto listen_thread = std::thread([&]() { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    listen_thread.join();
+  });
+
+  svr.wait_until_ready();
+
+  // Create data that compresses well but exceeds limit when decompressed
+  // 8KB of repeated null bytes compresses to a very small size
+  std::string original_data(8 * 1024, '\0');
+
+  // Compress the data using gzip
+  std::string compressed_data;
+  detail::gzip_compressor compressor;
+  compressor.compress(original_data.data(), original_data.size(), true,
+                      [&](const char *data, size_t size) {
+                        compressed_data.append(data, size);
+                        return true;
+                      });
+
+  // Verify compression worked (compressed should be much smaller)
+  ASSERT_LT(compressed_data.size(), original_data.size());
+  ASSERT_LT(compressed_data.size(), 1024u); // Compressed fits in limit
+
+  // Send compressed data with Content-Encoding: gzip
+  Client cli(HOST, PORT);
+  Headers headers = {{"Content-Encoding", "gzip"}};
+  auto res =
+      cli.Post("/test", headers, compressed_data, "application/octet-stream");
+
+  // Server should reject because decompressed size (8KB) exceeds limit (1KB)
+  ASSERT_TRUE(res);
+  EXPECT_EQ(StatusCode::BadRequest_400, res->status);
+}
+#endif
