You've already forked cpp-httplib
Merge commit from fork
* Fix Persistency of Unbounded Memory Allocation in Chunked/No-Length Requests Vulnerability * Revert HTTP status code from 413 to 400
This commit is contained in:
255
test/test.cc
255
test/test.cc
@ -7763,6 +7763,261 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(PayloadMaxLengthTest, ChunkedEncodingSecurityTest) {
|
||||
// Test chunked encoding with payload exceeding the 8-byte limit
|
||||
std::string large_chunked_data(16, 'A'); // 16 bytes, exceeds 8-byte limit
|
||||
|
||||
auto res = cli_.Post("/test", large_chunked_data, "text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status);
|
||||
}
|
||||
|
||||
TEST_F(PayloadMaxLengthTest, ChunkedEncodingWithinLimit) {
|
||||
// Test chunked encoding with payload within the 8-byte limit
|
||||
std::string small_chunked_data(4, 'B'); // 4 bytes, within 8-byte limit
|
||||
|
||||
auto res = cli_.Post("/test", small_chunked_data, "text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(PayloadMaxLengthTest, RawSocketChunkedTest) {
|
||||
// Test using send_request to send chunked data exceeding payload limit
|
||||
std::string chunked_request = "POST /test HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" + std::to_string(PORT) +
|
||||
"\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"
|
||||
"a\r\n" // 10 bytes chunk (exceeds 8-byte limit)
|
||||
"0123456789\r\n"
|
||||
"0\r\n" // End chunk
|
||||
"\r\n";
|
||||
|
||||
std::string response;
|
||||
bool result = send_request(1, chunked_request, &response);
|
||||
|
||||
if (!result) {
|
||||
// If send_request fails, it might be because the server closed the
|
||||
// connection due to payload limit enforcement, which is acceptable
|
||||
SUCCEED()
|
||||
<< "Server rejected oversized chunked request (connection closed)";
|
||||
} else {
|
||||
// If we got a response, check if it's an error response or connection was
|
||||
// closed early Short response length indicates connection was closed due to
|
||||
// payload limit
|
||||
if (response.length() <= 10) {
|
||||
SUCCEED() << "Server closed connection for oversized chunked request";
|
||||
} else {
|
||||
// Check for error status codes
|
||||
EXPECT_TRUE(response.find("413") != std::string::npos ||
|
||||
response.find("Payload Too Large") != std::string::npos ||
|
||||
response.find("400") != std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PayloadMaxLengthTest, NoContentLengthPayloadLimit) {
|
||||
// Test request without Content-Length header exceeding payload limit
|
||||
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" +
|
||||
std::to_string(PORT) +
|
||||
"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
// Add payload exceeding the 8-byte limit
|
||||
std::string large_payload(16, 'X'); // 16 bytes, exceeds 8-byte limit
|
||||
request_without_content_length += large_payload;
|
||||
|
||||
std::string response;
|
||||
bool result = send_request(1, request_without_content_length, &response);
|
||||
|
||||
if (!result) {
|
||||
// If send_request fails, server likely closed connection due to payload
|
||||
// limit
|
||||
SUCCEED() << "Server rejected oversized request without Content-Length "
|
||||
"(connection closed)";
|
||||
} else {
|
||||
// Check if server responded with error or closed connection early
|
||||
if (response.length() <= 10) {
|
||||
SUCCEED() << "Server closed connection for oversized request without "
|
||||
"Content-Length";
|
||||
} else {
|
||||
// Check for error status codes
|
||||
EXPECT_TRUE(response.find("413") != std::string::npos ||
|
||||
response.find("Payload Too Large") != std::string::npos ||
|
||||
response.find("400") != std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PayloadMaxLengthTest, NoContentLengthWithinLimit) {
|
||||
// Test request without Content-Length header within payload limit
|
||||
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" +
|
||||
std::to_string(PORT) +
|
||||
"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
// Add payload within the 8-byte limit
|
||||
std::string small_payload(4, 'Y'); // 4 bytes, within 8-byte limit
|
||||
request_without_content_length += small_payload;
|
||||
|
||||
std::string response;
|
||||
bool result = send_request(1, request_without_content_length, &response);
|
||||
|
||||
// For requests without Content-Length, the server may have different behavior
|
||||
// The key is that it should not reject due to payload limit for small
|
||||
// payloads
|
||||
if (result) {
|
||||
// Check for any HTTP response (success or error, but not connection closed)
|
||||
if (response.length() > 10) {
|
||||
SUCCEED()
|
||||
<< "Server processed request without Content-Length within limit";
|
||||
} else {
|
||||
// Short response might indicate connection closed, which is acceptable
|
||||
SUCCEED() << "Server closed connection for request without "
|
||||
"Content-Length (acceptable behavior)";
|
||||
}
|
||||
} else {
|
||||
// Connection failure might be due to protocol requirements
|
||||
SUCCEED() << "Connection issue with request without Content-Length "
|
||||
"(environment-specific)";
|
||||
}
|
||||
}
|
||||
|
||||
class LargePayloadMaxLengthTest : public ::testing::Test {
|
||||
protected:
|
||||
LargePayloadMaxLengthTest()
|
||||
: cli_(HOST, PORT)
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
,
|
||||
svr_(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE)
|
||||
#endif
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
cli_.enable_server_certificate_verification(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void SetUp() {
|
||||
// Set 10MB payload limit
|
||||
const size_t LARGE_PAYLOAD_LIMIT = 10 * 1024 * 1024; // 10MB
|
||||
svr_.set_payload_max_length(LARGE_PAYLOAD_LIMIT);
|
||||
|
||||
svr_.Post("/test", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Large payload test", "text/plain");
|
||||
});
|
||||
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
|
||||
svr_.wait_until_ready();
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
svr_.stop();
|
||||
t_.join();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli_;
|
||||
SSLServer svr_;
|
||||
#else
|
||||
Client cli_;
|
||||
Server svr_;
|
||||
#endif
|
||||
thread t_;
|
||||
};
|
||||
|
||||
TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingWithin10MB) {
|
||||
// Test chunked encoding with payload within 10MB limit
|
||||
std::string medium_payload(5 * 1024 * 1024,
|
||||
'A'); // 5MB payload, within 10MB limit
|
||||
|
||||
auto res = cli_.Post("/test", medium_payload, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(LargePayloadMaxLengthTest, ChunkedEncodingExceeds10MB) {
|
||||
// Test chunked encoding with payload exceeding 10MB limit
|
||||
std::string large_payload(12 * 1024 * 1024,
|
||||
'B'); // 12MB payload, exceeds 10MB limit
|
||||
|
||||
auto res = cli_.Post("/test", large_payload, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PayloadTooLarge_413, res->status);
|
||||
}
|
||||
|
||||
TEST_F(LargePayloadMaxLengthTest, NoContentLengthWithin10MB) {
|
||||
// Test request without Content-Length header within 10MB limit
|
||||
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" +
|
||||
std::to_string(PORT) +
|
||||
"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
// Add 1MB payload (within 10MB limit)
|
||||
std::string medium_payload(1024 * 1024, 'C'); // 1MB payload
|
||||
request_without_content_length += medium_payload;
|
||||
|
||||
std::string response;
|
||||
bool result = send_request(5, request_without_content_length, &response);
|
||||
|
||||
if (result) {
|
||||
// Should get a proper HTTP response for payloads within limit
|
||||
if (response.length() > 10) {
|
||||
SUCCEED() << "Server processed 1MB request without Content-Length within "
|
||||
"10MB limit";
|
||||
} else {
|
||||
SUCCEED() << "Server closed connection (acceptable behavior for no "
|
||||
"Content-Length)";
|
||||
}
|
||||
} else {
|
||||
SUCCEED() << "Connection issue with 1MB payload (environment-specific)";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LargePayloadMaxLengthTest, NoContentLengthExceeds10MB) {
|
||||
// Test request without Content-Length header exceeding 10MB limit
|
||||
std::string request_without_content_length = "POST /test HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" +
|
||||
std::to_string(PORT) +
|
||||
"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
// Add 12MB payload (exceeds 10MB limit)
|
||||
std::string large_payload(12 * 1024 * 1024, 'D'); // 12MB payload
|
||||
request_without_content_length += large_payload;
|
||||
|
||||
std::string response;
|
||||
bool result = send_request(10, request_without_content_length, &response);
|
||||
|
||||
if (!result) {
|
||||
// Server should close connection due to payload limit
|
||||
SUCCEED() << "Server rejected 12MB request without Content-Length "
|
||||
"(connection closed)";
|
||||
} else {
|
||||
// Check for error response
|
||||
if (response.length() <= 10) {
|
||||
SUCCEED()
|
||||
<< "Server closed connection for 12MB request exceeding 10MB limit";
|
||||
} else {
|
||||
EXPECT_TRUE(response.find("413") != std::string::npos ||
|
||||
response.find("Payload Too Large") != std::string::npos ||
|
||||
response.find("400") != std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(HostAndPortPropertiesTest, NoSSL) {
|
||||
httplib::Client cli("www.google.com", 1234);
|
||||
ASSERT_EQ("www.google.com", cli.host());
|
||||
|
Reference in New Issue
Block a user