diff --git a/include/boost/beast/http/basic_parser.hpp b/include/boost/beast/http/basic_parser.hpp index d2eda05ff4..fbc44273e8 100644 --- a/include/boost/beast/http/basic_parser.hpp +++ b/include/boost/beast/http/basic_parser.hpp @@ -78,7 +78,6 @@ class basic_parser std::uint64_t len0_ = 0; // content length if known std::unique_ptr buf_; // temp storage std::size_t buf_len_ = 0; // size of buf_ - std::size_t skip_ = 0; // resume search here std::uint32_t header_limit_ = 8192; // max header size unsigned short status_ = 0; // response status state state_ = state::nothing_yet; // initial state @@ -110,7 +109,6 @@ class basic_parser static unsigned constexpr flagContentLength = 1<< 10; static unsigned constexpr flagChunked = 1<< 11; static unsigned constexpr flagUpgrade = 1<< 12; - static unsigned constexpr flagFinalChunk = 1<< 13; static constexpr std::uint64_t @@ -642,23 +640,28 @@ class basic_parser error_code& ec); void - maybe_need_more( - char const* p, std::size_t n, - error_code& ec); + inner_parse_start_line( + char const*& p, char const* last, + error_code& ec, std::true_type); void - parse_start_line( + inner_parse_start_line( char const*& p, char const* last, - error_code& ec, std::true_type); + error_code& ec, std::false_type); void parse_start_line( + char const*& p, std::size_t n, + error_code& ec); + + void + inner_parse_fields( char const*& p, char const* last, - error_code& ec, std::false_type); + error_code& ec); void parse_fields( - char const*& p, char const* last, + char const*& p, std::size_t n, error_code& ec); void diff --git a/include/boost/beast/http/detail/basic_parser.hpp b/include/boost/beast/http/detail/basic_parser.hpp index 196d1c0a28..5fa25333f8 100644 --- a/include/boost/beast/http/detail/basic_parser.hpp +++ b/include/boost/beast/http/detail/basic_parser.hpp @@ -44,6 +44,7 @@ struct basic_parser_base chunk_header0, chunk_header, chunk_body, + trailer_fields, complete }; @@ -108,11 +109,6 @@ struct basic_parser_base char const* it, char const* last, error_code& ec); - BOOST_BEAST_DECL - static - char const* - find_eom(char const* p, char const* last); - //-------------------------------------------------------------------------- BOOST_BEAST_DECL diff --git a/include/boost/beast/http/detail/basic_parser.ipp b/include/boost/beast/http/detail/basic_parser.ipp index 31d7cd4048..117fd36a38 100644 --- a/include/boost/beast/http/detail/basic_parser.ipp +++ b/include/boost/beast/http/detail/basic_parser.ipp @@ -201,40 +201,6 @@ parse_hex(char const*& it, std::uint64_t& v) return true; } -char const* -basic_parser_base:: -find_eom(char const* p, char const* last) -{ - for(;;) - { - if(p + 4 > last) - return nullptr; - if(p[3] != '\n') - { - if(p[3] == '\r') - ++p; - else - p += 4; - } - else if(p[2] != '\r') - { - p += 4; - } - else if(p[1] != '\n') - { - p += 2; - } - else if(p[0] != '\r') - { - p += 2; - } - else - { - return p + 4; - } - } -} - //-------------------------------------------------------------------------- char const* diff --git a/include/boost/beast/http/error.hpp b/include/boost/beast/http/error.hpp index 079c6b7533..3018bc895e 100644 --- a/include/boost/beast/http/error.hpp +++ b/include/boost/beast/http/error.hpp @@ -164,7 +164,13 @@ enum class error unexpected end-of-file condition is encountered while trying to read from the file. */ - short_read + short_read, + + /// Header field name exceeds @ref basic_fields::max_name_size. + header_field_name_too_large, + + /// Header field value exceeds @ref basic_fields::max_value_size. + header_field_value_too_large }; } // http diff --git a/include/boost/beast/http/fields.hpp b/include/boost/beast/http/fields.hpp index 4203265f15..787396de78 100644 --- a/include/boost/beast/http/fields.hpp +++ b/include/boost/beast/http/fields.hpp @@ -10,9 +10,10 @@ #ifndef BOOST_BEAST_HTTP_FIELDS_HPP #define BOOST_BEAST_HTTP_FIELDS_HPP +#include #include +#include #include -#include #include #include #include @@ -218,6 +219,14 @@ class basic_fields public: + /// Maximum field name size + static std::size_t constexpr max_name_size = + (std::numeric_limits::max)() - 2; + + /// Maximum field value size + static std::size_t constexpr max_value_size = + (std::numeric_limits::max)() - 2; + /// Destructor ~basic_fields(); @@ -434,13 +443,15 @@ class basic_fields @param name The field name. - @param value The value of the field, as a @ref boost::beast::string_view + @param value The field value. + + @throws boost::system::system_error Thrown if an error occurs: + @li If the size of @c value exceeds @ref max_value_size, the + error code will be @ref error::header_field_value_too_large. */ void - insert(field name, string_view const& value); + insert(field name, string_view value); - /* Set a field from a null pointer (deleted). - */ void insert(field, std::nullptr_t) = delete; @@ -453,13 +464,17 @@ class basic_fields @param name The field name. It is interpreted as a case-insensitive string. - @param value The value of the field, as a @ref boost::beast::string_view + @param value The field value. + + @throws boost::system::system_error Thrown if an error occurs: + @li If the size of @c name exceeds @ref max_name_size, the + error code will be @ref error::header_field_name_too_large. + @li If the size of @c value exceeds @ref max_value_size, the + error code will be @ref error::header_field_value_too_large. */ void - insert(string_view name, string_view const& value); + insert(string_view name, string_view value); - /* Insert a field from a null pointer (deleted). - */ void insert(string_view, std::nullptr_t) = delete; @@ -477,15 +492,50 @@ class basic_fields must be equal to `to_string(name)` using a case-insensitive comparison, otherwise the behavior is undefined. - @param value The value of the field, as a @ref boost::beast::string_view + @param value The field value. + + @throws boost::system::system_error Thrown if an error occurs: + @li If the size of @c name_string exceeds @ref max_name_size, + the error code will be @ref error::header_field_name_too_large. + @li If the size of @c value exceeds @ref max_value_size, the + error code will be @ref error::header_field_value_too_large. */ void insert(field name, string_view name_string, - string_view const& value); + string_view value); void insert(field, string_view, std::nullptr_t) = delete; + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + The value can be an empty string. + + @param name The field name. + + @param name_string The literal text corresponding to the + field name. If `name != field::unknown`, then this value + must be equal to `to_string(name)` using a case-insensitive + comparison, otherwise the behavior is undefined. + + @param value The field value. + + @param ec Set to indicate what error occurred: + @li If the size of @c name_string exceeds @ref max_name_size, + the error code will be @ref error::header_field_name_too_large. + @li If the size of @c value exceeds @ref max_value_size, the + error code will be @ref error::header_field_value_too_large. + */ + void + insert(field name, string_view name_string, + string_view value, error_code& ec); + + void + insert(field, string_view, std::nullptr_t, error_code& ec) = delete; + /** Set a field value, removing any other instances of that field. First removes any values with matching field names, then @@ -493,13 +543,14 @@ class basic_fields @param name The field name. - @param value The value of the field, as a @ref boost::beast::string_view - - @return The field value. + @param value The field value. + @throws boost::system::system_error Thrown if an error occurs: + @li If the size of @c value exceeds @ref max_value_size, the + error code will be @ref error::header_field_value_too_large. */ void - set(field name, string_view const& value); + set(field name, string_view value); void set(field, std::nullptr_t) = delete; @@ -511,15 +562,21 @@ class basic_fields @param name The field name. It is interpreted as a case-insensitive string. - @param value The value of the field, as a @ref boost::beast::string_view + @param value The field value. + + @throws boost::system::system_error Thrown if an error occurs: + @li If the size of @c name exceeds @ref max_name_size, the + error code will be @ref error::header_field_name_too_large. + @li If the size of @c value exceeds @ref max_value_size, the + error code will be @ref error::header_field_value_too_large. */ void - set(string_view name, string_view const& value); + set(string_view name, string_view value); void set(string_view, std::nullptr_t) = delete; - /** Remove a field. + /** Remove a field. References and iterators to the erased elements are invalidated. Other references and iterators are not @@ -744,9 +801,21 @@ class basic_fields template friend class basic_fields; + element* + try_create_new_element( + field name, + string_view sname, + string_view value, + error_code& ec); + element& - new_element(field name, - string_view sname, string_view value); + new_element( + field name, + string_view sname, + string_view value); + + void + insert_element(element& e); void delete_element(element& e); diff --git a/include/boost/beast/http/impl/basic_parser.ipp b/include/boost/beast/http/impl/basic_parser.ipp index 9fd6f96f76..d2c28d1beb 100644 --- a/include/boost/beast/http/impl/basic_parser.ipp +++ b/include/boost/beast/http/impl/basic_parser.ipp @@ -111,65 +111,23 @@ loop: BOOST_FALLTHROUGH; case state::start_line: - { - maybe_need_more(p, n, ec); + parse_start_line(p, n, ec); if(ec) goto done; - parse_start_line(p, p + (std::min)( - header_limit_, n), ec, is_request{}); - if(ec) - { - if(ec == error::need_more) - { - if(n >= header_limit_) - { - BOOST_BEAST_ASSIGN_EC(ec, error::header_limit); - goto done; - } - if(p + 3 <= p1) - skip_ = static_cast< - std::size_t>(p1 - p - 3); - } - goto done; - } BOOST_ASSERT(! is_done()); n = static_cast(p1 - p); - if(p >= p1) - { - BOOST_BEAST_ASSIGN_EC(ec, error::need_more); - goto done; - } BOOST_FALLTHROUGH; - } case state::fields: - maybe_need_more(p, n, ec); - if(ec) - goto done; - parse_fields(p, p + (std::min)( - header_limit_, n), ec); + parse_fields(p, n, ec); if(ec) - { - if(ec == error::need_more) - { - if(n >= header_limit_) - { - BOOST_BEAST_ASSIGN_EC(ec, error::header_limit); - goto done; - } - if(p + 3 <= p1) - skip_ = static_cast< - std::size_t>(p1 - p - 3); - } goto done; - } finish_header(ec, is_request{}); if(ec) goto done; break; case state::body0: - BOOST_ASSERT(! skip_); this->on_body_init_impl(content_length(), ec); if(ec) goto done; @@ -177,14 +135,12 @@ loop: BOOST_FALLTHROUGH; case state::body: - BOOST_ASSERT(! skip_); parse_body(p, n, ec); if(ec) goto done; break; case state::body_to_eof0: - BOOST_ASSERT(! skip_); this->on_body_init_impl(content_length(), ec); if(ec) goto done; @@ -192,7 +148,6 @@ loop: BOOST_FALLTHROUGH; case state::body_to_eof: - BOOST_ASSERT(! skip_); parse_body_to_eof(p, n, ec); if(ec) goto done; @@ -209,7 +164,18 @@ loop: parse_chunk_header(p, n, ec); if(ec) goto done; - break; + if(state_ != state::trailer_fields) + break; + n = static_cast(p1 - p); + BOOST_FALLTHROUGH; + + case state::trailer_fields: + parse_fields(p, n, ec); + if(ec) + goto done; + state_ = state::complete; + this->on_finish_impl(ec); + goto done; case state::chunk_body: parse_chunk_body(p, n, ec); @@ -252,49 +218,15 @@ put_eof(error_code& ec) ec = {}; return; } + state_ = state::complete; ec = {}; this->on_finish_impl(ec); - if(ec) - return; - state_ = state::complete; -} - -template -void -basic_parser:: -maybe_need_more( - char const* p, std::size_t n, - error_code& ec) -{ - if(skip_ == 0) - return; - if( n > header_limit_) - n = header_limit_; - if(n < skip_ + 4) - { - BOOST_BEAST_ASSIGN_EC(ec, error::need_more); - return; - } - auto const term = - find_eom(p + skip_, p + n); - if(! term) - { - skip_ = n - 3; - if(skip_ + 4 > header_limit_) - { - BOOST_BEAST_ASSIGN_EC(ec, error::header_limit); - return; - } - BOOST_BEAST_ASSIGN_EC(ec, error::need_more); - return; - } - skip_ = 0; } template void basic_parser:: -parse_start_line( +inner_parse_start_line( char const*& in, char const* last, error_code& ec, std::true_type) { @@ -351,7 +283,7 @@ parse_start_line( template void basic_parser:: -parse_start_line( +inner_parse_start_line( char const*& in, char const* last, error_code& ec, std::false_type) { @@ -409,7 +341,24 @@ parse_start_line( template void basic_parser:: -parse_fields(char const*& in, +parse_start_line( + char const*& in, std::size_t n, error_code& ec) +{ + auto const p0 = in; + + inner_parse_start_line(in, in + (std::min) + (n, header_limit_), ec, is_request{}); + if(ec == error::need_more && n >= header_limit_) + { + BOOST_BEAST_ASSIGN_EC(ec, error::header_limit); + } + header_limit_ -= static_cast(in - p0); +} + +template +void +basic_parser:: +inner_parse_fields(char const*& in, char const* last, error_code& ec) { string_view name; @@ -447,6 +396,22 @@ parse_fields(char const*& in, } } +template +void +basic_parser:: +parse_fields(char const*& in, std::size_t n, error_code& ec) +{ + auto const p0 = in; + + inner_parse_fields(in, in + (std::min) + (n, header_limit_), ec); + if(ec == error::need_more && n >= header_limit_) + { + BOOST_BEAST_ASSIGN_EC(ec, error::header_limit); + } + header_limit_ -= static_cast(in - p0); +} + template void basic_parser:: @@ -494,11 +459,7 @@ finish_header(error_code& ec, std::true_type) if(ec) return; if(state_ == state::complete) - { this->on_finish_impl(ec); - if(ec) - return; - } } template @@ -555,11 +516,7 @@ finish_header(error_code& ec, std::false_type) if(ec) return; if(state_ == state::complete) - { this->on_finish_impl(ec); - if(ec) - return; - } } template @@ -577,10 +534,8 @@ parse_body(char const*& p, return; if(len_ > 0) return; - this->on_finish_impl(ec); - if(ec) - return; state_ = state::complete; + this->on_finish_impl(ec); } template @@ -608,7 +563,7 @@ parse_body_to_eof(char const*& p, template void basic_parser:: -parse_chunk_header(char const*& p0, +parse_chunk_header(char const*& in, std::size_t n, error_code& ec) { /* @@ -625,102 +580,50 @@ parse_chunk_header(char const*& p0, chunk-ext-val = token / quoted-string */ - auto p = p0; + auto p = in; auto const pend = p + n; - char const* eol; - if(! (f_ & flagFinalChunk)) + if(n < 2) { - if(n < skip_ + 2) - { - BOOST_BEAST_ASSIGN_EC(ec, error::need_more); - return; - } - if(f_ & flagExpectCRLF) - { - // Treat the last CRLF in a chunk as - // part of the next chunk, so p can - // be parsed in one call instead of two. - if(! parse_crlf(p)) - { - BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk); - return; - } - } - eol = find_eol(p0 + skip_, pend, ec); - if(ec) - return; - if(! eol) - { - BOOST_BEAST_ASSIGN_EC(ec, error::need_more); - skip_ = n - 1; - return; - } - skip_ = static_cast< - std::size_t>(eol - 2 - p0); - - std::uint64_t size; - if(! parse_hex(p, size)) + BOOST_BEAST_ASSIGN_EC(ec, error::need_more); + return; + } + if(f_ & flagExpectCRLF) + { + // Treat the last CRLF in a chunk as + // part of the next chunk, so p can + // be parsed in one call instead of two. + if(! parse_crlf(p)) { BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk); return; } - if(size != 0) - { - if (body_limit_.has_value()) - { - if (size > *body_limit_) - { - BOOST_BEAST_ASSIGN_EC(ec, error::body_limit); - return; - } - *body_limit_ -= size; - } - auto const start = p; - parse_chunk_extensions(p, pend, ec); - if(ec) - return; - if(p != eol -2 ) - { - BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk_extension); - return; - } - auto const ext = make_string(start, p); - this->on_chunk_header_impl(size, ext, ec); - if(ec) - return; - len_ = size; - skip_ = 2; - p0 = eol; - f_ |= flagExpectCRLF; - state_ = state::chunk_body; - return; - } - - f_ |= flagFinalChunk; } - else + + auto const eol = find_eol(p, pend, ec); + if(ec) + return; + if(! eol) { - BOOST_ASSERT(n >= 3); - if(f_ & flagExpectCRLF) - { - BOOST_ASSERT(n >= 5); - BOOST_VERIFY(parse_crlf(p)); - } - std::uint64_t size; - BOOST_VERIFY(parse_hex(p, size)); - eol = find_eol(p, pend, ec); - BOOST_ASSERT(! ec); + BOOST_BEAST_ASSIGN_EC(ec, error::need_more); + return; } - auto eom = find_eom(p0 + skip_, pend); - if(! eom) + std::uint64_t size; + if(! parse_hex(p, size)) { - BOOST_ASSERT(n >= 3); - skip_ = n - 3; - BOOST_BEAST_ASSIGN_EC(ec, error::need_more); + BOOST_BEAST_ASSIGN_EC(ec, error::bad_chunk); return; } + if (body_limit_.has_value()) + { + if (size > *body_limit_) + { + BOOST_BEAST_ASSIGN_EC(ec, error::body_limit); + return; + } + *body_limit_ -= size; + } auto const start = p; parse_chunk_extensions(p, pend, ec); @@ -732,20 +635,20 @@ parse_chunk_header(char const*& p0, return; } auto const ext = make_string(start, p); - this->on_chunk_header_impl(0, ext, ec); - if(ec) - return; - p = eol; - parse_fields(p, eom, ec); + this->on_chunk_header_impl(size, ext, ec); if(ec) return; - BOOST_ASSERT(p == eom); - p0 = eom; - this->on_finish_impl(ec); - if(ec) + len_ = size; + in = eol; + f_ |= flagExpectCRLF; + if(size != 0) + { + state_ = state::chunk_body; return; - state_ = state::complete; + } + state_ = state::trailer_fields; + header_limit_ += 2; // for the final chunk's CRLF } template diff --git a/include/boost/beast/http/impl/error.ipp b/include/boost/beast/http/impl/error.ipp index 2e7c7ba4ff..e3721f7cf3 100644 --- a/include/boost/beast/http/impl/error.ipp +++ b/include/boost/beast/http/impl/error.ipp @@ -61,6 +61,8 @@ public: case error::multiple_content_length: return "multiple Content-Length"; case error::stale_parser: return "stale parser"; case error::short_read: return "unexpected eof in body"; + case error::header_field_name_too_large: return "header field name too large"; + case error::header_field_value_too_large: return "header field value too large"; default: return "beast.http error"; diff --git a/include/boost/beast/http/impl/fields.hpp b/include/boost/beast/http/impl/fields.hpp index 48b6ef0cff..bd3e74a90f 100644 --- a/include/boost/beast/http/impl/fields.hpp +++ b/include/boost/beast/http/impl/fields.hpp @@ -537,7 +537,7 @@ template inline void basic_fields:: -insert(field name, string_view const& value) +insert(field name, string_view value) { BOOST_ASSERT(name != field::unknown); insert(name, to_string(name), value); @@ -546,58 +546,52 @@ insert(field name, string_view const& value) template void basic_fields:: -insert(string_view sname, string_view const& value) +insert(string_view sname, string_view value) { - auto const name = - string_to_field(sname); - insert(name, sname, value); + insert( + string_to_field(sname), sname, value); } template void basic_fields:: -insert(field name, - string_view sname, string_view const& value) +insert( + field name, + string_view sname, + string_view value, + error_code& ec) { - auto& e = new_element(name, sname, - static_cast(value)); - auto const before = - set_.upper_bound(sname, key_compare{}); - if(before == set_.begin()) - { - BOOST_ASSERT(count(sname) == 0); - set_.insert_before(before, e); - list_.push_back(e); - return; - } - auto const last = std::prev(before); - // VFALCO is it worth comparing `field name` first? - if(! beast::iequals(sname, last->name_string())) - { - BOOST_ASSERT(count(sname) == 0); - set_.insert_before(before, e); - list_.push_back(e); + ec = {}; + auto* e = try_create_new_element(name, sname, value, ec); + if(ec.failed()) return; - } - // keep duplicate fields together in the list - set_.insert_before(before, e); - list_.insert(++list_.iterator_to(*last), e); + insert_element(*e); } template void basic_fields:: -set(field name, string_view const& value) +insert(field name, + string_view sname, string_view value) +{ + insert_element( + new_element(name, sname, value)); +} + +template +void +basic_fields:: +set(field name, string_view value) { BOOST_ASSERT(name != field::unknown); - set_element(new_element(name, to_string(name), - static_cast(value))); + set_element( + new_element(name, to_string(name), value)); } template void basic_fields:: -set(string_view sname, string_view const& value) +set(string_view sname, string_view value) { set_element(new_element( string_to_field(sname), sname, value)); @@ -953,18 +947,22 @@ set_keep_alive_impl( template auto basic_fields:: -new_element(field name, - string_view sname, string_view value) -> - element& -{ - if(sname.size() + 2 > - (std::numeric_limits::max)()) - BOOST_THROW_EXCEPTION(std::length_error{ - "field name too large"}); - if(value.size() + 2 > - (std::numeric_limits::max)()) - BOOST_THROW_EXCEPTION(std::length_error{ - "field value too large"}); +try_create_new_element( + field name, + string_view sname, + string_view value, + error_code& ec) -> element* +{ + if(sname.size() > max_name_size) + { + BOOST_BEAST_ASSIGN_EC(ec, error::header_field_name_too_large); + return nullptr; + } + if(value.size() > max_value_size) + { + BOOST_BEAST_ASSIGN_EC(ec, error::header_field_value_too_large); + return nullptr; + } value = detail::trim(value); std::uint16_t const off = static_cast(sname.size() + 2); @@ -974,7 +972,50 @@ new_element(field name, auto const p = alloc_traits::allocate(a, (sizeof(element) + off + len + 2 + sizeof(align_type) - 1) / sizeof(align_type)); - return *(::new(p) element(name, sname, value)); + return ::new(p) element(name, sname, value); +} + +template +auto +basic_fields:: +new_element( + field name, + string_view sname, + string_view value) -> element& +{ + error_code ec; + auto* e = try_create_new_element(name, sname, value, ec); + if(ec.failed()) + BOOST_THROW_EXCEPTION(system_error{ec}); + return *e; +} + +template +void +basic_fields:: +insert_element(element& e) +{ + auto const before = + set_.upper_bound(e.name_string(), key_compare{}); + if(before == set_.begin()) + { + BOOST_ASSERT(count(e.name_string()) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + auto const last = std::prev(before); + // VFALCO is it worth comparing `field name` first? + if(! beast::iequals(e.name_string(), last->name_string())) + { + BOOST_ASSERT(count(e.name_string()) == 0); + set_.insert_before(before, e); + list_.push_back(e); + return; + } + // keep duplicate fields together in the list + set_.insert_before(before, e); + list_.insert(++list_.iterator_to(*last), e); } template diff --git a/include/boost/beast/http/parser.hpp b/include/boost/beast/http/parser.hpp index 4851fe8f31..a5b93d17d4 100644 --- a/include/boost/beast/http/parser.hpp +++ b/include/boost/beast/http/parser.hpp @@ -430,9 +430,9 @@ class parser field name, string_view name_string, string_view value, - error_code&) override + error_code& ec) override { - m_.insert(name, name_string, value); + m_.insert(name, name_string, value, ec); } void diff --git a/test/beast/http/basic_parser.cpp b/test/beast/http/basic_parser.cpp index 0a21d8931e..60c5ddd972 100644 --- a/test/beast/http/basic_parser.cpp +++ b/test/beast/http/basic_parser.cpp @@ -363,6 +363,18 @@ class basic_parser_test : public beast::unit_test::suite void testCallbacks() { + parsegrind>( + "GET / HTTP/1.1\r\n" + "\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 0); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 0); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); parsegrind>( "GET / HTTP/1.1\r\n" "User-Agent: test\r\n" @@ -378,6 +390,18 @@ class basic_parser_test : public beast::unit_test::suite BEAST_EXPECT(p.got_on_chunk == 0); BEAST_EXPECT(p.got_on_complete == 1); }); + parsegrind>( + "HTTP/1.1 100 Continue\r\n" + "\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 0); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 0); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); parsegrind>( "HTTP/1.1 200 OK\r\n" "Server: test\r\n" @@ -843,6 +867,42 @@ class basic_parser_test : public beast::unit_test::suite p.put(b.data(), ec); BEAST_EXPECTS(ec == error::header_limit, ec.message()); } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n"; + error_code ec; + test_parser p; + p.header_limit(18); + p.eager(true); + b.consume(p.put(b.data(), ec)); + BEAST_EXPECTS(ec == error::need_more, ec.message()); + ostream(b) << + "field: value\r\n"; + b.consume(p.put(b.data(), ec)); + BEAST_EXPECT(! p.is_done()); + BEAST_EXPECTS(ec == error::header_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "0\r\n"; + error_code ec; + test_parser p; + p.header_limit(47); + p.eager(true); + b.consume(p.put(b.data(), ec)); + BEAST_EXPECTS(ec == error::need_more, ec.message()); + ostream(b) << + "field: value\r\n" + "\r\n"; + b.consume(p.put(b.data(), ec)); + BEAST_EXPECT(! p.is_done()); + BEAST_EXPECTS(ec == error::header_limit, ec.message()); + } { multi_buffer b; ostream(b) << diff --git a/test/beast/http/error.cpp b/test/beast/http/error.cpp index 24dddbbbe3..97084978ed 100644 --- a/test/beast/http/error.cpp +++ b/test/beast/http/error.cpp @@ -72,6 +72,9 @@ class error_test : public unit_test::suite check("beast.http", error::stale_parser); check("beast.http", error::short_read); + + check("beast.http", error::header_field_name_too_large); + check("beast.http", error::header_field_value_too_large); } }; diff --git a/test/beast/http/fields.cpp b/test/beast/http/fields.cpp index e555f8228a..98d6d68e30 100644 --- a/test/beast/http/fields.cpp +++ b/test/beast/http/fields.cpp @@ -445,6 +445,33 @@ class fields_test : public beast::unit_test::suite BEAST_EXPECT(std::next(rng.first, 1)->value() == "4"); BEAST_EXPECT(std::next(rng.first, 2)->value() == "6"); } + + // max field name and max field value + { + fields f; + error_code ec; + auto fit_name = std::string(fields::max_name_size, 'a'); + auto big_name = std::string(fields::max_name_size + 1, 'a'); + auto fit_value = std::string(fields::max_value_size, 'a'); + auto big_value = std::string(fields::max_value_size + 1, 'a'); + + f.insert(fit_name, fit_value); + f.set(fit_name, fit_value); + + f.insert(field::age, big_name, "", ec); + BEAST_EXPECT(ec == error::header_field_name_too_large); + f.insert(field::age, "", big_value, ec); + BEAST_EXPECT(ec == error::header_field_value_too_large); + + BEAST_THROWS(f.insert(field::age, big_value), boost::system::system_error); + BEAST_THROWS(f.insert(field::age, big_name, ""), boost::system::system_error); + BEAST_THROWS(f.insert(field::age, "", big_value), boost::system::system_error); + BEAST_THROWS(f.insert(big_name, ""), boost::system::system_error); + BEAST_THROWS(f.insert("", big_value), boost::system::system_error); + BEAST_THROWS(f.set(field::age, big_value), boost::system::system_error); + BEAST_THROWS(f.set(big_name, ""), boost::system::system_error); + BEAST_THROWS(f.set("", big_value), boost::system::system_error); + } } struct sized_body diff --git a/test/beast/http/parser.cpp b/test/beast/http/parser.cpp index e6829e2d2f..af7c7cd98d 100644 --- a/test/beast/http/parser.cpp +++ b/test/beast/http/parser.cpp @@ -326,6 +326,40 @@ class parser_test BEAST_EXPECT(used == 0); } + void + testHeaderFieldLimits() + { + auto big_field_name = std::string(fields::max_name_size + 1, 'a'); + auto big_field_value = std::string(fields::max_value_size + 1, 'a'); + + { + parser_type p; + p.header_limit((std::numeric_limits::max)()); + error_code ec; + flat_buffer b; + ostream(b) << + "HTTP/1.1 200 OK\r\n" + << big_field_name + <<": value\r\n" + "\r\n"; + put(b.data(), p, ec); + BEAST_EXPECT(ec == error::header_field_name_too_large); + } + { + parser_type p; + p.header_limit((std::numeric_limits::max)()); + error_code ec; + flat_buffer b; + ostream(b) << + "HTTP/1.1 200 OK\r\n" + << "name: " + << big_field_value << "\r\n" + << "\r\n"; + put(b.data(), p, ec); + BEAST_EXPECT(ec == error::header_field_value_too_large); + } + } + void testIssue818() { @@ -436,7 +470,8 @@ class parser_test ostream(b) << "0\r\n"; // needs an extra CRLF used = p.put(b.data(), ec); - BEAST_EXPECT(used == 0); + BEAST_EXPECT(used == 3); + b.consume(used); BEAST_EXPECT(ec == error::need_more); ostream(b) << "\r"; @@ -446,7 +481,7 @@ class parser_test ostream(b) << "\n"; used = p.put(b.data(), ec); - BEAST_EXPECT(used == 5); + BEAST_EXPECT(used == 2); BEAST_EXPECT(!ec); BEAST_EXPECT(p.is_done()); } @@ -457,6 +492,7 @@ class parser_test testParse(); testNeedMore(); testNeedMore(); + testHeaderFieldLimits(); testGotSome(); testIssue818(); testIssue1187();