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
|
#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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
test/test.cc
74
test/test.cc
@ -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", "", ""},
|
||||||
|
Reference in New Issue
Block a user