Skip to content

Commit

Permalink
Add overload for basic_fields::insert() to set error_code
Browse files Browse the repository at this point in the history
  • Loading branch information
ashtum committed Aug 17, 2024
1 parent 848e206 commit 23ed47c
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 52 deletions.
8 changes: 7 additions & 1 deletion include/boost/beast/http/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 77 additions & 10 deletions include/boost/beast/http/fields.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
#ifndef BOOST_BEAST_HTTP_FIELDS_HPP
#define BOOST_BEAST_HTTP_FIELDS_HPP

#include <boost/beast/core/detail/allocator.hpp>
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/detail/allocator.hpp>
#include <boost/beast/http/field.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/core/empty_value.hpp>
Expand Down Expand Up @@ -218,6 +219,14 @@ class basic_fields


public:
/// Maximum field name size
static std::size_t constexpr max_name_size =
(std::numeric_limits<std::uint16_t>::max)() - 2;

/// Maximum field value size
static std::size_t constexpr max_value_size =
(std::numeric_limits<std::uint16_t>::max)() - 2;

/// Destructor
~basic_fields();

Expand Down Expand Up @@ -435,12 +444,14 @@ class basic_fields
@param name The field name.
@param value The value of the field, as a @ref boost::beast::string_view
@throws boost::system::system_error Thrown if an error occurs:
- 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);

/* Set a field from a null pointer (deleted).
*/
void
insert(field, std::nullptr_t) = delete;

Expand All @@ -454,12 +465,14 @@ 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
@throws boost::system::system_error Thrown if an error occurs:
- 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 a field from a null pointer (deleted).
*/
void
insert(string_view, std::nullptr_t) = delete;

Expand All @@ -478,6 +491,12 @@ class basic_fields
comparison, otherwise the behavior is undefined.
@param value The value of the field, as a @ref boost::beast::string_view
@throws boost::system::system_error Thrown if an error occurs:
- If the size of @c name_string exceeds @ref max_name_size, the
error code will be @ref error::header_field_name_too_large.
- 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,
Expand All @@ -486,6 +505,35 @@ class basic_fields
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 value of the field, as a @ref boost::beast::string_view
@param ec Set to indicate what error occurred, if any.
- If the size of @c name_string exceeds @ref max_name_size, the
error code will be @ref error::header_field_name_too_large.
- 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, 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
Expand All @@ -495,8 +543,9 @@ class basic_fields
@param value The value of the field, as a @ref boost::beast::string_view
@return The field value.
@throws boost::system::system_error Thrown if an error occurs:
- 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);
Expand All @@ -512,14 +561,20 @@ 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
@throws boost::system::system_error Thrown if an error occurs:
- If the size of @c name exceeds @ref max_name_size, the
error code will be @ref error::header_field_name_too_large.
- 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);

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
Expand Down Expand Up @@ -744,9 +799,21 @@ class basic_fields
template<class OtherAlloc>
friend class basic_fields;

element*
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);
Expand Down
2 changes: 2 additions & 0 deletions include/boost/beast/http/impl/error.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
123 changes: 82 additions & 41 deletions include/boost/beast/http/impl/fields.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,24 @@ void
basic_fields<Allocator>::
insert(string_view sname, string_view const& value)
{
auto const name =
string_to_field(sname);
insert(name, sname, value);
insert(
string_to_field(sname), sname, value);
}

template<class Allocator>
void
basic_fields<Allocator>::
insert(
field name,
string_view sname,
string_view const& value,
error_code& ec)
{
ec = {};
auto* e = new_element(name, sname, value, ec);
if(ec.failed())
return;
insert_element(*e);
}

template<class Allocator>
Expand All @@ -559,29 +574,8 @@ basic_fields<Allocator>::
insert(field name,
string_view sname, string_view const& value)
{
auto& e = new_element(name, sname,
static_cast<string_view>(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<class Allocator>
Expand All @@ -590,8 +584,8 @@ basic_fields<Allocator>::
set(field name, string_view const& value)
{
BOOST_ASSERT(name != field::unknown);
set_element(new_element(name, to_string(name),
static_cast<string_view>(value)));
set_element(
new_element(name, to_string(name), value));
}

template<class Allocator>
Expand Down Expand Up @@ -953,18 +947,22 @@ set_keep_alive_impl(
template<class Allocator>
auto
basic_fields<Allocator>::
new_element(field name,
string_view sname, string_view value) ->
element&
{
if(sname.size() + 2 >
(std::numeric_limits<off_t>::max)())
BOOST_THROW_EXCEPTION(std::length_error{
"field name too large"});
if(value.size() + 2 >
(std::numeric_limits<off_t>::max)())
BOOST_THROW_EXCEPTION(std::length_error{
"field value too large"});
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<off_t>(sname.size() + 2);
Expand All @@ -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<class Allocator>
auto
basic_fields<Allocator>::
new_element(
field name,
string_view sname,
string_view value) -> element&
{
error_code ec;
auto* e = new_element(name, sname, value, ec);
if(ec.failed())
BOOST_THROW_EXCEPTION(system_error{ec});
return *e;
}

template<class Allocator>
void
basic_fields<Allocator>::
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<class Allocator>
Expand Down
3 changes: 3 additions & 0 deletions test/beast/http/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down
27 changes: 27 additions & 0 deletions test/beast/http/fields.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 23ed47c

Please sign in to comment.