1
0
mirror of synced 2025-07-02 20:02:24 +03:00

Merge commit from fork

This commit is contained in:
yhirose
2025-06-24 07:56:00 -04:00
committed by GitHub
parent 91e79e9a63
commit 28dcf379e8
2 changed files with 91 additions and 0 deletions

View File

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

View File

@ -3,7 +3,11 @@
#include <signal.h> #include <signal.h>
#ifndef _WIN32 #ifndef _WIN32
#include <arpa/inet.h>
#include <curl/curl.h> #include <curl/curl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif #endif
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -3823,6 +3827,50 @@ TEST_F(ServerTest, TooLongHeader) {
EXPECT_EQ(StatusCode::OK_200, res->status); 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) { TEST_F(ServerTest, PercentEncoding) {
auto res = cli_.Get("/e%6edwith%"); auto res = cli_.Get("/e%6edwith%");
ASSERT_TRUE(res); ASSERT_TRUE(res);
@ -3860,6 +3908,32 @@ TEST_F(ServerTest, PlusSignEncoding) {
EXPECT_EQ("a +b", res->body); 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) { TEST_F(ServerTest, MultipartFormData) {
MultipartFormDataItems items = { MultipartFormDataItems items = {
{"text1", "text default", "", ""}, {"text1", "text default", "", ""},