diff --git a/include/boost/beast/http/error.hpp b/include/boost/beast/http/error.hpp index 079c6b75..3018bc89 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 4203265f..787396de 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 #include @@ -218,6 +219,14 @@ private: 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 @@ public: @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 @@ public: @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 @@ public: 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 @@ public: @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 @@ public: @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 @@ private: 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/error.ipp b/include/boost/beast/http/impl/error.ipp index 2e7c7ba4..e3721f7c 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 48b6ef0c..bd3e74a9 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 value, + error_code& ec) +{ + ec = {}; + auto* e = try_create_new_element(name, sname, value, ec); + if(ec.failed()) + return; + insert_element(*e); } template void basic_fields:: insert(field name, - string_view sname, string_view const& value) + string_view sname, string_view value) { - 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); - return; - } - // keep duplicate fields together in the list - set_.insert_before(before, e); - list_.insert(++list_.iterator_to(*last), e); + insert_element( + new_element(name, sname, value)); } template void basic_fields:: -set(field name, string_view const& value) +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& +try_create_new_element( + field name, + string_view sname, + string_view value, + error_code& ec) -> 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"}); + 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/test/beast/http/error.cpp b/test/beast/http/error.cpp index 24dddbbb..97084978 100644 --- a/test/beast/http/error.cpp +++ b/test/beast/http/error.cpp @@ -72,6 +72,9 @@ public: 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 e555f822..98d6d68e 100644 --- a/test/beast/http/fields.cpp +++ b/test/beast/http/fields.cpp @@ -445,6 +445,33 @@ public: 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