You've already forked cpp-httplib
Enforce payload_max_length on decompressed size for unframed requests
For a non-SSL request with neither Content-Length nor Transfer-Encoding, Server::read_content_core() fell back to reading raw wire bytes with detail::read_content_without_length() directly, bypassing the decompressor wrapper that the length-framed and chunked paths already use. As a result, payload_max_length only bounded the compressed bytes read off the socket, not the decompressed size a handler could produce from them. Route this fallback through detail::read_content(..., decompress=true) instead, the same helper already used below for the length-framed and chunked cases, so the decompressed-size guard applies uniformly.
This commit is contained in:
15
httplib.h
15
httplib.h
@@ -12704,15 +12704,12 @@ inline bool Server::read_content_core(
|
||||
}
|
||||
}
|
||||
if (has_data) {
|
||||
auto result =
|
||||
detail::read_content_without_length(strm, payload_max_length_, out);
|
||||
if (result == detail::ReadContentResult::PayloadTooLarge) {
|
||||
res.status = StatusCode::PayloadTooLarge_413;
|
||||
return false;
|
||||
} else if (result != detail::ReadContentResult::Success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// Route through the same decompressing reader used by the
|
||||
// length-framed and chunked paths below, so payload_max_length_ is
|
||||
// enforced on the decompressed size here too instead of only on the
|
||||
// compressed wire bytes.
|
||||
return detail::read_content(strm, req, payload_max_length_, res.status,
|
||||
nullptr, out, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
65
test/test.cc
65
test/test.cc
@@ -10898,6 +10898,71 @@ TEST(PayloadLimitBypassTest, StreamingGzipDecompression) {
|
||||
// Decompressed bytes delivered to the handler must not exceed LIMIT.
|
||||
EXPECT_LE(total, LIMIT);
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_SSL_ENABLED
|
||||
// For a request with neither Content-Length nor Transfer-Encoding, the
|
||||
// non-SSL raw-socket fallback in read_content_core() used to hand the
|
||||
// compressed wire bytes straight to the ContentReader without ever routing
|
||||
// them through the decompressor, so payload_max_length_ only bounded the
|
||||
// compressed size on the wire, not the decompressed size.
|
||||
TEST(PayloadLimitBypassTest, UnframedGzipDecompression) {
|
||||
Server svr;
|
||||
const size_t LIMIT = 64 * 1024; // 64KB
|
||||
svr.set_payload_max_length(LIMIT);
|
||||
|
||||
size_t total = 0;
|
||||
svr.Post("/stream", [&](const Request & /*req*/, Response &res,
|
||||
const ContentReader &content_reader) {
|
||||
content_reader([&](const char * /*data*/, size_t len) {
|
||||
total += len;
|
||||
return true;
|
||||
});
|
||||
res.status = 200;
|
||||
res.set_content("stream_ok", "text/plain");
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
auto thread = std::thread([&]() { svr.listen_after_bind(); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Prepare 256KB raw data and gzip-compress it, same payload as the
|
||||
// StreamingGzipDecompression test above.
|
||||
std::string raw(256 * 1024, 'A');
|
||||
std::string gz;
|
||||
{
|
||||
httplib::detail::gzip_compressor compressor;
|
||||
bool result = compressor.compress(raw.data(), raw.size(), /*last=*/true,
|
||||
[&](const char *chunk, size_t len) {
|
||||
gz.append(chunk, len);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Send the gzip body over raw TCP with neither Content-Length nor
|
||||
// Transfer-Encoding, and Connection: close so the server's stray-body scan
|
||||
// kicks in.
|
||||
std::string req = "POST /stream HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" + std::to_string(port) +
|
||||
"\r\n"
|
||||
"Content-Encoding: gzip\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n" +
|
||||
gz;
|
||||
|
||||
std::string response;
|
||||
ASSERT_TRUE(send_request(5, req, &response, port));
|
||||
|
||||
// Decompressed bytes delivered to the handler must not exceed LIMIT.
|
||||
EXPECT_LE(total, LIMIT);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Some servers misuse Content-Encoding to advertise a character set, e.g.
|
||||
|
||||
Reference in New Issue
Block a user