From fbe7a13d0b7c2a42978b3b0ec83bd65e1e668d1e Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sun, 21 Jun 2026 07:34:55 +0000 Subject: [PATCH 1/3] update build.yml --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b94dde3a95..c9a2cafff8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,7 @@ concurrency: env: UBSAN_OPTIONS: print_stacktrace=1 + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true jobs: runner-selection: From e23dd381c91a3d6f16a9f740383ad54845b7c6e3 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sun, 21 Jun 2026 07:03:39 +0000 Subject: [PATCH 2/3] reject request transfer-encoding that does not end in chunked --- .../boost/beast/http/impl/basic_parser.ipp | 9 + test/beast/http/basic_parser.cpp | 168 +++++++++++++----- 2 files changed, 132 insertions(+), 45 deletions(-) diff --git a/include/boost/beast/http/impl/basic_parser.ipp b/include/boost/beast/http/impl/basic_parser.ipp index b5449eed49..1b3c5ec2cf 100644 --- a/include/boost/beast/http/impl/basic_parser.ipp +++ b/include/boost/beast/http/impl/basic_parser.ipp @@ -442,6 +442,15 @@ finish_header(error_code& ec, std::true_type) return; } + // if a Transfer-Encoding is present in a request and chunked is not the + // final coding, the body length cannot be determined reliably; reject the + // request instead of silently treating it as having no body. + if((f_ & flagTransferEncoding) && ! (f_ & flagChunked)) + { + BOOST_BEAST_ASSIGN_EC(ec, error::bad_transfer_encoding); + return; + } + if(f_ & flagSkipBody) { state_ = state::complete; diff --git a/test/beast/http/basic_parser.cpp b/test/beast/http/basic_parser.cpp index 17ed3e65e1..1ee1a45764 100644 --- a/test/beast/http/basic_parser.cpp +++ b/test/beast/http/basic_parser.cpp @@ -738,78 +738,141 @@ class basic_parser_test : public beast::unit_test::suite auto const ce = [](std::string const& s) { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; }; auto const te = [](std::string const& s) - { return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; }; - - using P = test_parser; - - parsegrind

(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); - parsegrind

