Limit the number of header lines per multipart form-data part

The multipart/form-data parser bounded the length of each individual
header line via CPPHTTPLIB_HEADER_MAX_LENGTH but did not cap how many
header lines a single part could contain. Within the overall payload
limit, a client could pack a large number of small header lines into
one part, each of which is parsed twice, inflating CPU usage.

Add a per-part header-line counter that resets when a new entry begins
and abort parsing once it reaches CPPHTTPLIB_HEADER_MAX_COUNT, mirroring
the limit already enforced by read_headers() for request headers.
This commit is contained in:
Mario Limonciello
2026-07-18 19:20:51 -05:00
committed by Mario Limonciello (AMD)
parent 0c1cc8c986
commit 877a52f6b3
2 changed files with 46 additions and 0 deletions
+9
View File
@@ -8095,6 +8095,13 @@ public:
break;
}
// Check header count limit
if (header_count_ >= CPPHTTPLIB_HEADER_MAX_COUNT) {
is_valid_ = false;
return false;
}
header_count_++;
const auto header = buf_head(pos);
if (!parse_header(header.data(), header.data() + header.size(),
@@ -8210,6 +8217,7 @@ private:
file_.filename.clear();
file_.content_type.clear();
file_.headers.clear();
header_count_ = 0;
}
bool start_with_case_ignore(const std::string &a, const char *b,
@@ -8263,6 +8271,7 @@ private:
size_t state_ = 0;
bool is_valid_ = false;
FormData file_;
size_t header_count_ = 0;
// Buffer
bool start_with(const std::string &a, size_t spos, size_t epos,
+37
View File
@@ -13612,6 +13612,43 @@ TEST(MultipartFormDataTest, MakeFileProvider) {
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST(MultipartFormDataTest, ExcessivePartHeaders) {
// Verify that a multipart part with more than CPPHTTPLIB_HEADER_MAX_COUNT
// (100) headers is rejected with a 400 Bad Request response.
Server svr;
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain");
});
thread t = thread([&] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
// Build a multipart part with 101 custom headers (exceeding CPPHTTPLIB_HEADER_MAX_COUNT=100)
std::stringstream body;
body << "--simpleboundary\r\n"
<< "Content-Disposition: form-data; name=\"field1\"\r\n";
for (int i = 0; i < 101; i++) {
body << "X-Custom-Header-" << i << ": value\r\n";
}
body << "\r\n"
<< "value1\r\n"
<< "--simpleboundary--\r\n";
std::string content_type = "multipart/form-data; boundary=simpleboundary";
Client cli(HOST, PORT);
auto res = cli.Post("/post", body.str(), content_type.c_str());
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
}
TEST(MakeFileBodyTest, Basic) {
const std::string file_content(4096, 'Z');
const std::string tmp_path = "./httplib_test_make_file_body.bin";