mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-20 11:29:38 +00:00
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:
1 parent
f00e476f1b
commit
89e0c5c238
2 files changed
+71
-9
No files matched your search
@@ -12704,15 +12704,12 @@ inline bool Server::read_content_core(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (has_data) {
|
if (has_data) {
|
||||||
auto result =
|
// Route through the same decompressing reader used by the
|
||||||
detail::read_content_without_length(strm, payload_max_length_, out);
|
// length-framed and chunked paths below, so payload_max_length_ is
|
||||||
if (result == detail::ReadContentResult::PayloadTooLarge) {
|
// enforced on the decompressed size here too instead of only on the
|
||||||
res.status = StatusCode::PayloadTooLarge_413;
|
// compressed wire bytes.
|
||||||
return false;
|
return detail::read_content(strm, req, payload_max_length_, res.status,
|
||||||
} else if (result != detail::ReadContentResult::Success) {
|
nullptr, out, true);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -10898,6 +10898,71 @@ TEST(PayloadLimitBypassTest, StreamingGzipDecompression) {
|
|||||||
// Decompressed bytes delivered to the handler must not exceed LIMIT.
|
// Decompressed bytes delivered to the handler must not exceed LIMIT.
|
||||||
EXPECT_LE(total, 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
|
#endif
|
||||||
|
|
||||||
// Some servers misuse Content-Encoding to advertise a character set, e.g.
|
// Some servers misuse Content-Encoding to advertise a character set, e.g.
|
||||||
|
|||||||
Reference in new issue
Block a user