(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + { return "HTTP/1.1 200 OK\r\nTransfer-Encoding: " + s + "\r\n"; }; + + using Preq = test_parser; + using Pres = test_parser; + + parsegrind(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); // Technically invalid but beyond the parser's scope to detect // VFALCO Look into this - //parsegrind

(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); - - parsegrind

(te("gzip\r\n"), expect_flags{*this, 0}); - parsegrind

(te("chunked, gzip\r\n"), expect_flags{*this, 0}); - parsegrind

(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); - parsegrind

(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); - parsegrind

(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); - parsegrind

(te("bigchunked\r\n"), expect_flags{*this, 0}); - parsegrind

(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); - parsegrind

(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); - parsegrind

(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); - - parsegrind

(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); - parsegrind

(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); - parsegrind

(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); - - failgrind>( + //parsegrind(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + parsegrind(te("gzip\r\n"), expect_flags{*this, 0}); + parsegrind(te("chunked, gzip\r\n"), expect_flags{*this, 0}); + parsegrind(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); + parsegrind(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); + parsegrind(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); + parsegrind(te("bigchunked\r\n"), expect_flags{*this, 0}); + parsegrind(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); + parsegrind(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); + parsegrind(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); + + parsegrind(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); + parsegrind(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); + parsegrind(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); + + failgrind( "HTTP/1.1 200 OK\r\n" "Content-Length: 1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n", error::bad_transfer_encoding); + // repeated Transfer-Encoding fields concatenate + parsegrind( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: gzip\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "0\r\n\r\n", + expect_flags{*this, parse_flag::chunked}); + + // a coding after chunked means chunked is not final + failgrind( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "Transfer-Encoding: gzip\r\n" + "\r\n", + error::bad_transfer_encoding); + + // chunked applied twice is invalid + failgrind( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", + error::bad_transfer_encoding); + + // a request whose Transfer-Encoding does not end in chunked has an + // indeterminate body length and must be rejected (RFC 7230 3.3.3) + failgrind( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: gzip\r\n" + "\r\n", error::bad_transfer_encoding); + failgrind( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked, gzip\r\n" + "\r\n", error::bad_transfer_encoding); + parsegrind( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: gzip\r\n" + "\r\n", expect_flags{*this, 0}); + parsegrind( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked, gzip\r\n" + "\r\n", expect_flags{*this, 0}); + // chunked is an HTTP/1.1 transfer coding; reject a request that // resolves to chunked framing over HTTP/1.0 - failgrind>( + failgrind( "GET / HTTP/1.0\r\n" "Transfer-Encoding: chunked\r\n" "\r\n0\r\n\r\n", error::bad_transfer_encoding); - failgrind>( + failgrind( "GET / HTTP/1.0\r\n" "Transfer-Encoding: gzip, chunked\r\n" "\r\n0\r\n\r\n", error::bad_transfer_encoding); + parsegrind( + "HTTP/1.0 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n0\r\n\r\n", expect_flags{*this, parse_flag::chunked}); + parsegrind( + "HTTP/1.0 200 OK\r\n" + "Transfer-Encoding: gzip, chunked\r\n" + "\r\n0\r\n\r\n", expect_flags{*this, parse_flag::chunked}); // a message must not carry both Transfer-Encoding and Content-Length, // regardless of the transfer coding or the order the fields arrive in - failgrind>( + failgrind( "POST / HTTP/1.1\r\n" "Content-Length: 5\r\n" "Transfer-Encoding: gzip\r\n" "\r\n", error::bad_transfer_encoding); - failgrind>( + failgrind( "POST / HTTP/1.1\r\n" "Transfer-Encoding: gzip\r\n" "Content-Length: 5\r\n" "\r\n", error::bad_content_length); + failgrind( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "Transfer-Encoding: gzip\r\n" + "\r\n", error::bad_transfer_encoding); + failgrind( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: gzip\r\n" + "Content-Length: 5\r\n" + "\r\n", error::bad_content_length); } void @@ -1052,6 +1115,21 @@ class basic_parser_test : public beast::unit_test::suite "hello", expect_body(*this, "hello")); + // a response whose Transfer-Encoding does not end in chunked is + // delimited by eof (RFC 7230 3.3.3) + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: gzip\r\n" + "\r\n" + "hello", + expect_body(*this, "hello")); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked, gzip\r\n" + "\r\n" + "hello", + expect_body(*this, "hello")); + parsegrind>(buffers_cat( buf("GET / HTTP/1.1\r\n" "Content-Length: 10\r\n" From cdc2c5a92e734b36f0a1e1bb9078b68af9ee2426 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sun, 21 Jun 2026 11:36:45 +0000 Subject: [PATCH 3/3] fix unreachable code warning in async_base tests --- test/beast/core/async_base.cpp | 6 ++++-- test/beast/core/saved_handler.cpp | 14 ++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/test/beast/core/async_base.cpp b/test/beast/core/async_base.cpp index ee60efb0e2..7729eb48eb 100644 --- a/test/beast/core/async_base.cpp +++ b/test/beast/core/async_base.cpp @@ -644,10 +644,12 @@ class async_base_test : public beast::unit_test::suite { struct throwing_data { + volatile bool always = true; + throwing_data() { - BOOST_THROW_EXCEPTION( - std::exception{}); + if(always) + BOOST_THROW_EXCEPTION(std::exception{}); } }; stable_async_base< diff --git a/test/beast/core/saved_handler.cpp b/test/beast/core/saved_handler.cpp index 0a1aa02c2a..23fa5efd7b 100644 --- a/test/beast/core/saved_handler.cpp +++ b/test/beast/core/saved_handler.cpp @@ -9,11 +9,6 @@ #include -#ifdef BOOST_MSVC -#pragma warning(push) -#pragma warning(disable: 4702) // unreachable code -#endif - // Test that header file is self-contained. #include #include @@ -88,11 +83,14 @@ class saved_handler_test : public unit_test::suite struct throwing_handler { + volatile bool always = true; + throwing_handler() = default; throwing_handler(throwing_handler&&) { - BOOST_THROW_EXCEPTION(std::exception{}); + if(always) + BOOST_THROW_EXCEPTION(std::exception{}); } void @@ -291,7 +289,3 @@ BEAST_DEFINE_TESTSUITE(beast,core,saved_handler); } // beast } // boost - -#ifdef BOOST_MSVC -#pragma warning(pop) -#endif