Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ concurrency:

env:
UBSAN_OPTIONS: print_stacktrace=1
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true

jobs:
runner-selection:
Expand Down
9 changes: 9 additions & 0 deletions include/boost/beast/http/impl/basic_parser.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions test/beast/core/async_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
14 changes: 4 additions & 10 deletions test/beast/core/saved_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable: 4702) // unreachable code
#endif

// Test that header file is self-contained.
#include <boost/beast/core/saved_handler.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -291,7 +289,3 @@ BEAST_DEFINE_TESTSUITE(beast,core,saved_handler);

} // beast
} // boost

#ifdef BOOST_MSVC
#pragma warning(pop)
#endif
168 changes: 123 additions & 45 deletions test/beast/http/basic_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<true>;

parsegrind<P>(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<P>(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<true>;
using Pres = test_parser<false>;

parsegrind<Preq>(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked});
parsegrind<Preq>(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<P>(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked});

parsegrind<P>(te("gzip\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("chunked, gzip\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("bigchunked\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("chunk\r\n ked\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0});
parsegrind<P>(te("barley\r\n chunked\r\n"), expect_flags{*this, 0});

parsegrind<P>(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0});
parsegrind<P>(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0});
parsegrind<P>(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0});

failgrind<test_parser<false>>(
//parsegrind<Preq>(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked});

parsegrind<Pres>(te("gzip\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("chunked, gzip\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("bigchunked\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("chunk\r\n ked\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0});
parsegrind<Pres>(te("barley\r\n chunked\r\n"), expect_flags{*this, 0});

parsegrind<Preq>(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0});
parsegrind<Preq>(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0});
parsegrind<Preq>(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0});

failgrind<Pres>(
"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<Preq>(
"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<Preq>(
"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<Preq>(
"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<Preq>(
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: gzip\r\n"
"\r\n", error::bad_transfer_encoding);
failgrind<Preq>(
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: chunked, gzip\r\n"
"\r\n", error::bad_transfer_encoding);
parsegrind<Pres>(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: gzip\r\n"
"\r\n", expect_flags{*this, 0});
parsegrind<Pres>(
"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<test_parser<true>>(
failgrind<Preq>(
"GET / HTTP/1.0\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n0\r\n\r\n", error::bad_transfer_encoding);
failgrind<test_parser<true>>(
failgrind<Preq>(
"GET / HTTP/1.0\r\n"
"Transfer-Encoding: gzip, chunked\r\n"
"\r\n0\r\n\r\n", error::bad_transfer_encoding);
parsegrind<Pres>(
"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<Pres>(
"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<test_parser<true>>(
failgrind<Preq>(
"POST / HTTP/1.1\r\n"
"Content-Length: 5\r\n"
"Transfer-Encoding: gzip\r\n"
"\r\n", error::bad_transfer_encoding);
failgrind<test_parser<true>>(
failgrind<Preq>(
"POST / HTTP/1.1\r\n"
"Transfer-Encoding: gzip\r\n"
"Content-Length: 5\r\n"
"\r\n", error::bad_content_length);
failgrind<Pres>(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 5\r\n"
"Transfer-Encoding: gzip\r\n"
"\r\n", error::bad_transfer_encoding);
failgrind<Pres>(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: gzip\r\n"
"Content-Length: 5\r\n"
"\r\n", error::bad_content_length);
}

void
Expand Down Expand Up @@ -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<test_parser<false>>(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: gzip\r\n"
"\r\n"
"hello",
expect_body(*this, "hello"));
parsegrind<test_parser<false>>(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked, gzip\r\n"
"\r\n"
"hello",
expect_body(*this, "hello"));

parsegrind<test_parser<true>>(buffers_cat(
buf("GET / HTTP/1.1\r\n"
"Content-Length: 10\r\n"
Expand Down
Loading