From 337fbb0793c598046d636d4450a5dcf8d3620a0b Mon Sep 17 00:00:00 2001 From: yhirose Date: Tue, 25 Nov 2025 20:30:43 -0500 Subject: [PATCH] Fix #2279 Enhance request handling: add support for requests without Content-Length or Transfer-Encoding headers --- httplib.h | 6 ++++- test/test.cc | 76 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/httplib.h b/httplib.h index 1049cc2..c9506ce 100644 --- a/httplib.h +++ b/httplib.h @@ -7932,7 +7932,11 @@ inline bool Server::read_content_core( size_t /*len*/) { return receiver(buf, n); }; } - if (req.method == "DELETE" && !req.has_header("Content-Length")) { + // RFC 7230 Section 3.3.3: If this is a request message and none of the above + // are true (no Transfer-Encoding and no Content-Length), then the message + // body length is zero (no message body is present). + if (!req.has_header("Content-Length") && + !detail::is_chunked_transfer_encoding(req.headers)) { return true; } diff --git a/test/test.cc b/test/test.cc index 6944c7b..72e0436 100644 --- a/test/test.cc +++ b/test/test.cc @@ -3424,7 +3424,7 @@ protected: res.set_content(req.body, "text/plain"); }) .Post("/post-loopback", - [&](const Request &req, Response &res, + [&](const Request &, Response &res, ContentReader const &content_reader) { std::string body; content_reader([&](const char *data, size_t data_length) { @@ -3435,7 +3435,7 @@ protected: res.set_content(body, "text/plain"); }) .Put("/put-loopback", - [&](const Request &req, Response &res, + [&](const Request &, Response &res, ContentReader const &content_reader) { std::string body; content_reader([&](const char *data, size_t data_length) { @@ -3446,7 +3446,7 @@ protected: res.set_content(body, "text/plain"); }) .Patch("/patch-loopback", - [&](const Request &req, Response &res, + [&](const Request &, Response &res, ContentReader const &content_reader) { std::string body; content_reader([&](const char *data, size_t data_length) { @@ -11609,3 +11609,73 @@ TEST(ForwardedHeadersTest, HandlesWhitespaceAroundIPs) { EXPECT_EQ(observed_xff, "198.51.100.23 , 203.0.113.66 , 192.0.2.45"); EXPECT_EQ(observed_remote_addr, "203.0.113.66"); } + +TEST(ServerRequestParsingTest, RequestWithoutContentLengthOrTransferEncoding) { + Server svr; + + svr.Post("/post", [&](const Request &req, Response &res) { + res.set_content(req.body, "text/plain"); + }); + + svr.Put("/put", [&](const Request &req, Response &res) { + res.set_content(req.body, "text/plain"); + }); + + svr.Patch("/patch", [&](const Request &req, Response &res) { + res.set_content(req.body, "text/plain"); + }); + + svr.Delete("/delete", [&](const Request &req, Response &res) { + res.set_content(req.body, "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(); + + std::string resp; + + // POST without Content-Length + ASSERT_TRUE(send_request(5, + "POST /post HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n", + &resp)); + EXPECT_TRUE(resp.find("HTTP/1.1 200 OK") == 0); + + // PUT without Content-Length + resp.clear(); + ASSERT_TRUE(send_request(5, + "PUT /put HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n", + &resp)); + EXPECT_TRUE(resp.find("HTTP/1.1 200 OK") == 0); + + // PATCH without Content-Length + resp.clear(); + ASSERT_TRUE(send_request(5, + "PATCH /patch HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n", + &resp)); + EXPECT_TRUE(resp.find("HTTP/1.1 200 OK") == 0); + + // DELETE without Content-Length + resp.clear(); + ASSERT_TRUE(send_request(5, + "DELETE /delete HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n", + &resp)); + EXPECT_TRUE(resp.find("HTTP/1.1 200 OK") == 0); +}