Jelajahi Sumber

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.
yhirose 4 hari lalu
induk
melakukan
672255619b
2 mengubah file dengan 31 tambahan dan 7 penghapusan
  1. 7 7
      httplib.h
  2. 24 0
      test/test.cc

+ 7 - 7
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<size_t>(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<size_t>(len), size_t(1024 * 1024));
+        if (payload_max_length_ > 0) {
+          reserve_len = (std::min)(reserve_len, payload_max_length_);
         }
         res.body.reserve(reserve_len);
       }

+ 24 - 0
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.