Преглед на файлове

Bound the size of an SSE event

The SSE client buffered a line, or the data of an event, with no limit
of its own, and with payload_max_length(0) with none at all. Add
CPPHTTPLIB_SSE_MAX_EVENT_LENGTH (16MB) and end the connection with
ExceedMaxPayloadSize past it.
yhirose преди 1 ден
родител
ревизия
cadcc425b8
променени са 3 файла, в които са добавени 44 реда и са изтрити 0 реда
  1. 2 0
      README-sse.md
  2. 13 0
      httplib.h
  3. 29 0
      test/test.cc

+ 2 - 0
README-sse.md

@@ -79,6 +79,8 @@ sse.set_max_reconnect_attempts(10);
 sse.set_headers({{"Authorization", "Bearer new_token"}});
 ```
 
+An event, or a line still waiting for its end, longer than `CPPHTTPLIB_SSE_MAX_EVENT_LENGTH` (16MB by default, `0` for no limit) ends the connection with `Error::ExceedMaxPayloadSize`, and the client reconnects as usual.
+
 #### Control
 
 ```cpp

+ 13 - 0
httplib.h

@@ -138,6 +138,10 @@
 #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
 #endif
 
+#ifndef CPPHTTPLIB_SSE_MAX_EVENT_LENGTH
+#define CPPHTTPLIB_SSE_MAX_EVENT_LENGTH (16 * 1024 * 1024) // 16MB
+#endif
+
 #ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
 #define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
 #endif
@@ -5084,6 +5088,15 @@ inline void SSEClient::run_event_loop() {
 
       // Keep unprocessed data in buffer
       buffer.erase(0, line_start);
+
+      // Neither an event nor a line still waiting for its end may grow
+      // without bound. 0 means no limit.
+      if (CPPHTTPLIB_SSE_MAX_EVENT_LENGTH > 0 &&
+          buffer.size() + current_msg.data.size() >
+              CPPHTTPLIB_SSE_MAX_EVENT_LENGTH) {
+        if (on_error_) { on_error_(Error::ExceedMaxPayloadSize); }
+        break;
+      }
     }
 
     // Connection ended

+ 29 - 0
test/test.cc

@@ -23809,6 +23809,35 @@ TEST_F(SSEIntegrationTest, SuccessfulConnection) {
   EXPECT_TRUE(message_received.load());
 }
 
+TEST_F(SSEIntegrationTest, TooLongEventEndsTheConnection) {
+  // A data line that never ends.
+  server_->Get("/endless-line", [](const Request &, Response &res) {
+    res.set_chunked_content_provider(
+        "text/event-stream", [](size_t offset, DataSink &sink) {
+          if (offset == 0) { sink.write("data: ", 6); }
+          std::string chunk(64 * 1024, 'A');
+          return sink.write(chunk.data(), chunk.size());
+        });
+  });
+
+  Client client("localhost", get_port());
+  // A stream that may run forever, so that only the event is bounded.
+  client.set_payload_max_length(0);
+  sse::SSEClient sse(client, "/endless-line");
+  std::atomic<bool> too_long{false};
+  sse.on_error([&](Error err) {
+    if (err == Error::ExceedMaxPayloadSize) { too_long = true; }
+  });
+  sse.set_max_reconnect_attempts(1);
+  sse.start_async();
+
+  for (int i = 0; i < 500 && !too_long; i++) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(10));
+  }
+  sse.stop();
+  EXPECT_TRUE(too_long);
+}
+
 // Test: on_message callback
 TEST_F(SSEIntegrationTest, OnMessageCallback) {
   // Endpoint that sends multiple events then closes