From 28e6e56b80919fd83058da0dd6575fd4620b1826 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sat, 13 Jun 2026 19:36:37 +0000 Subject: [PATCH 1/2] Fix parser::body() abort on data past the body --- src/parser.cpp | 3 +-- test/unit/parser.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index af60a7cc..aacdd398 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1316,8 +1316,7 @@ class parser::impl detail::throw_logic_error(); auto cbp = (is_plain() ? cb0_ : cb1_).data(); - BOOST_ASSERT(cbp[1].size() == 0); - BOOST_ASSERT(cbp[0].size() == body_avail_); + BOOST_ASSERT(body_avail_ <= cbp[0].size()); return core::string_view( static_cast(cbp[0].data()), body_avail_); diff --git a/test/unit/parser.cpp b/test/unit/parser.cpp index f14db6c6..833567eb 100644 --- a/test/unit/parser.cpp +++ b/test/unit/parser.cpp @@ -1748,6 +1748,50 @@ struct parser_test pr.get().payload(), payload::chunked); } + void + testBodyWithTrailingData() + { + parser_config cfg{false}; + cfg.headers.max_size = 100; + cfg.min_buffer = 100; + auto pcfg = make_parser_config(cfg); + + auto const check = [&pcfg]( + core::string_view octets, + core::string_view body) + { + response_parser pr(pcfg); + pr.reset(); + pr.start(); + pr.commit(capy::buffer_copy( + pr.prepare(), + capy::const_buffer( + octets.data(), octets.size()))); + + system::error_code ec; + pr.parse(ec); + BOOST_TEST(! ec); + BOOST_TEST(pr.got_header()); + pr.parse(ec); + BOOST_TEST(! ec); + BOOST_TEST(pr.is_complete()); + BOOST_TEST_EQ(pr.body(), body); + }; + + check( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2\r\n" + "\r\n" + "ok" + "HTTP/1.1 200 OK\r\n", "ok"); + + check( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2\r\n" + "\r\n" + "okX", "ok"); + } + void run() { @@ -1764,6 +1808,7 @@ struct parser_test testMultipleMessageInPlaceChunked(); testSetBodyLimit(); testAccessHeaderAfterBodyError(); + testBodyWithTrailingData(); #else // For profiling for(int i = 0; i < 10000; ++i ) From 5f4bebc5c8b3002f4ac2f279087d06c9a7560688 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sat, 13 Jun 2026 19:37:42 +0000 Subject: [PATCH 2/2] Add parser::has_buffered_data() --- include/boost/http/parser.hpp | 22 ++++++++ src/parser.cpp | 19 +++++++ test/unit/parser.cpp | 95 +++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/include/boost/http/parser.hpp b/include/boost/http/parser.hpp index f466a3ad..321c2ab9 100644 --- a/include/boost/http/parser.hpp +++ b/include/boost/http/parser.hpp @@ -307,6 +307,28 @@ class parser core::string_view body() const; + /** Return true if data is buffered past the message. + + After a complete message, returns true when the + parser's buffer still holds octets that lie + beyond it, such as the start of a pipelined + message or data the peer sent past the message + framing. Returns false before the message is + complete. + + This does not include the message body, which is + retrieved separately via @ref body or + @ref pull_body. + + @return true if octets remain buffered past the + completed message. + + @see @ref is_complete, @ref release_buffered_data. + */ + BOOST_HTTP_DECL + bool + has_buffered_data() const noexcept; + /** Return unconsumed data past the last message. Use this after an upgrade or CONNECT request diff --git a/src/parser.cpp b/src/parser.cpp index aacdd398..a64d36c2 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1322,6 +1322,17 @@ class parser::impl body_avail_); } + bool + has_buffered_data() const noexcept + { + if(state_ != state::complete) + return false; + + if(is_plain()) + return cb0_.size() > body_avail_; + return cb0_.size() > 0; + } + void set_body_limit(std::uint64_t n) { @@ -1578,6 +1589,14 @@ release_buffered_data() noexcept return {}; } +bool +parser:: +has_buffered_data() const noexcept +{ + BOOST_ASSERT(impl_); + return impl_->has_buffered_data(); +} + void parser:: set_body_limit(std::uint64_t n) diff --git a/test/unit/parser.cpp b/test/unit/parser.cpp index 833567eb..50247afd 100644 --- a/test/unit/parser.cpp +++ b/test/unit/parser.cpp @@ -1776,6 +1776,7 @@ struct parser_test BOOST_TEST(! ec); BOOST_TEST(pr.is_complete()); BOOST_TEST_EQ(pr.body(), body); + BOOST_TEST(pr.has_buffered_data()); }; check( @@ -1792,6 +1793,99 @@ struct parser_test "okX", "ok"); } + void + testHasBufferedData() + { + parser_config cfg{false}; + cfg.headers.max_size = 100; + cfg.min_buffer = 100; + auto pcfg = make_parser_config(cfg); + + auto const check = [&pcfg]( + core::string_view octets, + core::string_view body, + bool buffered) + { + response_parser pr(pcfg); + pr.reset(); + pr.start(); + pr.commit(capy::buffer_copy( + pr.prepare(), + capy::const_buffer( + octets.data(), octets.size()))); + + system::error_code ec; + pr.parse(ec); + BOOST_TEST(! ec); + BOOST_TEST(pr.got_header()); + pr.parse(ec); + BOOST_TEST(! ec); + BOOST_TEST(pr.is_complete()); + + // the body is not counted as buffered data + BOOST_TEST_EQ(pr.body(), body); + BOOST_TEST_EQ(pr.has_buffered_data(), buffered); + }; + + check( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2\r\n" + "\r\n" + "ok", "ok", false); + + check( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2\r\n" + "\r\n" + "ok" + "HTTP/1.1 200 OK\r\n", "ok", true); + + check( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 2\r\n" + "\r\n" + "okX", "ok", true); + + check( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\nok\r\n0\r\n\r\n", "ok", false); + + check( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\nok\r\n0\r\n\r\n" + "GARBAGE", "ok", true); + + check( + "HTTP/1.1 204 No Content\r\n" + "\r\n" + "GARBAGE", "", true); + + { + response_parser pr(pcfg); + pr.reset(); + pr.start(); + core::string_view octets = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 10\r\n" + "\r\n" + "123"; // body incomplete + pr.commit(capy::buffer_copy( + pr.prepare(), + capy::const_buffer( + octets.data(), octets.size()))); + system::error_code ec; + pr.parse(ec); + BOOST_TEST(pr.got_header()); + pr.parse(ec); + BOOST_TEST(! pr.is_complete()); + BOOST_TEST(! pr.has_buffered_data()); + } + } + void run() { @@ -1809,6 +1903,7 @@ struct parser_test testSetBodyLimit(); testAccessHeaderAfterBodyError(); testBodyWithTrailingData(); + testHasBufferedData(); #else // For profiling for(int i = 0; i < 10000; ++i )