1
0
Эх сурвалжийг харах

Enforce payload_max_length on decompressed size for unframed requests

For a non-SSL request with neither Content-Length nor Transfer-Encoding,
Server::read_content_core() fell back to reading raw wire bytes with
detail::read_content_without_length() directly, bypassing the decompressor
wrapper that the length-framed and chunked paths already use. As a result,
payload_max_length only bounded the compressed bytes read off the socket,
not the decompressed size a handler could produce from them.

Route this fallback through detail::read_content(..., decompress=true)
instead, the same helper already used below for the length-framed and
chunked cases, so the decompressed-size guard applies uniformly.
yhirose 2 өдөр өмнө
parent
commit
89e0c5c238
2 өөрчлөгдсөн 71 нэмэгдсэн , 9 устгасан
  1. 6 9
      httplib.h
  2. 65 0
      test/test.cc

+ 6 - 9
httplib.h

@@ -12704,15 +12704,12 @@ inline bool Server::read_content_core(
         }
       }
       if (has_data) {
-        auto result =
-            detail::read_content_without_length(strm, payload_max_length_, out);
-        if (result == detail::ReadContentResult::PayloadTooLarge) {
-          res.status = StatusCode::PayloadTooLarge_413;
-          return false;
-        } else if (result != detail::ReadContentResult::Success) {
-          return false;
-        }
-        return true;
+        // Route through the same decompressing reader used by the
+        // length-framed and chunked paths below, so payload_max_length_ is
+        // enforced on the decompressed size here too instead of only on the
+        // compressed wire bytes.
+        return detail::read_content(strm, req, payload_max_length_, res.status,
+                                    nullptr, out, true);
       }
     }
     return true;

+ 65 - 0
test/test.cc

@@ -10898,6 +10898,71 @@ TEST(PayloadLimitBypassTest, StreamingGzipDecompression) {
   // Decompressed bytes delivered to the handler must not exceed LIMIT.
   EXPECT_LE(total, LIMIT);
 }
+
+#ifndef CPPHTTPLIB_SSL_ENABLED
+// For a request with neither Content-Length nor Transfer-Encoding, the
+// non-SSL raw-socket fallback in read_content_core() used to hand the
+// compressed wire bytes straight to the ContentReader without ever routing
+// them through the decompressor, so payload_max_length_ only bounded the
+// compressed size on the wire, not the decompressed size.
+TEST(PayloadLimitBypassTest, UnframedGzipDecompression) {
+  Server svr;
+  const size_t LIMIT = 64 * 1024; // 64KB
+  svr.set_payload_max_length(LIMIT);
+
+  size_t total = 0;
+  svr.Post("/stream", [&](const Request & /*req*/, Response &res,
+                          const ContentReader &content_reader) {
+    content_reader([&](const char * /*data*/, size_t len) {
+      total += len;
+      return true;
+    });
+    res.status = 200;
+    res.set_content("stream_ok", "text/plain");
+  });
+
+  auto port = svr.bind_to_any_port(HOST);
+  auto thread = std::thread([&]() { svr.listen_after_bind(); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    thread.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+  svr.wait_until_ready();
+
+  // Prepare 256KB raw data and gzip-compress it, same payload as the
+  // StreamingGzipDecompression test above.
+  std::string raw(256 * 1024, 'A');
+  std::string gz;
+  {
+    httplib::detail::gzip_compressor compressor;
+    bool result = compressor.compress(raw.data(), raw.size(), /*last=*/true,
+                                      [&](const char *chunk, size_t len) {
+                                        gz.append(chunk, len);
+                                        return true;
+                                      });
+    ASSERT_TRUE(result);
+  }
+
+  // Send the gzip body over raw TCP with neither Content-Length nor
+  // Transfer-Encoding, and Connection: close so the server's stray-body scan
+  // kicks in.
+  std::string req = "POST /stream HTTP/1.1\r\n"
+                    "Host: " +
+                    std::string(HOST) + ":" + std::to_string(port) +
+                    "\r\n"
+                    "Content-Encoding: gzip\r\n"
+                    "Connection: close\r\n"
+                    "\r\n" +
+                    gz;
+
+  std::string response;
+  ASSERT_TRUE(send_request(5, req, &response, port));
+
+  // Decompressed bytes delivered to the handler must not exceed LIMIT.
+  EXPECT_LE(total, LIMIT);
+}
+#endif
 #endif
 
 // Some servers misuse Content-Encoding to advertise a character set, e.g.