Skip to content

Commit 28dcf37

Browse files
authored
Merge commit from fork
1 parent 91e79e9 commit 28dcf37

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

httplib.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@
9090
#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
9191
#endif
9292

93+
#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
94+
#define CPPHTTPLIB_HEADER_MAX_COUNT 100
95+
#endif
96+
9397
#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
9498
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
9599
#endif
@@ -4355,6 +4359,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
43554359
char buf[bufsiz];
43564360
stream_line_reader line_reader(strm, buf, bufsiz);
43574361

4362+
size_t header_count = 0;
4363+
43584364
for (;;) {
43594365
if (!line_reader.getline()) { return false; }
43604366

@@ -4375,6 +4381,9 @@ inline bool read_headers(Stream &strm, Headers &headers) {
43754381

43764382
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
43774383

4384+
// Check header count limit
4385+
if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
4386+
43784387
// Exclude line terminator
43794388
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
43804389

@@ -4384,6 +4393,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
43844393
})) {
43854394
return false;
43864395
}
4396+
4397+
header_count++;
43874398
}
43884399

43894400
return true;
@@ -4486,9 +4497,13 @@ inline bool read_content_chunked(Stream &strm, T &x,
44864497
// chunked transfer coding data without the final CRLF.
44874498
if (!line_reader.getline()) { return true; }
44884499

4500+
size_t trailer_header_count = 0;
44894501
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
44904502
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
44914503

4504+
// Check trailer header count limit
4505+
if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
4506+
44924507
// Exclude line terminator
44934508
constexpr auto line_terminator_len = 2;
44944509
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
@@ -4498,6 +4513,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
44984513
x.headers.emplace(key, val);
44994514
});
45004515

4516+
trailer_header_count++;
4517+
45014518
if (!line_reader.getline()) { return false; }
45024519
}
45034520

test/test.cc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
#include <signal.h>
44

55
#ifndef _WIN32
6+
#include <arpa/inet.h>
67
#include <curl/curl.h>
8+
#include <netinet/in.h>
9+
#include <sys/socket.h>
10+
#include <unistd.h>
711
#endif
812
#include <gtest/gtest.h>
913

@@ -3823,6 +3827,50 @@ TEST_F(ServerTest, TooLongHeader) {
38233827
EXPECT_EQ(StatusCode::OK_200, res->status);
38243828
}
38253829

3830+
TEST_F(ServerTest, HeaderCountAtLimit) {
3831+
// Test with headers just under the 100 limit
3832+
httplib::Headers headers;
3833+
3834+
// Add 95 custom headers (the client will add Host, User-Agent, Accept, etc.)
3835+
// This should keep us just under the 100 header limit
3836+
for (int i = 0; i < 95; i++) {
3837+
std::string name = "X-Test-Header-" + std::to_string(i);
3838+
std::string value = "value" + std::to_string(i);
3839+
headers.emplace(name, value);
3840+
}
3841+
3842+
// This should work fine as we're under the limit
3843+
auto res = cli_.Get("/hi", headers);
3844+
EXPECT_TRUE(res);
3845+
if (res) {
3846+
EXPECT_EQ(StatusCode::OK_200, res->status);
3847+
}
3848+
}
3849+
3850+
TEST_F(ServerTest, HeaderCountExceedsLimit) {
3851+
// Test with many headers to exceed the 100 limit
3852+
httplib::Headers headers;
3853+
3854+
// Add 150 headers to definitely exceed the 100 limit
3855+
for (int i = 0; i < 150; i++) {
3856+
std::string name = "X-Test-Header-" + std::to_string(i);
3857+
std::string value = "value" + std::to_string(i);
3858+
headers.emplace(name, value);
3859+
}
3860+
3861+
// This should fail due to exceeding header count limit
3862+
auto res = cli_.Get("/hi", headers);
3863+
3864+
// The request should either fail or return 400 Bad Request
3865+
if (res) {
3866+
// If we get a response, it should be 400 Bad Request
3867+
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
3868+
} else {
3869+
// Or the request should fail entirely
3870+
EXPECT_FALSE(res);
3871+
}
3872+
}
3873+
38263874
TEST_F(ServerTest, PercentEncoding) {
38273875
auto res = cli_.Get("/e%6edwith%");
38283876
ASSERT_TRUE(res);
@@ -3860,6 +3908,32 @@ TEST_F(ServerTest, PlusSignEncoding) {
38603908
EXPECT_EQ("a +b", res->body);
38613909
}
38623910

3911+
TEST_F(ServerTest, HeaderCountSecurityTest) {
3912+
// This test simulates a potential DoS attack using many headers
3913+
// to verify our security fix prevents memory exhaustion
3914+
3915+
httplib::Headers attack_headers;
3916+
3917+
// Attempt to add many headers like an attacker would (200 headers to far exceed limit)
3918+
for (int i = 0; i < 200; i++) {
3919+
std::string name = "X-Attack-Header-" + std::to_string(i);
3920+
std::string value = "attack_payload_" + std::to_string(i);
3921+
attack_headers.emplace(name, value);
3922+
}
3923+
3924+
// Try to POST with excessive headers
3925+
auto res = cli_.Post("/", attack_headers, "test_data", "text/plain");
3926+
3927+
// Should either fail or return 400 Bad Request due to security limit
3928+
if (res) {
3929+
// If we get a response, it should be 400 Bad Request
3930+
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
3931+
} else {
3932+
// Request failed, which is the expected behavior for DoS protection
3933+
EXPECT_FALSE(res);
3934+
}
3935+
}
3936+
38633937
TEST_F(ServerTest, MultipartFormData) {
38643938
MultipartFormDataItems items = {
38653939
{"text1", "text default", "", ""},

0 commit comments

Comments
 (0)