Переглянути джерело

Treat 0 as no limit for the remaining limits

set_keep_alive_max_count(0) tripped an assert, a negative WebSocket ping
interval sent pings in a tight loop, and the form length, multipart file
count and range count macros rejected every request at 0. Make 0 mean
no limit for those, and a ping interval of 0 or less turn pings off. The
URI, header and line bounds cannot be lifted, so a static_assert keeps
them positive.
yhirose 1 день тому
батько
коміт
f29721c7d8

+ 8 - 7
README.md

@@ -764,11 +764,12 @@ svr.Post("/content_receiver",
   });
 ```
 
-`CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT` (default 1024) caps the number of
-form-data parts only on the buffered path, where every part is accumulated into
-`req.form`. The content receiver keeps nothing, so the cap does not apply here.
-If your handler needs an upper bound on the number of parts, count them yourself
-and return `false` from the callback to stop the parser.
+`CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT` (default 1024, `0` for no limit)
+caps the number of form-data parts only on the buffered path, where every part
+is accumulated into `req.form`. The content receiver keeps nothing, so the cap
+does not apply here. If your handler needs an upper bound on the number of
+parts, count them yourself and return `false` from the callback to stop the
+parser.
 
 ### Send content with the content provider
 
@@ -884,7 +885,7 @@ svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
 ### Keep-Alive connection
 
 ```cpp
-svr.set_keep_alive_max_count(2); // Default is 100
+svr.set_keep_alive_max_count(2); // Default is 100, 0 for no limit
 svr.set_keep_alive_timeout(10);  // Default is 5
 ```
 
@@ -909,7 +910,7 @@ svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
 ```
 
 > [!NOTE]
-> When the request body content type is 'www-form-urlencoded', the actual payload length shouldn't exceed `CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH`.
+> When the request body content type is 'www-form-urlencoded', the actual payload length shouldn't exceed `CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH` (`0` for no limit).
 
 ### Server-Sent Events
 

+ 1 - 1
docs-src/pages/en/cookbook/s20-keep-alive.md

@@ -10,7 +10,7 @@ status: "draft"
 
 | API | Default | Meaning |
 | --- | --- | --- |
-| `set_keep_alive_max_count` | 100 | Max requests served over a single connection |
+| `set_keep_alive_max_count` | 100 | Max requests served over a single connection (0 for no limit) |
 | `set_keep_alive_timeout` | 5s | How long an idle connection is kept before closing |
 
 ## Basic usage

+ 1 - 1
docs-src/pages/ja/cookbook/s20-keep-alive.md

@@ -10,7 +10,7 @@ status: "draft"
 
 | API | デフォルト | 意味 |
 | --- | --- | --- |
-| `set_keep_alive_max_count` | 100 | 1本の接続で受け付けるリクエストの最大数 |
+| `set_keep_alive_max_count` | 100 | 1本の接続で受け付けるリクエストの最大数(0 で無制限) |
 | `set_keep_alive_timeout` | 5秒 | アイドル状態の接続を閉じるまでの秒数 |
 
 ## 基本の使い方

+ 27 - 11
httplib.h

@@ -223,6 +223,16 @@
 #define CPPHTTPLIB_MAX_LINE_LENGTH 32768
 #endif
 
+// These bound the parsing of every request, so 0 cannot lift them.
+static_assert(CPPHTTPLIB_REQUEST_URI_MAX_LENGTH > 0,
+              "CPPHTTPLIB_REQUEST_URI_MAX_LENGTH must be positive");
+static_assert(CPPHTTPLIB_HEADER_MAX_LENGTH > 0,
+              "CPPHTTPLIB_HEADER_MAX_LENGTH must be positive");
+static_assert(CPPHTTPLIB_HEADER_MAX_COUNT > 0,
+              "CPPHTTPLIB_HEADER_MAX_COUNT must be positive");
+static_assert(CPPHTTPLIB_MAX_LINE_LENGTH > 0,
+              "CPPHTTPLIB_MAX_LINE_LENGTH must be positive");
+
 #ifndef CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH
 #define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH 16777216
 #endif
