ソースを参照

Merge commit from fork

yhirose 7 ヶ月 前
コミット
28dcf379e8
2 ファイル変更91 行追加0 行削除
  1. 17 0
      httplib.h
  2. 74 0
      test/test.cc

+ 17 - 0
httplib.h

@@ -90,6 +90,10 @@
 #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
 #endif
 
+#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
+#define CPPHTTPLIB_HEADER_MAX_COUNT 100
+#endif
+
 #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
 #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
 #endif
@@ -4355,6 +4359,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
   char buf[bufsiz];
   stream_line_reader line_reader(strm, buf, bufsiz);
 
+  size_t header_count = 0;
+
   for (;;) {
     if (!line_reader.getline()) { return false; }
 
@@ -4375,6 +4381,9 @@ inline bool read_headers(Stream &strm, Headers &headers) {
 
     if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
 
+    // Check header count limit
+    if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
+
     // Exclude line terminator
     auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
 
@@ -4384,6 +4393,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
                       })) {
       return false;
     }
+
+    header_count++;
   }
 
   return true;
@@ -4486,9 +4497,13 @@ inline bool read_content_chunked(Stream &strm, T &x,
   // chunked transfer coding data without the final CRLF.
   if (!line_reader.getline()) { return true; }
 
+  size_t trailer_header_count = 0;
   while (strcmp(line_reader.ptr(), "\r\n") != 0) {
     if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
 
+    // Check trailer header count limit
+    if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
+
     // Exclude line terminator
     constexpr auto line_terminator_len = 2;
     auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
@@ -4498,6 +4513,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
                    x.headers.emplace(key, val);
                  });
 
+    trailer_header_count++;
+
     if (!line_reader.getline()) { return false; }
   }
 

+ 74 - 0
test/test.cc

@@ -3,7 +3,11 @@
 #include <signal.h>
 
 #ifndef _WIN32
+#include <arpa/inet.h>
 #include <curl/curl.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
 #endif
 #include <gtest/gtest.h>
 
@@ -3823,6 +3827,50 @@ TEST_F(ServerTest, TooLongHeader) {
   EXPECT_EQ(StatusCode::OK_200, res->status);
 }
 
+TEST_F(ServerTest, HeaderCountAtLimit) {
+  // Test with headers just under the 100 limit
+  httplib::Headers headers;
+  
+  // Add 95 custom headers (the client will add Host, User-Agent, Accept, etc.)
+  // This should keep us just under the 100 header limit
+  for (int i = 0; i < 95; i++) {
+    std::string name = "X-Test-Header-" + std::to_string(i);
+    std::string value = "value" + std::to_string(i);
+    headers.emplace(name, value);
+  }
+  
+  // This should work fine as we're under the limit
+  auto res = cli_.Get("/hi", headers);
+  EXPECT_TRUE(res);
+  if (res) {
+    EXPECT_EQ(StatusCode::OK_200, res->status);
+  }
+}
+
+TEST_F(ServerTest, HeaderCountExceedsLimit) {
+  // Test with many headers to exceed the 100 limit
+  httplib::Headers headers;
+  
+  // Add 150 headers to definitely exceed the 100 limit
+  for (int i = 0; i < 150; i++) {
+    std::string name = "X-Test-Header-" + std::to_string(i);
+    std::string value = "value" + std::to_string(i);
+    headers.emplace(name, value);
+  }
+  
+  // This should fail due to exceeding header count limit
+  auto res = cli_.Get("/hi", headers);
+  
+  // The request should either fail or return 400 Bad Request
+  if (res) {
+    // If we get a response, it should be 400 Bad Request
+    EXPECT_EQ(StatusCode::BadRequest_400, res->status);
+  } else {
+    // Or the request should fail entirely
+    EXPECT_FALSE(res);
+  }
+}
+
 TEST_F(ServerTest, PercentEncoding) {
   auto res = cli_.Get("/e%6edwith%");
   ASSERT_TRUE(res);
@@ -3860,6 +3908,32 @@ TEST_F(ServerTest, PlusSignEncoding) {
   EXPECT_EQ("a +b", res->body);
 }
 
+TEST_F(ServerTest, HeaderCountSecurityTest) {
+  // This test simulates a potential DoS attack using many headers
+  // to verify our security fix prevents memory exhaustion
+  
+  httplib::Headers attack_headers;
+  
+  // Attempt to add many headers like an attacker would (200 headers to far exceed limit)
+  for (int i = 0; i < 200; i++) {
+    std::string name = "X-Attack-Header-" + std::to_string(i);
+    std::string value = "attack_payload_" + std::to_string(i);
+    attack_headers.emplace(name, value);
+  }
+  
+  // Try to POST with excessive headers
+  auto res = cli_.Post("/", attack_headers, "test_data", "text/plain");
+  
+  // Should either fail or return 400 Bad Request due to security limit
+  if (res) {
+    // If we get a response, it should be 400 Bad Request
+    EXPECT_EQ(StatusCode::BadRequest_400, res->status);
+  } else {
+    // Request failed, which is the expected behavior for DoS protection
+    EXPECT_FALSE(res);
+  }
+}
+
 TEST_F(ServerTest, MultipartFormData) {
   MultipartFormDataItems items = {
       {"text1", "text default", "", ""},