You've already forked cpp-httplib
Merge commit from fork
This commit is contained in:
17
httplib.h
17
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
test/test.cc
74
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", "", ""},
|
||||
|
Reference in New Issue
Block a user