@@ -6826,20 +6836,19 @@ inline bool process_server_socket_core(const std::atomic<socket_t> &svr_sock,
                                        size_t keep_alive_max_count,
                                        time_t keep_alive_timeout_sec,
                                        P has_buffered_request, T callback) {
-  assert(keep_alive_max_count > 0);
   auto ret = false;
-  auto count = keep_alive_max_count;
-  while (count > 0) {
+  // A keep_alive_max_count of 0 sets no limit.
+  for (size_t served = 0;
+       keep_alive_max_count == 0 || served < keep_alive_max_count; served++) {
     auto socket_readable = false;
     if (!has_buffered_request()) {
       if (!keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { break; }
       socket_readable = true;
     }
-    auto close_connection = count == 1;
+    auto close_connection = served + 1 == keep_alive_max_count;
     auto connection_closed = false;
     ret = callback(socket_readable, close_connection, connection_closed);
     if (!ret || connection_closed) { break; }
-    count--;
   }
   return ret;
 }
@@ -10119,7 +10128,10 @@ inline bool range_error(Request &req, Response &res) {
     // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
 
     // Too many ranges
-    if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
+    if (CPPHTTPLIB_RANGE_MAX_COUNT > 0 &&
+        req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) {
+      return true;
+    }
 
     for (auto &r : req.ranges) {
       auto &first_pos = r.first;
@@ -13844,8 +13856,10 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
   } else {
     std::string s = "timeout=";
     s += std::to_string(keep_alive_timeout_sec_);
-    s += ", max=";
-    s += std::to_string(keep_alive_max_count_);
+    if (keep_alive_max_count_ > 0) {
+      s += ", max=";
+      s += std::to_string(keep_alive_max_count_);
+    }
     res.set_header("Keep-Alive", s);
   }
 
@@ -13996,7 +14010,8 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
           },
           // Multipart FormData
           [&](const FormData &file) {
-            if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
+            if (CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT > 0 &&
+                count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
               output_error_log(Error::TooManyFormDataFiles, &req);
               return false;
             }
@@ -14026,7 +14041,8 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
     const auto &content_type = req.get_header_value("Content-Type");
     if (detail::case_ignore::equal(detail::extract_media_type(content_type),
                                    "application/x-www-form-urlencoded")) {
-      if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
+      if (CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH > 0 &&
+          req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
         res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
         output_error_log(Error::ExceedMaxPayloadSize, &req);
         return false;
@@ -23219,7 +23235,7 @@ inline WebSocket::~WebSocket() {
 }
 
 inline void WebSocket::start_heartbeat() {
-  if (ping_interval_sec_ == 0) { return; }
+  if (ping_interval_sec_ <= 0) { return; }
   ping_thread_ = std::thread([this]() {
     std::unique_lock<std::mutex> lock(ping_mutex_);
     // The predicate keeps a spurious wakeup from sending a ping early

+ 55 - 0
test/test.cc

@@ -12457,6 +12457,35 @@ TEST(KeepAliveTest, Issue1041) {
   EXPECT_EQ(StatusCode::OK_200, result->status);
 }
 
+TEST(KeepAliveTest, MaxCountZeroSetsNoLimit) {
+  Server svr;
+  svr.set_keep_alive_max_count(0);
+  std::set<int> remote_ports;
+  svr.Get("/hi", [&](const Request &req, Response &res) {
+    remote_ports.insert(req.remote_port);
+    res.set_content("hi", "text/plain");
+  });
+
+  auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    listen_thread.join();
+    ASSERT_FALSE(svr.is_running());
+  });
+
+  svr.wait_until_ready();
+
+  Client cli(HOST, PORT);
+  cli.set_keep_alive(true);
+  for (int i = 0; i < 3; i++) {
+    auto res = cli.Get("/hi");
+    ASSERT_TRUE(res);
+    EXPECT_EQ("timeout=5", res->get_header_value("Keep-Alive"));
+  }
+  // All three over one connection
+  EXPECT_EQ(1u, remote_ports.size());
+}
+
 TEST(KeepAliveTest, Issue1959) {
   Server svr;
   svr.set_keep_alive_timeout(5);
@@ -27411,6 +27440,32 @@ TEST(WebSocketTest, AssemblesFragmentsAroundAPing) {
   EXPECT_EQ(std::string("\x8a\x01p", 3), std::string(pong, 3));
 }
 
+TEST(WebSocketTest, NegativePingIntervalSendsNoPings) {
+  Server svr;
+  svr.set_websocket_ping_interval(-1);
+  svr.WebSocket("/ws", [&](const Request &, ws::WebSocket &ws) {
+    std::string msg;
+    while (ws.read(msg)) {}
+  });
+
+  auto port = svr.bind_to_any_port("127.0.0.1");
+  std::thread t([&]() { svr.listen_after_bind(); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    t.join();
+  });
+  svr.wait_until_ready();
+
+  auto sock = connect_raw_websocket(port, "/ws");
+  ASSERT_NE(INVALID_SOCKET, sock);
+  auto se_sock = detail::scope_exit([&] { detail::close_socket(sock); });
+
+  // Nothing comes while the client stays quiet.
+  detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO, 0, 300000);
+  char c;
+  EXPECT_EQ(-1, ::recv(sock, &c, 1, 0));
+}
+
 TEST(WebSocketTest, AnswersAMalformedCloseWithProtocolError) {
   Server svr;
   svr.set_websocket_ping_interval(0);