From de5a255ac650ba7915320ab610e2618d94d26f1d Mon Sep 17 00:00:00 2001 From: herbrechtsmeier Date: Tue, 24 Jun 2025 18:57:32 +0200 Subject: [PATCH] Fix bad request for multipart form data with boundary split (#2159) * Add test for multipart form data with boundary split Add a test for multipart form data requests with a large header which leads to a split inside the boundary because of the read buffer size inside the SocketStream class. * Fix bad request for multipart form data with boundary split Fix a bad request (400) response for multipart form data requests with a large header which leads to a split inside the boundary because of the read buffer size inside the SocketStream class. The parse function inside the MultipartFormDataParser wrongly erase the receive buffer if it doesn't find the boundary inside the buffer during first call. --- httplib.h | 7 +++---- test/test.cc | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/httplib.h b/httplib.h index 778c1d4..c875fb3 100644 --- a/httplib.h +++ b/httplib.h @@ -5028,10 +5028,9 @@ public: while (buf_size() > 0) { switch (state_) { case 0: { // Initial boundary - buf_erase(buf_find(dash_boundary_crlf_)); - if (dash_boundary_crlf_.size() > buf_size()) { return true; } - if (!buf_start_with(dash_boundary_crlf_)) { return false; } - buf_erase(dash_boundary_crlf_.size()); + auto pos = buf_find(dash_boundary_crlf_); + if (pos == buf_size()) { return true; } + buf_erase(pos + dash_boundary_crlf_.size()); state_ = 1; break; } diff --git a/test/test.cc b/test/test.cc index df52529..b67c87f 100644 --- a/test/test.cc +++ b/test/test.cc @@ -8257,6 +8257,60 @@ TEST(MultipartFormDataTest, AccessPartHeaders) { } #endif +TEST(MultipartFormDataTest, LargeHeader) { + auto handled = false; + + Server svr; + svr.Post("/test", [&](const Request &req, Response &) { + ASSERT_EQ(1u, req.files.size()); + + auto it = req.files.begin(); + ASSERT_EQ("name1", it->second.name); + ASSERT_EQ("text1", it->second.content); + + handled = true; + }); + + thread t = thread([&] { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + ASSERT_TRUE(handled); + }); + + svr.wait_until_ready(); + + auto boundary = std::string("cpp-httplib-multipart-data"); + std::string content = "--" + boundary + + "\r\n" + "Content-Disposition: form-data; name=\"name1\"\r\n" + "\r\n" + "text1\r\n" + "--" + + boundary + "--\r\n"; + std::string header_prefix = "POST /test HTTP/1.1\r\n" + "Content-Type: multipart/form-data;boundary=" + + boundary + + "\r\n" + "Content-Length: " + + std::to_string(content.size()) + + "\r\n" + "Dummy-Header: "; + std::string header_suffix = "\r\n" + "\r\n"; + size_t read_buff_size = 1024u * 4; // SocketStream::read_buff_size_ + size_t header_dummy_size = + read_buff_size - + (header_prefix.size() + header_suffix.size() + boundary.size() / 2); + auto header_dummy = std::string(header_dummy_size, '@'); + auto req = header_prefix + header_dummy + header_suffix + content; + + std::string response; + ASSERT_TRUE(send_request(1, req, &response)); + ASSERT_EQ("200", response.substr(9, 3)); +} + TEST(TaskQueueTest, IncreaseAtomicInteger) { static constexpr unsigned int number_of_tasks{1000000}; std::atomic_uint count{0};