From a5deebeb3451c87936cf23d22ed2549eebd41aa2 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sun, 20 Jul 2025 19:48:25 +0000 Subject: [PATCH 1/7] Refactor transfer-coding parser rules --- cmake/toolchains/common.cmake | 8 - include/boost/http_proto/detail/header.hpp | 59 +++--- include/boost/http_proto/metadata.hpp | 64 ++---- src/detail/header.cpp | 189 ++++++++---------- src/fields_base.cpp | 2 +- src/parser.cpp | 8 +- src/rfc/transfer_coding_rule.cpp | 149 ++++++++++++++ src/rfc/transfer_coding_rule.hpp | 91 +++++++++ src/rfc/transfer_encoding_rule.cpp | 163 --------------- src/rfc/transfer_encoding_rule.hpp | 124 ------------ src/serializer.cpp | 8 +- test/unit/fields_base.cpp | 2 +- test/unit/metadata.cpp | 68 +++---- ...ding_rule.cpp => transfer_coding_rule.cpp} | 46 ++--- 14 files changed, 417 insertions(+), 564 deletions(-) create mode 100644 src/rfc/transfer_coding_rule.cpp create mode 100644 src/rfc/transfer_coding_rule.hpp delete mode 100644 src/rfc/transfer_encoding_rule.cpp delete mode 100644 src/rfc/transfer_encoding_rule.hpp rename test/unit/rfc/{transfer_encoding_rule.cpp => transfer_coding_rule.cpp} (63%) diff --git a/cmake/toolchains/common.cmake b/cmake/toolchains/common.cmake index 0b7727ae..04ec0bd8 100644 --- a/cmake/toolchains/common.cmake +++ b/cmake/toolchains/common.cmake @@ -14,11 +14,3 @@ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON CACHE STRING "") if(WIN32) add_definitions(-D_WIN32_WINNT=0x0601 -D_CRT_SECURE_NO_WARNINGS) endif() - -# Project options. -set(BOOST_JSON_BUILD_BENCHMARKS ON CACHE STRING "") - -# Detect Boost tree. -if(NOT DEFINED BOOST_JSON_IN_BOOST_TREE AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../../../Jamroot") - set(BOOST_JSON_IN_BOOST_TREE ON CACHE STRING "") -endif() diff --git a/include/boost/http_proto/detail/header.hpp b/include/boost/http_proto/detail/header.hpp index a8929d9f..36df89c5 100644 --- a/include/boost/http_proto/detail/header.hpp +++ b/include/boost/http_proto/detail/header.hpp @@ -18,10 +18,11 @@ #include #include #include -#include + #include +#include + #include -#include namespace boost { namespace http_proto { @@ -45,11 +46,11 @@ struct empty struct header { - // +------------+-----------+--------------+------------------------------+ - // | start-line | headers | free space | entry[count-1] ... entry[0] | - // +------------+-----------+--------------+------------------------------+ - // ^ ^ ^ ^ - // buf buf+prefix buf+size buf+cap + // +------------+---------+------+------------+-----------------------------+ + // | start-line | headers | \r\n | free space | entry[count-1] ... entry[0] | + // +------------+---------+------+------------+-----------------------------+ + // ^ ^ ^ ^ + // buf buf+prefix buf+size buf+cap #ifdef BOOST_HTTP_PROTO_TEST_LIMITS using offset_type = std::uint8_t; @@ -57,10 +58,10 @@ struct header using offset_type = std::uint32_t; #endif - static - constexpr + static constexpr std::size_t max_offset = - std::numeric_limits::max(); + std::numeric_limits< + offset_type>::max(); struct entry { @@ -123,7 +124,8 @@ struct header char* buf = nullptr; std::size_t cap = 0; std::size_t max_cap = - std::numeric_limits::max(); + std::numeric_limits< + std::size_t>::max(); offset_type size = 0; offset_type count = 0; @@ -151,22 +153,30 @@ struct header public: // in fields_base.hpp - static header& get(fields_base& f) noexcept; + static header& + get(fields_base& f) noexcept; - BOOST_HTTP_PROTO_DECL static header const* - get_default(detail::kind k) noexcept; + BOOST_HTTP_PROTO_DECL + static header const* + get_default(detail::kind k) noexcept; // called from parser explicit header(empty) noexcept; - BOOST_HTTP_PROTO_DECL header(detail::kind) noexcept; - BOOST_HTTP_PROTO_DECL void swap(header&) noexcept; - BOOST_HTTP_PROTO_DECL bool keep_alive() const noexcept; + BOOST_HTTP_PROTO_DECL + header(detail::kind) noexcept; + + BOOST_HTTP_PROTO_DECL + void swap(header&) noexcept; + + BOOST_HTTP_PROTO_DECL + bool keep_alive() const noexcept; static std::size_t bytes_needed( std::size_t size, std::size_t count) noexcept; static std::size_t table_space( std::size_t count) noexcept; + std::size_t table_space() const noexcept; table tab() const noexcept; @@ -188,7 +198,7 @@ struct header void on_insert_connection(core::string_view); void on_insert_content_length(core::string_view); void on_insert_expect(core::string_view); - void on_insert_transfer_encoding(); + void on_insert_transfer_encoding(core::string_view); void on_insert_content_encoding(core::string_view); void on_insert_upgrade(core::string_view); void on_erase_connection(); @@ -202,11 +212,14 @@ struct header // parsing - static std::size_t count_crlf( - core::string_view s) noexcept; - BOOST_HTTP_PROTO_DECL void parse( - std::size_t, header_limits const&, - system::error_code&) noexcept; + static std::size_t + count_crlf(core::string_view s) noexcept; + + BOOST_HTTP_PROTO_DECL + void parse( + std::size_t, + header_limits const&, + system::error_code&) noexcept; }; } // detail diff --git a/include/boost/http_proto/metadata.hpp b/include/boost/http_proto/metadata.hpp index 967b5ff4..3d754532 100644 --- a/include/boost/http_proto/metadata.hpp +++ b/include/boost/http_proto/metadata.hpp @@ -12,7 +12,6 @@ #define BOOST_HTTP_PROTO_METADATA_HPP #include -#include // VFALCO TEMPORARY #include #include #include @@ -26,9 +25,6 @@ namespace http_proto { */ enum class payload { -// VFALCO 3 space indent or -// else Doxygen malfunctions - /** * This message has no payload */ @@ -55,35 +51,19 @@ enum class payload ,to_eof }; -/** The effective encoding of the body octets. +/** Standard content-codings for HTTP message bodies */ -enum class -encoding +enum class content_coding { - /** - * Indicates the body is not encoded. - */ - identity, - - /** - * Indicates the body encoding is unsupported. - */ - unsupported, - - /** - * Indicates the body has deflate applied. - */ + unknown, + br, + compress, + dcb, + dcz, deflate, - - /** - * Indicates the body has gzip applied. - */ gzip, - - /** - * Indicates the body has brotli applied. - */ - br + identity, + zstd, }; //------------------------------------------------ @@ -153,8 +133,8 @@ struct metadata /** The body encoding. */ - http_proto::encoding encoding = - http_proto::encoding::identity; + content_coding coding = + content_coding::identity; #ifdef BOOST_HTTP_PROTO_AGGREGATE_WORKAROUND constexpr @@ -164,7 +144,7 @@ struct metadata content_encoding_t( system::error_code ec_, std::size_t count_, - http_proto::encoding encoding_) noexcept + encoding encoding_) noexcept : ec(ec_) , count(count_) , encoding(encoding_) @@ -261,26 +241,10 @@ struct metadata */ std::size_t count = 0; - /** The total number of codings - */ - std::size_t codings = 0; - /** True if valid and chunked is specified last */ bool is_chunked = false; - /** The effective body encoding. - - This indicates the type of encoding detected on the body, - if the fields contain a valid encoding. Otherwise it will have - @ref encoding::identity if the header is invalid. - - Whether or not the message entity is also chunked is set - in @ref metadata::is_chunked and not here. - */ - http_proto::encoding encoding = - http_proto::encoding::identity; - #ifdef BOOST_HTTP_PROTO_AGGREGATE_WORKAROUND constexpr transfer_encoding_t() = default; @@ -289,14 +253,10 @@ struct metadata transfer_encoding_t( system::error_code ec_, std::size_t count_, - std::size_t codings_, bool is_chunked_) noexcept : ec(ec_) , count(count_) - , codings(codings_) , is_chunked(is_chunked_) - , encoding( - http_proto::encoding::identity) { } #endif diff --git a/src/detail/header.cpp b/src/detail/header.cpp index e09c4f4b..a0b3e6be 100644 --- a/src/detail/header.cpp +++ b/src/detail/header.cpp @@ -8,29 +8,27 @@ // Official repository: https://github.com/cppalliance/http_proto // +#include "src/rfc/transfer_coding_rule.hpp" + #include #include #include #include +#include #include #include #include -#include - -#include "../rfc/transfer_encoding_rule.hpp" - +#include +#include +#include #include #include #include #include #include -#include -#include -#include #include - namespace boost { namespace http_proto { namespace detail { @@ -440,7 +438,7 @@ on_insert( case field::expect: return on_insert_expect(v); case field::transfer_encoding: - return on_insert_transfer_encoding(); + return on_insert_transfer_encoding(v); case field::upgrade: return on_insert_upgrade(v); default: @@ -584,84 +582,45 @@ on_insert_expect( void header:: -on_insert_transfer_encoding() +on_insert_transfer_encoding( + core::string_view v) { ++md.transfer_encoding.count; if(md.transfer_encoding.ec.failed()) return; - auto const n = - md.transfer_encoding.count; - md.transfer_encoding = {}; - md.transfer_encoding.count = n; - for(auto s : - fields_view_base::subrange( - this, find(field::transfer_encoding))) + + auto rv = grammar::parse( + v, list_rule(transfer_coding_rule, 1)); + if(! rv) { - auto rv = grammar::parse( - s, transfer_encoding_rule); - if(! rv) + // parse error + goto error; + } + for(auto t : *rv) + { + if(! md.transfer_encoding.is_chunked) { - // parse error - md.transfer_encoding.ec = - BOOST_HTTP_PROTO_ERR( - error::bad_transfer_encoding); - md.transfer_encoding.codings = 0; - md.transfer_encoding.is_chunked = false; - update_payload(); - return; + if(t.id == transfer_coding_rule_t::chunked) + md.transfer_encoding.is_chunked = true; + continue; } - md.transfer_encoding.codings += rv->size(); - for(auto t : *rv) + if(t.id == transfer_coding_rule_t::chunked) { - auto& mte = md.transfer_encoding; - - if(! mte.is_chunked ) - { - if( t.id == transfer_encoding::chunked ) - { - mte.is_chunked = true; - continue; - } - - auto b = - mte.encoding == - http_proto::encoding::identity; - - if( t.id == transfer_encoding::deflate ) - mte.encoding = http_proto::encoding::deflate; - - if( t.id == transfer_encoding::gzip ) - mte.encoding = http_proto::encoding::gzip; - - if( b ) - continue; - } - if(t.id == transfer_encoding::chunked) - { - // chunked appears twice - md.transfer_encoding.ec = - BOOST_HTTP_PROTO_ERR( - error::bad_transfer_encoding); - md.transfer_encoding.codings = 0; - md.transfer_encoding.is_chunked = false; - md.transfer_encoding.encoding = - http_proto::encoding::identity; - update_payload(); - return; - } - // chunked must be last - md.transfer_encoding.ec = - BOOST_HTTP_PROTO_ERR( - error::bad_transfer_encoding); - md.transfer_encoding.codings = 0; - md.transfer_encoding.is_chunked = false; - md.transfer_encoding.encoding = - http_proto::encoding::identity; - update_payload(); - return; + // chunked appears twice + goto error; } + // chunked must be last + goto error; } update_payload(); + return; + +error: + md.transfer_encoding.ec = + BOOST_HTTP_PROTO_ERR( + error::bad_transfer_encoding); + md.transfer_encoding.is_chunked = false; + update_payload(); } void @@ -670,49 +629,50 @@ on_insert_content_encoding( core::string_view v) { ++md.content_encoding.count; - if( md.content_encoding.ec.failed() ) + if(md.content_encoding.ec.failed()) return; auto rv = grammar::parse( v, list_rule(token_rule, 1)); - if( !rv ) + if(!rv) { md.content_encoding.ec = BOOST_HTTP_PROTO_ERR( error::bad_content_encoding); + md.content_encoding.coding = + content_coding::unknown; return; } - if( rv->size() > 1 || - md.content_encoding.count > 1) + if(rv->size() > 1 || md.content_encoding.count > 1) { - md.content_encoding.encoding = - encoding::unsupported; + md.content_encoding.coding = + content_coding::unknown; return; } - if( grammar::ci_is_equal(*(rv->begin()), - "deflate") ) + if(grammar::ci_is_equal( + *rv->begin(), "deflate")) { - md.content_encoding.encoding = - encoding::deflate; + md.content_encoding.coding = + content_coding::deflate; } - else if( grammar::ci_is_equal(*(rv->begin()), - "gzip") ) + else if(grammar::ci_is_equal( + *rv->begin(), "gzip")) { - md.content_encoding.encoding = - encoding::gzip; + md.content_encoding.coding = + content_coding::gzip; } - else if( grammar::ci_is_equal(*(rv->begin()), - "br") ) + else if(grammar::ci_is_equal( + *rv->begin(), "br")) { - md.content_encoding.encoding = - encoding::br; + md.content_encoding.coding = + content_coding::br; } else { - md.content_encoding.encoding = - encoding::unsupported; + md.content_encoding.coding = + content_coding::unknown; } } @@ -774,10 +734,12 @@ on_erase_connection() while(n > 0) { if(e->id == field::connection) + { on_insert_connection( core::string_view( p + e->vp, e->vn)); - --n; + --n; + } --e; } } @@ -809,10 +771,12 @@ on_erase_content_length() while(n > 0) { if(e->id == field::content_length) + { on_insert_content_length( core::string_view( p + e->vp, e->vn)); - --n; + --n; + } --e; } update_payload(); @@ -841,17 +805,19 @@ on_erase_expect() return; */ // reset and re-insert - auto n = count; + auto n = md.expect.count; auto const p = cbuf + prefix; auto const* e = &tab()[0]; md.expect = {}; while(n > 0) { if(e->id == field::expect) + { on_insert_expect( core::string_view( p + e->vp, e->vn)); - --n; + --n; + } --e; } } @@ -862,17 +828,22 @@ on_erase_transfer_encoding() { BOOST_ASSERT( md.transfer_encoding.count > 0); - --md.transfer_encoding.count; - if(md.transfer_encoding.count == 0) + // reset and re-insert + auto n = md.transfer_encoding.count - 1; + auto const p = cbuf + prefix; + auto const* e = &tab()[0]; + md.transfer_encoding = {}; + while(n > 0) { - // no Transfer-Encoding - md.transfer_encoding = {}; - update_payload(); - return; + if(e->id == field::transfer_encoding) + { + on_insert_transfer_encoding( + core::string_view( + p + e->vp, e->vn)); + --n; + } + --e; } - // re-insert everything - --md.transfer_encoding.count; - on_insert_transfer_encoding(); } void diff --git a/src/fields_base.cpp b/src/fields_base.cpp index 1804d2a9..a45f4138 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -67,7 +67,7 @@ verify_field_name( return rv; } -system::result +system::result verify_field_value( core::string_view value) { diff --git a/src/parser.cpp b/src/parser.cpp index 8b9ba8dd..6ec77e47 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1087,23 +1087,23 @@ parse( // must be installed after them. auto const p = ws_.reserve_front(cap); - switch(h_.md.content_encoding.encoding) + switch(h_.md.content_encoding.coding) { - case encoding::deflate: + case content_coding::deflate: if(!svc_.cfg.apply_deflate_decoder) goto no_filter; filter_ = &ws_.emplace( ctx_, ws_, svc_.cfg.zlib_window_bits); break; - case encoding::gzip: + case content_coding::gzip: if(!svc_.cfg.apply_deflate_decoder) goto no_filter; filter_ = &ws_.emplace( ctx_, ws_, svc_.cfg.zlib_window_bits + 16); break; - case encoding::br: + case content_coding::br: if(!svc_.cfg.apply_brotli_decoder) goto no_filter; filter_ = &ws_.emplace( diff --git a/src/rfc/transfer_coding_rule.cpp b/src/rfc/transfer_coding_rule.cpp new file mode 100644 index 00000000..f4276203 --- /dev/null +++ b/src/rfc/transfer_coding_rule.cpp @@ -0,0 +1,149 @@ +// +// Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#include "src/rfc/transfer_coding_rule.hpp" + +#include +#include +#include +#include +#include + +namespace boost { +namespace http_proto { +namespace detail { + +auto +transfer_parameter_rule_t::parse( + char const*& it, + char const* end) const noexcept -> + system::result +{ + value_type t; + auto it0 = it; + // OWS + it = grammar::find_if_not( + it, end, ws); + // ";" + if(it == end) + { + it = it0; + BOOST_HTTP_PROTO_RETURN_EC( + grammar::error::need_more); + } + if(*it != ';') + { + it = it0; + BOOST_HTTP_PROTO_RETURN_EC( + grammar::error::mismatch); + } + ++it; + // OWS + it = grammar::find_if_not( + it, end, ws); + // token + { + auto rv = grammar::parse( + it, end, token_rule); + if(! rv) + return rv.error(); + t.name = *rv; + } + // BWS + it = grammar::find_if_not( + it, end, ws); + // "=" + if(it == end) + { + it = it0; + BOOST_HTTP_PROTO_RETURN_EC( + grammar::error::need_more); + } + if(*it != '=') + { + it = it0; + BOOST_HTTP_PROTO_RETURN_EC( + grammar::error::syntax); + } + ++it; + // BWS + it = grammar::find_if_not( + it, end, ws); + // quoted-token + { + auto rv = grammar::parse( + it, end, quoted_token_rule); + if(! rv) + return rv.error(); + t.value = *rv; + } + return t; +} + +//------------------------------------------------ + +auto +transfer_coding_rule_t:: +parse( + char const*& it, + char const* end) const noexcept -> + system::result +{ + value_type t; + { + // token + auto rv = grammar::parse( + it, end, token_rule); + if(! rv) + return rv.error(); + + // These can't have transfer-parameter + if(grammar::ci_is_equal( + *rv, "chunked")) + { + t.id = coding::chunked; + return t; + } + if(grammar::ci_is_equal( + *rv, "compress")) + { + t.id = coding::compress; + return t; + } + if(grammar::ci_is_equal( + *rv, "deflate")) + { + t.id = coding::deflate; + return t; + } + if(grammar::ci_is_equal( + *rv, "gzip")) + { + t.id = coding::gzip; + return t; + } + + t.extension.token = *rv; + } + // transfer-extension = token *( OWS ";" OWS transfer-parameter ) + { + auto rv = grammar::parse(it, end, + grammar::range_rule( + detail::transfer_parameter_rule)); + if(! rv) + return rv.error(); + t.extension.params = std::move(*rv); + } + return t; +} + +} // detail +} // http_proto +} // boost diff --git a/src/rfc/transfer_coding_rule.hpp b/src/rfc/transfer_coding_rule.hpp new file mode 100644 index 00000000..6912e83e --- /dev/null +++ b/src/rfc/transfer_coding_rule.hpp @@ -0,0 +1,91 @@ +// +// Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#ifndef BOOST_HTTP_PROTO_RFC_TRANSFER_CODING_RULE_HPP +#define BOOST_HTTP_PROTO_RFC_TRANSFER_CODING_RULE_HPP + +#include +#include +#include +#include + +namespace boost { +namespace http_proto { +namespace detail { + +/* + https://datatracker.ietf.org/doc/html/rfc7230#section-4 + + transfer-parameter = token BWS "=" BWS ( token / quoted-string ) +*/ +struct transfer_parameter_rule_t +{ + struct value_type + { + core::string_view name; + quoted_token_view value; + }; + + auto + parse( + char const*& it, + char const* end) const noexcept -> + system::result; +}; + +constexpr transfer_parameter_rule_t transfer_parameter_rule{}; + +/* + https://datatracker.ietf.org/doc/html/rfc7230#section-4 + + transfer-coding = "chunked" + / "compress" ; + / "deflate" ; + / "gzip" ; + / transfer-extension + transfer-extension = token *( OWS ";" OWS transfer-parameter ) +*/ +struct transfer_coding_rule_t +{ + enum coding + { + unknown, + chunked, + compress, + deflate, + gzip + }; + + struct value_type + { + coding id = unknown; + struct + { + core::string_view token; + grammar::range< + transfer_parameter_rule_t:: + value_type> params; + } extension; + }; + + auto + parse( + char const*& it, + char const* end) const noexcept -> + system::result; +}; + +constexpr transfer_coding_rule_t transfer_coding_rule{}; + +} // detail +} // http_proto +} // boost + +#endif diff --git a/src/rfc/transfer_encoding_rule.cpp b/src/rfc/transfer_encoding_rule.cpp deleted file mode 100644 index 337f8636..00000000 --- a/src/rfc/transfer_encoding_rule.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// -// Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#include "transfer_encoding_rule.hpp" - -#include -#include -#include -#include - -namespace boost { -namespace http_proto { - -//------------------------------------------------ - -namespace detail { - -/* - tparams = *tparam - tparam = OWS ";" OWS token BWS "=" BWS ( token / quoted-string ) -*/ - -struct tparam_rule_t -{ - using value_type = - transfer_encoding::param; - - auto - parse( - char const*& it, - char const* end) const noexcept -> - system::result - { - value_type t; - auto it0 = it; - // OWS - it = grammar::find_if_not( - it, end, ws); - // ";" - if(it == end) - { - it = it0; - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::need_more); - } - if(*it != ';') - { - it = it0; - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::mismatch); - } - ++it; - // OWS - it = grammar::find_if_not( - it, end, ws); - // token - { - auto rv = grammar::parse( - it, end, token_rule); - if(! rv) - return rv.error(); - t.key = *rv; - } - // BWS - it = grammar::find_if_not( - it, end, ws); - // "=" - if(it == end) - { - it = it0; - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::need_more); - } - if(*it != '=') - { - it = it0; - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::syntax); - } - ++it; - // BWS - it = grammar::find_if_not( - it, end, ws); - // quoted-token - { - auto rv = grammar::parse( - it, end, quoted_token_rule); - if(! rv) - return rv.error(); - t.value = *rv; - } - return t; - } -}; - -constexpr tparam_rule_t tparam_rule{}; - -//------------------------------------------------ - -auto -transfer_encoding_rule_t:: -parse( - char const*& it, - char const* end) const noexcept -> - system::result -{ - value_type t; - { - // token - auto rv = grammar::parse( - it, end, token_rule); - if(! rv) - return rv.error(); - t.str = *rv; - - // These can't have tparams - if(grammar::ci_is_equal( - t.str, "chunked")) - { - t.id = transfer_encoding::chunked; - return t; - } - if(grammar::ci_is_equal( - t.str, "compress")) - { - t.id = transfer_encoding::compress; - return t; - } - if(grammar::ci_is_equal( - t.str, "deflate")) - { - t.id = transfer_encoding::deflate; - return t; - } - if(grammar::ci_is_equal( - t.str, "gzip")) - { - t.id = transfer_encoding::gzip; - return t; - } - } -// *( OWS ";" OWS token BWS "=" BWS ( token / quoted-string ) - { - auto rv = grammar::parse(it, end, - grammar::range_rule( - detail::tparam_rule)); - if(! rv) - return rv.error(); - t.params = std::move(*rv); - } - return t; -} -} // detail - - -} // http_proto -} // boost diff --git a/src/rfc/transfer_encoding_rule.hpp b/src/rfc/transfer_encoding_rule.hpp deleted file mode 100644 index ede9f551..00000000 --- a/src/rfc/transfer_encoding_rule.hpp +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_RFC_TRANSFER_ENCODING_RULE_HPP -#define BOOST_HTTP_PROTO_RFC_TRANSFER_ENCODING_RULE_HPP - -#include -#include -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -//------------------------------------------------ - -/** A value of Transfer-Encoding -*/ -struct transfer_encoding -{ - enum coding - { - unknown = 0, - chunked, - compress, - deflate, - gzip - }; - - struct param - { - core::string_view key; - quoted_token_view value; - }; - - coding id = unknown; - core::string_view str; - grammar::range params; -}; - -//------------------------------------------------ - -/** Rule to match transfer-coding - - @par Value Type - @code - using value_type = transfer_encoding; - @endcode - - @par Example - @code - @endcode - - @par BNF - @code - transfer-coding = "chunked" - / "compress" - / "deflate" - / "gzip" - / transfer-extension - transfer-extension = token *( OWS ";" OWS transfer-parameter ) - transfer-parameter = token BWS "=" BWS ( token / quoted-string ) - @endcode - - @par Specification - @li 3.3.1. Transfer-Encoding (rfc7230) -*/ -#ifdef BOOST_HTTP_PROTO_DOCS -constexpr __implementation_defined__ transfer_encoding_rule; -#else -struct transfer_encoding_rule_t -{ - using value_type = transfer_encoding; - - BOOST_HTTP_PROTO_DECL - auto - parse( - char const*& it, - char const* end) const noexcept -> - system::result; -}; - -constexpr transfer_encoding_rule_t transfer_encoding_rule_impl{}; -#endif - -//------------------------------------------------ - -/** Rule matching the Transfer-Encoding field value - - @par Value Type - @code - using value_type = grammar::range< transfer_encoding >; - @endcode - - @par Example - @code - @endcode - - @par BNF - @code - Transfer-Encoding = 1#transfer-coding - @endcode - - @par Specification - @li 3.3.1. Transfer-Encoding (rfc7230) -*/ -constexpr auto transfer_encoding_rule = - list_rule( transfer_encoding_rule_impl, 1 ); - -} // detail -} // http_proto -} // boost - -#endif diff --git a/src/serializer.cpp b/src/serializer.cpp index 9e13e769..ef0c7a17 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -707,9 +707,9 @@ start_init( is_chunked_ = md.transfer_encoding.is_chunked; // Content-Encoding - switch (md.content_encoding.encoding) + switch (md.content_encoding.coding) { - case encoding::deflate: + case content_coding::deflate: if(!svc_.cfg.apply_deflate_encoder) goto no_filter; filter_ = &ws_.emplace( @@ -720,7 +720,7 @@ start_init( svc_.cfg.zlib_mem_level); break; - case encoding::gzip: + case content_coding::gzip: if(!svc_.cfg.apply_gzip_encoder) goto no_filter; filter_ = &ws_.emplace( @@ -731,7 +731,7 @@ start_init( svc_.cfg.zlib_mem_level); break; - case encoding::br: + case content_coding::br: if(!svc_.cfg.apply_brotli_encoder) goto no_filter; filter_ = &ws_.emplace( diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index 130de03f..75c581c9 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -642,7 +642,7 @@ struct fields_base_test "\r\n", [](fields_base& f) { - system::result rv; + system::result rv; rv = f.insert(f.find("U"), "Ser ver", "x"); BOOST_TEST(rv.has_error()); diff --git a/test/unit/metadata.cpp b/test/unit/metadata.cpp index 8f083c86..dc7e66de 100644 --- a/test/unit/metadata.cpp +++ b/test/unit/metadata.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2021 Vinnie Falco (vinnie dot falco at gmail dot com) +// Copyright (c) 2025 Mohammad Nejati // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,9 +16,6 @@ #include "test_helpers.hpp" -#include -#include - namespace boost { namespace http_proto { @@ -286,28 +284,28 @@ struct metadata_test req.metadata().content_encoding; BOOST_TEST_EQ(t.ec, ce.ec); BOOST_TEST_EQ(t.count, ce.count); - BOOST_TEST_EQ(t.encoding, ce.encoding); + BOOST_TEST_EQ(t.coding, ce.coding); }; check( "GET / HTTP/1.1\r\n" "\r\n", [](message_base&){}, - { ok, 0, encoding::identity }); + { ok, 0, content_coding::identity }); check( "GET / HTTP/1.1\r\n" "Content-Encoding: gzip\r\n" "\r\n", [](message_base&){}, - { ok, 1, encoding::gzip }); + { ok, 1, content_coding::gzip }); check( "GET / HTTP/1.1\r\n" "Content-Encoding: gzip, deflate\r\n" "\r\n", [](message_base&){}, - { ok, 1, encoding::unsupported }); + { ok, 1, content_coding::unknown }); check( "GET / HTTP/1.1\r\n" @@ -315,14 +313,14 @@ struct metadata_test "Content-Encoding: deflate\r\n" "\r\n", [](message_base&){}, - { ok, 2, encoding::unsupported }); + { ok, 2, content_coding::unknown }); check( "GET / HTTP/1.1\r\n" "Content-Encoding: bad;\r\n" "\r\n", [](message_base&){}, - { error::bad_content_encoding, 1, encoding::identity}); + { error::bad_content_encoding, 1, content_coding::unknown}); } void @@ -474,7 +472,6 @@ struct metadata_test req.metadata().transfer_encoding; BOOST_TEST_EQ(t.ec, te.ec); BOOST_TEST_EQ(t.count, te.count); - BOOST_TEST_EQ(t.codings, te.codings); BOOST_TEST_EQ(t.is_chunked, te.is_chunked); }; @@ -482,21 +479,21 @@ struct metadata_test "GET / HTTP/1.1\r\n" "\r\n", [](message_base&){}, - { ok, 0, 0, false }); + { ok, 0, false }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n" "\r\n", [](message_base&){}, - { ok, 1, 1, true }); + { ok, 1, true }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: chunked, chunked\r\n" "\r\n", [](message_base&){}, - { error::bad_transfer_encoding, 1, 0, false }); + { error::bad_transfer_encoding, 1, false }); check( "GET / HTTP/1.1\r\n" @@ -504,14 +501,14 @@ struct metadata_test "Transfer-Encoding: chunked\r\n" "\r\n", [](message_base&){}, - { error::bad_transfer_encoding, 2, 0, false }); + { error::bad_transfer_encoding, 2, false }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: chunked, compress\r\n" "\r\n", [](message_base&){}, - { error::bad_transfer_encoding, 1, 0, false}); + { error::bad_transfer_encoding, 1, false}); check( "GET / HTTP/1.1\r\n" @@ -519,14 +516,14 @@ struct metadata_test "Transfer-Encoding: compress\r\n" "\r\n", [](message_base&){}, - { error::bad_transfer_encoding, 2, 0, false}); + { error::bad_transfer_encoding, 2, false}); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: chunked;a=b\r\n" "\r\n", [](message_base&){}, - { error::bad_transfer_encoding, 1, 0, false }); + { error::bad_transfer_encoding, 1, false }); check( "GET / HTTP/1.1\r\n" @@ -534,55 +531,42 @@ struct metadata_test "Transfer-Encoding: chunked\r\n" "\r\n", [](message_base&){}, - { error::bad_transfer_encoding, 2, 0, false }); - - check( - "GET / HTTP/1.1\r\n" - "Server: localhost\r\n" - "Transfer-Encoding: gzip\r\n" - "Transfer-Encoding: compress\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n", - [](message_base& f) - { - f.erase(f.find(field::transfer_encoding)); - }, - { error::bad_transfer_encoding, 2, 0, false }); + { error::bad_transfer_encoding, 2, false }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: compress\r\n" "\r\n", [](message_base&){}, - { ok, 1, 1, false }); + { ok, 1, false }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: deflate\r\n" "\r\n", [](message_base&){}, - { ok, 1, 1, false }); + { ok, 1, false }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: gzip\r\n" "\r\n", [](message_base&){}, - { ok, 1, 1, false }); + { ok, 1, false }); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: custom;a=1\r\n" "\r\n", [](message_base&){}, - { ok, 1, 1, false}); + { ok, 1, false}); check( "GET / HTTP/1.1\r\n" "Transfer-Encoding: a,b,c\r\n" "\r\n", [](message_base&){}, - { ok, 1, 3, false}); + { ok, 1, false}); check( "GET / HTTP/1.1\r\n" @@ -590,7 +574,7 @@ struct metadata_test "Transfer-Encoding: x,y\r\n" "\r\n", [](message_base&){}, - { ok, 2, 5, false}); + { ok, 2, false}); //---------------------------------------- @@ -602,7 +586,7 @@ struct metadata_test { f.erase(f.find(field::transfer_encoding)); }, - { ok, 0, 0, false }); + { ok, 0, false }); check( "GET / HTTP/1.1\r\n" @@ -613,7 +597,7 @@ struct metadata_test { f.erase(f.find(field::transfer_encoding)); }, - { ok, 1, 1, true }); + { ok, 1, true }); check( "GET / HTTP/1.1\r\n" @@ -625,7 +609,7 @@ struct metadata_test { f.erase(f.find(field::transfer_encoding)); }, - { ok, 1, 1, true }); + { ok, 1, true }); check( "GET / HTTP/1.1\r\n" @@ -636,7 +620,7 @@ struct metadata_test { f.erase(f.find(field::transfer_encoding)); }, - { ok, 0, 0, false }); + { ok, 0, false }); check( "GET / HTTP/1.1\r\n" @@ -649,7 +633,7 @@ struct metadata_test { f.erase(field::transfer_encoding); }, - { ok, 0, 0, false }); + { ok, 0, false }); } void diff --git a/test/unit/rfc/transfer_encoding_rule.cpp b/test/unit/rfc/transfer_coding_rule.cpp similarity index 63% rename from test/unit/rfc/transfer_encoding_rule.cpp rename to test/unit/rfc/transfer_coding_rule.cpp index 4bddf9d1..dc3d7f7c 100644 --- a/test/unit/rfc/transfer_encoding_rule.cpp +++ b/test/unit/rfc/transfer_coding_rule.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,55 +9,33 @@ // // Test that header file is self-contained. -#include "../../src/rfc/transfer_encoding_rule.hpp" +#include "../../src/rfc/transfer_coding_rule.hpp" +#include "test_helpers.hpp" + +#include #include #include -#include "test_helpers.hpp" - namespace boost { namespace http_proto { +namespace detail { BOOST_STATIC_ASSERT( std::is_nothrow_copy_assignable< - system::result>::value); - -BOOST_STATIC_ASSERT( - std::is_nothrow_copy_assignable< - grammar::range>::value); - -BOOST_STATIC_ASSERT( - std::is_nothrow_copy_assignable< - detail::transfer_encoding::param>::value); - -BOOST_STATIC_ASSERT( - std::is_nothrow_copy_assignable< - quoted_token_view>::value); - -BOOST_STATIC_ASSERT( - std::is_nothrow_copy_constructible< - system::result>::value); - -BOOST_STATIC_ASSERT( - std::is_nothrow_copy_constructible< - grammar::range>::value); - -BOOST_STATIC_ASSERT( - std::is_nothrow_copy_constructible< - detail::transfer_encoding::param>::value); + system::result>::value); BOOST_STATIC_ASSERT( std::is_nothrow_copy_constructible< - quoted_token_view>::value); + system::result>::value); -struct transfer_encoding_rule_test +struct transfer_coding_rule_test { void run() { auto const& t = - detail::transfer_encoding_rule; + list_rule(transfer_coding_rule, 1); bad(t, ""); bad(t, " "); @@ -98,8 +77,9 @@ struct transfer_encoding_rule_test }; TEST_SUITE( - transfer_encoding_rule_test, - "boost.http_proto.transfer_encoding_rule"); + transfer_coding_rule_test, + "boost.http_proto.detail.transfer_coding_rule"); +} // detail } // http_proto } // boost From 3b87472c35aafd4627b084aefc5a511a8f694fad Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Mon, 21 Jul 2025 19:55:42 +0000 Subject: [PATCH 2/7] max_cap_ is a member of fields_base --- include/boost/http_proto/detail/header.hpp | 3 --- include/boost/http_proto/fields.hpp | 1 + include/boost/http_proto/fields_base.hpp | 22 +++++------------- include/boost/http_proto/request.hpp | 1 + include/boost/http_proto/response.hpp | 1 + src/detail/header.cpp | 3 --- src/fields_base.cpp | 26 +++++++++++----------- src/rfc/detail/rules.cpp | 2 +- 8 files changed, 22 insertions(+), 37 deletions(-) diff --git a/include/boost/http_proto/detail/header.hpp b/include/boost/http_proto/detail/header.hpp index 36df89c5..f9de1c79 100644 --- a/include/boost/http_proto/detail/header.hpp +++ b/include/boost/http_proto/detail/header.hpp @@ -123,9 +123,6 @@ struct header char const* cbuf = nullptr; char* buf = nullptr; std::size_t cap = 0; - std::size_t max_cap = - std::numeric_limits< - std::size_t>::max(); offset_type size = 0; offset_type count = 0; diff --git a/include/boost/http_proto/fields.hpp b/include/boost/http_proto/fields.hpp index 3c6c7c20..5bd3ef06 100644 --- a/include/boost/http_proto/fields.hpp +++ b/include/boost/http_proto/fields.hpp @@ -214,6 +214,7 @@ class fields final swap(fields& other) noexcept { h_.swap(other.h_); + std::swap(max_cap_, other.max_cap_); } /** Swap two instances diff --git a/include/boost/http_proto/fields_base.hpp b/include/boost/http_proto/fields_base.hpp index 30f37ff9..908a41ba 100644 --- a/include/boost/http_proto/fields_base.hpp +++ b/include/boost/http_proto/fields_base.hpp @@ -38,7 +38,10 @@ class fields_base : public virtual fields_view_base { detail::header h_; - bool static_storage = false; + bool static_storage_ = false; + std::size_t max_cap_ = + std::numeric_limits< + std::size_t>::max(); using entry = detail::header::entry; @@ -77,7 +80,6 @@ class fields_base friend class static_response; friend class serializer; friend class message_base; - friend struct detail::header; friend struct detail::prefix_op; BOOST_HTTP_PROTO_DECL @@ -142,7 +144,7 @@ class fields_base std::size_t max_capacity_in_bytes() noexcept { - return h_.max_cap; + return max_cap_; } /** Returns the total number of bytes allocated by the container @@ -588,20 +590,6 @@ class fields_base void raw_erase_n(field, std::size_t) noexcept; }; -//------------------------------------------------ - -#ifndef BOOST_HTTP_PROTO_DOCS -namespace detail { -inline -header& -header:: -get(fields_base& f) noexcept -{ - return f.h_; -} -} // detail -#endif - } // http_proto } // boost diff --git a/include/boost/http_proto/request.hpp b/include/boost/http_proto/request.hpp index dc36a089..b1993f41 100644 --- a/include/boost/http_proto/request.hpp +++ b/include/boost/http_proto/request.hpp @@ -194,6 +194,7 @@ class request swap(request& other) noexcept { h_.swap(other.h_); + std::swap(max_cap_, other.max_cap_); } /** Swap two instances diff --git a/include/boost/http_proto/response.hpp b/include/boost/http_proto/response.hpp index c0c247be..144a6bde 100644 --- a/include/boost/http_proto/response.hpp +++ b/include/boost/http_proto/response.hpp @@ -213,6 +213,7 @@ class response swap(response& other) noexcept { h_.swap(other.h_); + std::swap(max_cap_, other.max_cap_); } /** Swap two instances diff --git a/src/detail/header.cpp b/src/detail/header.cpp index a0b3e6be..be53b743 100644 --- a/src/detail/header.cpp +++ b/src/detail/header.cpp @@ -137,7 +137,6 @@ swap(header& h) noexcept std::swap(cbuf, h.cbuf); std::swap(buf, h.buf); std::swap(cap, h.cap); - std::swap(max_cap, h.max_cap); std::swap(size, h.size); std::swap(count, h.count); std::swap(prefix, h.prefix); @@ -339,12 +338,10 @@ assign_to( auto const buf_ = dest.buf; auto const cbuf_ = dest.cbuf; auto const cap_ = dest.cap; - auto const max_cap_ = dest.max_cap; dest = *this; dest.buf = buf_; dest.cbuf = cbuf_; dest.cap = cap_; - dest.max_cap = max_cap_; } //------------------------------------------------ diff --git a/src/fields_base.cpp b/src/fields_base.cpp index a45f4138..f55c0b33 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -171,7 +171,7 @@ op_t:: reserve( std::size_t n) { - if(n > self_.max_capacity_in_bytes()) + if(n > self_.max_cap_) { // max capacity exceeded detail::throw_length_error(); @@ -270,7 +270,7 @@ prefix_op_t( { // static storage will always throw which is // intended since they cannot reallocate. - if(self.h_.max_cap < new_size) + if(self.max_cap_ < new_size) detail::throw_length_error(); auto bytes_needed = @@ -347,14 +347,14 @@ fields_base( std::size_t storage_size) noexcept : fields_view_base(&h_) , h_(k) - , static_storage(true) + , static_storage_(true) { h_.buf = storage; h_.cap = align_down( storage, storage_size, alignof(detail::header::entry)); - h_.max_cap = h_.cap; + max_cap_ = h_.cap; } fields_base:: @@ -367,7 +367,7 @@ fields_base( if(storage_size != 0) { reserve_bytes(storage_size); - h_.max_cap = h_.cap; + max_cap_ = h_.cap; } } @@ -383,7 +383,7 @@ fields_base( detail::throw_length_error(); reserve_bytes(storage_size); - h_.max_cap = max_storage_size; + max_cap_ = max_storage_size; } // copy s and parse it @@ -427,7 +427,7 @@ fields_base( core::string_view s) : fields_view_base(&h_) , h_(detail::empty{k}) - , static_storage(true) + , static_storage_(true) { h_.cbuf = storage; h_.buf = storage; @@ -435,7 +435,7 @@ fields_base( storage, storage_size, alignof(detail::header::entry)); - h_.max_cap = h_.cap; + max_cap_ = h_.cap; auto n = detail::header::count_crlf(s); if(h_.kind == detail::kind::fields) @@ -492,14 +492,14 @@ fields_base( std::size_t storage_size) : fields_view_base(&h_) , h_(h.kind) - , static_storage(true) + , static_storage_(true) { h_.buf = storage; h_.cap = align_down( storage, storage_size, alignof(detail::header::entry)); - h_.max_cap = h_.cap; + max_cap_ = h_.cap; if(h.is_default()) return; @@ -522,7 +522,7 @@ fields_base( fields_base:: ~fields_base() { - if(h_.buf && !static_storage) + if(h_.buf && !static_storage_) delete[] h_.buf; } @@ -578,7 +578,7 @@ shrink_to_fit() noexcept h_.cap) return; - if(static_storage) + if(static_storage_) return; fields_base tmp(h_); @@ -941,7 +941,7 @@ copy_impl( return; } - if(static_storage) + if(static_storage_) { if(h.is_default()) { diff --git a/src/rfc/detail/rules.cpp b/src/rfc/detail/rules.cpp index 25e88d4a..cf0ef819 100644 --- a/src/rfc/detail/rules.cpp +++ b/src/rfc/detail/rules.cpp @@ -22,7 +22,7 @@ #include #include -#include "rules.hpp" +#include "src/rfc/detail/rules.hpp" namespace boost { namespace http_proto { From b23295268c291fb2cba050c92b71f62316282304 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Tue, 22 Jul 2025 19:40:40 +0000 Subject: [PATCH 3/7] fields_base modifiers have error_code overloads --- include/boost/http_proto/fields_base.hpp | 497 ++++++++++++++++++++--- src/fields_base.cpp | 87 ++-- test/unit/fields_base.cpp | 226 +++++++---- 3 files changed, 627 insertions(+), 183 deletions(-) diff --git a/include/boost/http_proto/fields_base.hpp b/include/boost/http_proto/fields_base.hpp index 908a41ba..de57a3d0 100644 --- a/include/boost/http_proto/fields_base.hpp +++ b/include/boost/http_proto/fields_base.hpp @@ -13,9 +13,9 @@ #define BOOST_HTTP_PROTO_FIELDS_BASE_HPP #include +#include #include #include -#include namespace boost { namespace http_proto { @@ -181,12 +181,13 @@ class fields_base /** Append a header - This function appends a new header with the - specified id and value. The value must be - syntactically valid or else an error is returned. - Any leading or trailing whitespace in the new value - is ignored. -
+ This function appends a new header. + Existing headers with the same name are + not changed. + + Any leading or trailing whitespace in the + value is ignored. + No iterators are invalidated. @par Example @@ -201,7 +202,6 @@ class fields_base @par Exception Safety Strong guarantee. - Calls to allocate may throw. @param id The field name constant, which may not be @ref field::unknown. @@ -209,27 +209,87 @@ class fields_base @param value A value, which must be semantically valid for the message. - @return The error, if any occurred. + @throw boost::system::system_error if value + is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ - system::result + void append( field id, core::string_view value) { - BOOST_ASSERT( - id != field::unknown); - return insert_impl( - id, to_string(id), value, h_.count); + system::error_code ec; + append(id, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); } /** Append a header - This function appends a new header with the - specified name and value. Both values must be - syntactically valid or else an error is returned. - Any leading or trailing whitespace in the new + This function appends a new header. + Existing headers with the same name are + not changed. + + Any leading or trailing whitespace in the value is ignored. -
+ + No iterators are invalidated. + + @par Example + @code + request req; + + req.append( field::user_agent, "Boost" ); + @endcode + + @par Complexity + Linear in `to_string( id ).size() + value.size()`. + + @par Exception Safety + Strong guarantee. + + @param id The field name constant, + which may not be @ref field::unknown. + + @param value A value, which must be semantically + valid for the message. + + @param ec Set to the error, if value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + void + append( + field id, + core::string_view value, + system::error_code& ec) + { + // TODO: this should probably return an error + BOOST_ASSERT(id != field::unknown); + insert_impl( + id, + to_string(id), + value, + h_.count, + ec); + } + + /** Append a header + + This function appends a new header. + Existing headers with the same name are + not changed. + + Any leading or trailing whitespace in the + value is ignored. + No iterators are invalidated. @par Example @@ -244,26 +304,80 @@ class fields_base @par Exception Safety Strong guarantee. - Calls to allocate may throw. @param name The header name. @param value A value, which must be semantically valid for the message. - @return The error, if any occurred. + @throw boost::system::system_error if name or + value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ - system::result + void append( core::string_view name, core::string_view value) { - return insert_impl( - string_to_field( - name), + system::error_code ec; + append(name, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Append a header + + This function appends a new header. + Existing headers with the same name are + not changed. + + Any leading or trailing whitespace in the + value is ignored. + + No iterators are invalidated. + + @par Example + @code + request req; + + req.append( "User-Agent", "Boost" ); + @endcode + + @par Complexity + Linear in `name.size() + value.size()`. + + @par Exception Safety + Strong guarantee. + + @param name The header name. + + @param value A value, which must be semantically + valid for the message. + + @param ec Set to the error, if name or value is + invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + void + append( + core::string_view name, + core::string_view value, + system::error_code& ec) + { + insert_impl( + string_to_field(name), name, value, - h_.count); + h_.count, + ec); } /** Insert a header @@ -274,7 +388,7 @@ class fields_base inserted. Names are not case-sensitive. Any leading or trailing whitespace in the new value is ignored. -
+ All iterators that are equal to `before` or come after are invalidated. @@ -290,10 +404,8 @@ class fields_base @par Exception Safety Strong guarantee. - Calls to allocate may throw. - @return An iterator the newly inserted header, or - an error if any occurred. + @return An iterator to the newly inserted header. @param before Position to insert before. @@ -302,22 +414,86 @@ class fields_base @param value A value, which must be semantically valid for the message. + + @throw boost::system::system_error if value + is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ - system::result + iterator insert( iterator before, field id, core::string_view value) { - // TODO: this should probably return an error - BOOST_ASSERT( - id != field::unknown); + system::error_code ec; + auto const it = insert( + before, id, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); + return it; + } + + /** Insert a header + + If a matching header with the same name + exists, it is not replaced. Instead, an + additional header with the same name is + inserted. Names are not case-sensitive. + Any leading or trailing whitespace in + the new value is ignored. + + All iterators that are equal to `before` + or come after are invalidated. + + @par Example + @code + request req; + + req.insert( req.begin(), field::user_agent, "Boost" ); + @endcode + + @par Complexity + Linear in `to_string( id ).size() + value.size()`. - auto rv = insert_impl( - id, to_string(id), value, before.i_); + @par Exception Safety + Strong guarantee. + + @return An iterator to the newly inserted header. + + @param before Position to insert before. - if( rv.has_error() ) - return rv.error(); + @param id The field name constant, + which may not be @ref field::unknown. + + @param value A value, which must be semantically + valid for the message. + + @param ec Set to the error, if value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + iterator + insert( + iterator before, + field id, + core::string_view value, + system::error_code& ec) + { + // TODO: this should probably return an error + BOOST_ASSERT(id != field::unknown); + insert_impl( + id, + to_string(id), + value, + before.i_, + ec); return before; } @@ -329,7 +505,7 @@ class fields_base inserted. Names are not case-sensitive. Any leading or trailing whitespace in the new value is ignored. -
+ All iterators that are equal to `before` or come after are invalidated. @@ -345,10 +521,8 @@ class fields_base @par Exception Safety Strong guarantee. - Calls to allocate may throw. - @return An iterator the newly inserted header, or - an error if any occurred. + @return An iterator to the newly inserted header. @param before Position to insert before. @@ -356,22 +530,83 @@ class fields_base @param value A value, which must be semantically valid for the message. + + @throw boost::system::system_error if name or + value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ - system::result + iterator insert( iterator before, core::string_view name, core::string_view value) { - auto rv = insert_impl( - string_to_field( - name), + system::error_code ec; + insert(before, name, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); + return before; + } + + /** Insert a header + + If a matching header with the same name + exists, it is not replaced. Instead, an + additional header with the same name is + inserted. Names are not case-sensitive. + Any leading or trailing whitespace in + the new value is ignored. + + All iterators that are equal to `before` + or come after are invalidated. + + @par Example + @code + request req; + + req.insert( req.begin(), "User-Agent", "Boost" ); + @endcode + + @par Complexity + Linear in `name.size() + value.size()`. + + @par Exception Safety + Strong guarantee. + + @return An iterator to the newly inserted header. + + @param before Position to insert before. + + @param name The header name. + + @param value A value, which must be semantically + valid for the message. + + @param ec Set to the error, if name or value is + invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + iterator + insert( + iterator before, + core::string_view name, + core::string_view value, + system::error_code& ec) + { + insert_impl( + string_to_field(name), name, value, - before.i_); - - if( rv.has_error() ) - return rv.error(); + before.i_, + ec); return before; } @@ -469,20 +704,64 @@ class fields_base @par Exception Safety Strong guarantee. - Calls to allocate may throw. - @return The error, if any occurred. + @param it An iterator to the header. + + @param value A value, which must be semantically + valid for the message. + + @throw boost::system::system_error if value is + invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + BOOST_HTTP_PROTO_DECL + void + set( + iterator it, + core::string_view value) + { + system::error_code ec; + set(it, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Set a header value + + Uses the given value to overwrite the + current one in the header field pointed to by the + iterator. The value must be syntactically + valid or else an error is returned. + Any leading or trailing whitespace in the new value + is ignored. + + @par Complexity + + @par Exception Safety + Strong guarantee. @param it An iterator to the header. @param value A value, which must be semantically valid for the message. + + @param ec Set to the error, if value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ BOOST_HTTP_PROTO_DECL - system::result + void set( iterator it, - core::string_view value); + core::string_view value, + system::error_code& ec); /** Set a header value @@ -500,19 +779,67 @@ class fields_base @par Complexity - @return The error, if any occurred. + @param id The field constant of the + header to set. + + @param value A value, which must be semantically + valid for the message. + + @throw boost::system::system_error if value is + invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + BOOST_HTTP_PROTO_DECL + void + set( + field id, + core::string_view value) + { + system::error_code ec; + set(id, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Set a header value + + The container is modified to contain exactly + one field with the specified id set to the given value, + which must be syntactically valid or else an error is + returned. + Any leading or trailing whitespace in the new value + is ignored. + + @par Postconditions + @code + this->count( id ) == 1 && this->at( id ) == value + @endcode + + @par Complexity @param id The field constant of the header to set. @param value A value, which must be semantically valid for the message. + + @param ec Set to the error, if value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ BOOST_HTTP_PROTO_DECL - system::result + void set( field id, - core::string_view value); + core::string_view value, + system::error_code& ec); /** Set a header value @@ -528,18 +855,63 @@ class fields_base this->count( name ) == 1 && this->at( name ) == value @endcode - @return The error, if any occurred. + @param name The field name. + + @param value A value, which must be semantically + valid for the message. + + @throw boost::system::system_error if name or value is + invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. + */ + BOOST_HTTP_PROTO_DECL + void + set( + core::string_view name, + core::string_view value) + { + system::error_code ec; + set(name, value, ec); + if(ec.failed()) + detail::throw_system_error(ec); + } + + /** Set a header value + + The container is modified to contain exactly + one field with the specified name set to the given value, + which must be syntactically valid or else an error is + returned. + Any leading or trailing whitespace in the new value + is ignored. + + @par Postconditions + @code + this->count( name ) == 1 && this->at( name ) == value + @endcode @param name The field name. @param value A value, which must be semantically valid for the message. + + @param ec Set to the error, if name or value is invalid. + + @throw std::length_error if the required space + exceeds the maximum capacity. + + @throw std::bad_alloc if the allocation fails. */ BOOST_HTTP_PROTO_DECL - system::result + void set( core::string_view name, - core::string_view value); + core::string_view value, + system::error_code& ec); //-------------------------------------------- @@ -558,12 +930,13 @@ class fields_base bool has_obs_fold); BOOST_HTTP_PROTO_DECL - system::result + void insert_impl( field id, core::string_view name, core::string_view value, - std::size_t before); + std::size_t before, + system::error_code& ec); BOOST_HTTP_PROTO_DECL void diff --git a/src/fields_base.cpp b/src/fields_base.cpp index f55c0b33..0b356f8f 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -50,21 +50,18 @@ align_down( return 0; } -system::result +void verify_field_name( - core::string_view name) + core::string_view name, + system::error_code& ec) { - auto rv = - grammar::parse(name, detail::field_name_rule); - if( rv.has_error() ) + auto rv = grammar::parse( + name, detail::field_name_rule); + if(rv.has_error()) { - auto ec = rv.error(); - if( ec == urls::grammar::error::leftover ) - return error::bad_field_name; - if( ec == condition::need_more_input ) - return error::bad_field_name; + ec = BOOST_HTTP_PROTO_ERR( + error::bad_field_name); } - return rv; } system::result @@ -701,15 +698,19 @@ erase( //------------------------------------------------ -system::result +void fields_base:: set( iterator it, - core::string_view value) + core::string_view value, + system::error_code& ec) { auto rv = verify_field_value(value); - if( rv.has_error() ) - return rv.error(); + if(rv.has_error()) + { + ec = rv.error(); + return; + } value = rv->value; bool has_obs_fold = rv->has_obs_fold; @@ -818,23 +819,26 @@ set( e.id = id; h_.on_insert(id, it->value); } - return {}; } // erase existing fields with id // and then add the field with value -system::result +void fields_base:: set( field id, - core::string_view value) + core::string_view value, + system::error_code& ec) { BOOST_ASSERT( id != field::unknown); auto rv = verify_field_value(value); - if( rv.has_error() ) - return rv.error(); + if(rv.has_error()) + { + ec = rv.error(); + return; + } value = rv->value; bool has_obs_fold = rv->has_obs_fold; @@ -859,26 +863,27 @@ set( insert_impl_unchecked( id, to_string(id), value, h_.count, has_obs_fold); - return {}; } // erase existing fields with name // and then add the field with value -system::result +void fields_base:: set( core::string_view name, - core::string_view value) + core::string_view value, + system::error_code& ec) { - { - auto rv = verify_field_name(name); - if( rv.has_error() ) - return rv.error(); - } + verify_field_name(name , ec); + if(ec.failed()) + return; auto rv = verify_field_value(value); - if( rv.has_error() ) - return rv.error(); + if(rv.has_error()) + { + ec = rv.error(); + return; + } value = rv->value; bool has_obs_fold = rv->has_obs_fold; @@ -906,7 +911,6 @@ set( insert_impl_unchecked( string_to_field(name), name, value, h_.count, has_obs_fold); - return {}; } //------------------------------------------------ @@ -1059,27 +1063,28 @@ insert_impl_unchecked( h_.on_insert(id, value); } -system::result +void fields_base:: insert_impl( field id, core::string_view name, core::string_view value, - std::size_t before) + std::size_t before, + system::error_code& ec) { - { - auto rv = verify_field_name(name); - if( rv.has_error() ) - return rv.error(); - } + verify_field_name(name, ec); + if(ec.failed()) + return; auto rv = verify_field_value(value); - if( rv.has_error() ) - return rv.error(); + if(rv.has_error()) + { + ec = rv.error(); + return; + } insert_impl_unchecked( id, name, rv->value, before, rv->has_obs_fold); - return {}; } // erase i and update metadata diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index 75c581c9..fec5a564 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -407,6 +407,16 @@ struct fields_base_test "Server: y\r\n" "\r\n"); + check( + "\r\n", + [](fields_base& f) + { + BOOST_TEST_THROWS( + f.append(field::server, "bad\r\nvalue"), + system::system_error); + }, + "\r\n"); + // append(string_view, string_view) check( @@ -459,10 +469,8 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.append( + f.append( "!#$%&'*+-.^_`|~1A", "\r\n\t \r\n AB\r\n C \r\n\t"); - - BOOST_TEST(rv.has_value()); }, "!#$%&'*+-.^_`|~1A: AB C\r\n" "\r\n"); @@ -471,8 +479,7 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.append("A", "A\r\n\tB\r\n C"); - BOOST_TEST(rv.has_value()); + f.append("A", "A\r\n\tB\r\n C"); }, "A: A \tB C\r\n" "\r\n"); @@ -481,8 +488,7 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.append("A", "custom: rawr\r\n\tB\r\n C"); - BOOST_TEST(rv.has_value()); + f.append("A", "custom: rawr\r\n\tB\r\n C"); }, "A: custom: rawr \tB C\r\n" "\r\n"); @@ -491,8 +497,7 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.append("A", " \t \r\n \r\n\t \t \t \r\n "); - BOOST_TEST(rv.has_value()); + f.append("A", " \t \r\n \r\n\t \t \t \r\n "); }, "A:\r\n" "\r\n"); @@ -501,26 +506,28 @@ struct fields_base_test "\r\n", [](fields_base& f) { - system::result rv; + system::error_code ec; // ends with invalid obs-fold - rv = f.append("X", "AB\r\n C \r\n"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_value); + f.append("X", "AB\r\n C \r\n", ec); + BOOST_TEST(ec == error::bad_field_value); + BOOST_TEST_THROWS( + f.append("X", "AB\r\n C \r\n"), + system::system_error); // contains invalid obs-fold between {AB, C} - rv = f.append("X", "\r\n\x09 \r\n AB: rawr\r\nC"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + ec = {}; + f.append("X", "\r\n\x09 \r\n AB: rawr\r\nC", ec); + BOOST_TEST(ec == error::bad_field_smuggle); - rv = f.append("X", " \r"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_value); + ec = {}; + f.append("X", " \r", ec); + BOOST_TEST(ec == error::bad_field_value); // empty field name - rv = f.append("", "ABC"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_name); + ec = {}; + f.append("", "ABC", ec); + BOOST_TEST(ec == error::bad_field_name); std::vector strs = { "\r\nABC", "\rABC", "A\rBC", @@ -529,8 +536,9 @@ struct fields_base_test for (auto const str : strs) { - rv = f.append("X", str); - BOOST_TEST(rv.has_error()); + ec = {}; + f.append("X", str, ec); + BOOST_TEST(ec.failed()); } }); @@ -567,9 +575,17 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.insert(f.find("T"), field::server, "x"); - BOOST_TEST(rv.has_value()); - BOOST_TEST(rv.value() == f.find(field::server)); + { + system::error_code ec; + auto it = f.insert(f.find("T"), field::server, "x", ec); + BOOST_TEST(!ec.failed()); + BOOST_TEST(it == f.find(field::server)); + } + f.erase(field::server); + { + auto it = f.insert(f.find("T"), field::server, "x"); + BOOST_TEST(it == f.find(field::server)); + } }, "Server: x\r\n" "T: 1\r\n" @@ -581,12 +597,21 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto pos = f.find("T"); - auto rv = f.insert(f.find("U"), field::server, "x"); - - BOOST_TEST(rv.has_value()); - BOOST_TEST(rv.value() == f.find(field::server)); - BOOST_TEST(pos == f.find("T")); + { + system::error_code ec; + auto pos = f.find("T"); + auto it = f.insert(f.find("U"), field::server, "x", ec); + BOOST_TEST(!ec.failed()); + BOOST_TEST(it == f.find(field::server)); + BOOST_TEST(pos == f.find("T")); + } + f.erase(field::server); + { + auto pos = f.find("T"); + auto it = f.insert(f.find("U"), field::server, "x"); + BOOST_TEST(it == f.find(field::server)); + BOOST_TEST(pos == f.find("T")); + } }, "T: 1\r\n" "Server: x\r\n" @@ -599,8 +624,12 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.insert(f.find("U"), field::server, "a\r\nb"); - BOOST_TEST(rv.has_error()); + system::error_code ec; + f.insert(f.find("U"), field::server, "a\r\nb", ec); + BOOST_TEST(ec.failed()); + BOOST_TEST_THROWS( + f.insert(f.find("U"), field::server, "a\r\nb"), + system::system_error); }); // insert(iterator, string_view, string_view) @@ -610,9 +639,8 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.insert(f.find("T"), "Server", "x"); - BOOST_TEST(rv.has_value()); - BOOST_TEST(rv.value() == f.find("Server")); + auto it = f.insert(f.find("T"), "Server", "x"); + BOOST_TEST(it == f.find("Server")); }, "Server: x\r\n" "T: 1\r\n" @@ -625,10 +653,8 @@ struct fields_base_test [](fields_base& f) { auto pos = f.find("T"); - auto rv = f.insert(f.find("U"), "Server", "x"); - - BOOST_TEST(rv.has_value()); - BOOST_TEST(rv.value() == f.find("Server")); + auto it = f.insert(f.find("U"), "Server", "x"); + BOOST_TEST(it == f.find("Server")); BOOST_TEST(pos == f.find("T")); }, "T: 1\r\n" @@ -642,16 +668,21 @@ struct fields_base_test "\r\n", [](fields_base& f) { - system::result rv; + system::error_code ec; - rv = f.insert(f.find("U"), "Ser ver", "x"); - BOOST_TEST(rv.has_error()); + f.insert(f.find("U"), "Ser ver", "x", ec); + BOOST_TEST(ec == error::bad_field_name); + BOOST_TEST_THROWS( + f.insert(f.find("U"), "Ser ver", "x"), + system::system_error); - rv = f.insert(f.find("U"), " Server", "x"); - BOOST_TEST(rv.has_error()); + ec = {}; + f.insert(f.find("U"), " Server", "x", ec); + BOOST_TEST(ec == error::bad_field_name); - rv = f.insert(f.find("U"), "Server ", "x"); - BOOST_TEST(rv.has_error()); + ec = {}; + f.insert(f.find("U"), "Server ", "x", ec); + BOOST_TEST(ec == error::bad_field_name); }); // self-intersect @@ -897,8 +928,10 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.set(f.find("T"), "2"); - BOOST_TEST(rv.has_value()); + f.set(f.find("T"), "2"); + system::error_code ec; + f.set(f.find("T"), "2", ec); + BOOST_TEST(!ec.failed()); }, "T: 2\r\n" "\r\n"); @@ -938,13 +971,24 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.set(f.find("T"), "\r\n"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_value); - - rv = f.set(f.find("T"), "abcdefghijk\r\nlmnopqrstuvwxyz"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + system::error_code ec; + f.set(f.find("T"), "\r\n", ec); + BOOST_TEST_EQ(ec, error::bad_field_value); + BOOST_TEST_THROWS( + f.set(f.find("T"), "\r\n"), + system::system_error); + + ec = {}; + f.set( + f.find("T"), + "abcdefghijk\r\nlmnopqrstuvwxyz", + ec); + BOOST_TEST_EQ(ec, error::bad_field_smuggle); + BOOST_TEST_THROWS( + f.set( + f.find("T"), + "abcdefghijk\r\nlmnopqrstuvwxyz"), + system::system_error); }); // set(field, string_view) @@ -953,8 +997,10 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.set(field::server, "x"); - BOOST_TEST(rv.has_value()); + f.set(field::server, "x"); + system::error_code ec; + f.set(field::server, "x"); + BOOST_TEST(!ec.failed()); }, "Server: x\r\n" "\r\n"); @@ -1002,8 +1048,10 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.set(field::server, "\r\n x\r\n yz \r\n \r\n\t"); - BOOST_TEST(rv.has_value()); + f.set(field::server, "\r\n x\r\n yz \r\n \r\n\t"); + system::error_code ec; + f.set(field::server, "\r\n x\r\n yz \r\n \r\n\t", ec); + BOOST_TEST(!ec.failed()); }, "Server: x yz\r\n" "\r\n"); @@ -1013,13 +1061,19 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.set(field::server, "\r\n x\r\nyz \r\n \r\n\t"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + system::error_code ec; + f.set(field::server, "\r\n x\r\nyz \r\n \r\n\t", ec); + BOOST_TEST_EQ(ec, error::bad_field_smuggle); + BOOST_TEST_THROWS( + f.set(field::server, "\r\n x\r\nyz \r\n \r\n\t"), + system::system_error); - rv = f.set(field::server, "yz\r\n\x01\x02\x03"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + ec = {}; + f.set(field::server, "yz\r\n\x01\x02\x03", ec); + BOOST_TEST_EQ(ec, error::bad_field_smuggle); + BOOST_TEST_THROWS( + f.set(field::server, "yz\r\n\x01\x02\x03"), + system::system_error); }); // set(string_view, string_view) @@ -1099,21 +1153,33 @@ struct fields_base_test "\r\n", [](fields_base& f) { - auto rv = f.set(" invalid string", "valid string"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_name); + system::error_code ec; + f.set(" invalid string", "valid string", ec); + BOOST_TEST_EQ(ec, error::bad_field_name); + BOOST_TEST_THROWS( + f.set(" invalid string", "valid string"), + system::system_error); - rv = f.set("invalid\r\n string", "valid string"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_name); + ec = {}; + f.set("invalid\r\n string", "valid string", ec); + BOOST_TEST_EQ(ec, error::bad_field_name); + BOOST_TEST_THROWS( + f.set("invalid\r\n string", "valid string"), + system::system_error); - rv = f.set("valid", "\r\ninvalid string"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + ec = {}; + f.set("valid", "\r\ninvalid string", ec); + BOOST_TEST_EQ(ec, error::bad_field_smuggle); + BOOST_TEST_THROWS( + f.set("valid", "\r\ninvalid string"), + system::system_error); - rv = f.set("valid", "invalid\x01\x02\r\nstring"); - BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_value); + ec = {}; + f.set("valid", "invalid\x01\x02\r\nstring", ec); + BOOST_TEST_EQ(ec, error::bad_field_value); + BOOST_TEST_THROWS( + f.set("valid", "\r\ninvalid string"), + system::system_error); }); } From a6a43b41e746d17df43088767fbc7736f249dd5d Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Wed, 23 Jul 2025 06:43:00 +0000 Subject: [PATCH 4/7] feilds_base modifiers throw on precondition violation --- include/boost/http_proto/fields_base.hpp | 18 ++++++---- src/fields_base.cpp | 13 ++++--- test/unit/fields_base.cpp | 46 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/include/boost/http_proto/fields_base.hpp b/include/boost/http_proto/fields_base.hpp index de57a3d0..a4df387e 100644 --- a/include/boost/http_proto/fields_base.hpp +++ b/include/boost/http_proto/fields_base.hpp @@ -271,8 +271,10 @@ class fields_base core::string_view value, system::error_code& ec) { - // TODO: this should probably return an error - BOOST_ASSERT(id != field::unknown); + // Precondition violation + if(id == field::unknown) + detail::throw_logic_error(); + insert_impl( id, to_string(id), @@ -486,8 +488,10 @@ class fields_base core::string_view value, system::error_code& ec) { - // TODO: this should probably return an error - BOOST_ASSERT(id != field::unknown); + // Precondition violation + if(id == field::unknown) + detail::throw_logic_error(); + insert_impl( id, to_string(id), @@ -662,7 +666,7 @@ class fields_base */ BOOST_HTTP_PROTO_DECL std::size_t - erase(field id) noexcept; + erase(field id); /** Erase all matching fields @@ -821,8 +825,8 @@ class fields_base @par Complexity - @param id The field constant of the - header to set. + @param id The field name constant, + which may not be @ref field::unknown. @param value A value, which must be semantically valid for the message. diff --git a/src/fields_base.cpp b/src/fields_base.cpp index 0b356f8f..1bba82da 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -591,10 +591,12 @@ shrink_to_fit() noexcept std::size_t fields_base:: erase( - field id) noexcept + field id) { - BOOST_ASSERT( - id != field::unknown); + // Precondition violation + if(id == field::unknown) + detail::throw_logic_error(); + #if 1 auto const end_ = end(); auto it = find_last(end_, id); @@ -830,8 +832,9 @@ set( core::string_view value, system::error_code& ec) { - BOOST_ASSERT( - id != field::unknown); + // Precondition violation + if(id == field::unknown) + detail::throw_logic_error(); auto rv = verify_field_value(value); if(rv.has_error()) diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index fec5a564..bc10d6be 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -417,6 +417,17 @@ struct fields_base_test }, "\r\n"); + check( + "\r\n", + [](fields_base& f) + { + system::error_code ec; + BOOST_TEST_THROWS( + f.append(field::unknown, "y", ec), + std::logic_error); + }, + "\r\n"); + // append(string_view, string_view) check( @@ -591,6 +602,19 @@ struct fields_base_test "T: 1\r\n" "\r\n"); + check( + "T: 1\r\n" + "\r\n", + [](fields_base& f) + { + system::error_code ec; + BOOST_TEST_THROWS( + f.insert(f.find("T"), field::unknown, "x", ec), + std::logic_error); + }, + "T: 1\r\n" + "\r\n"); + check( "T: 1\r\n" "U: 2\r\n" @@ -916,6 +940,17 @@ struct fields_base_test "Connection: keep-alive\r\n" "Server: Boost\r\n" "\r\n"); + + // unknown field id + check( + "\r\n", + [](fields_base& f) + { + BOOST_TEST_THROWS( + f.erase(field::unknown), + std::logic_error); + }, + "\r\n"); } void @@ -1015,6 +1050,17 @@ struct fields_base_test "Server: y\r\n" "\r\n"); + check( + "\r\n", + [](fields_base& f) + { + system::error_code ec; + BOOST_TEST_THROWS( + f.set(field::unknown, "y" , ec), + std::logic_error); + }, + "\r\n"); + check( "T: 1\r\n" "Server: x\r\n" From 9b71c08dc18beeaa5dfbe027370626dc6170227c Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Wed, 23 Jul 2025 10:04:15 +0000 Subject: [PATCH 5/7] fields_base erases fields by name when id is field::unknown --- include/boost/http_proto/fields_base.hpp | 13 +- include/boost/http_proto/metadata.hpp | 4 +- src/fields_base.cpp | 200 ++++++++--------------- src/rfc/transfer_coding_rule.hpp | 1 + test/unit/Jamfile | 2 +- test/unit/fields_base.cpp | 14 ++ 6 files changed, 90 insertions(+), 144 deletions(-) diff --git a/include/boost/http_proto/fields_base.hpp b/include/boost/http_proto/fields_base.hpp index a4df387e..0267e39a 100644 --- a/include/boost/http_proto/fields_base.hpp +++ b/include/boost/http_proto/fields_base.hpp @@ -722,7 +722,6 @@ class fields_base @throw std::bad_alloc if the allocation fails. */ - BOOST_HTTP_PROTO_DECL void set( iterator it, @@ -797,7 +796,6 @@ class fields_base @throw std::bad_alloc if the allocation fails. */ - BOOST_HTTP_PROTO_DECL void set( field id, @@ -872,7 +870,6 @@ class fields_base @throw std::bad_alloc if the allocation fails. */ - BOOST_HTTP_PROTO_DECL void set( core::string_view name, @@ -926,7 +923,7 @@ class fields_base detail::header const&); void - insert_impl_unchecked( + insert_unchecked_impl( field id, core::string_view name, core::string_view value, @@ -948,7 +945,8 @@ class fields_base std::size_t i, field id) noexcept; - void raw_erase( + void + raw_erase( std::size_t) noexcept; std::size_t @@ -956,6 +954,11 @@ class fields_base std::size_t i0, field id) noexcept; + std::size_t + erase_all_impl( + std::size_t i0, + core::string_view name) noexcept; + std::size_t offset( std::size_t i) const noexcept; diff --git a/include/boost/http_proto/metadata.hpp b/include/boost/http_proto/metadata.hpp index 3d754532..bc5ba693 100644 --- a/include/boost/http_proto/metadata.hpp +++ b/include/boost/http_proto/metadata.hpp @@ -144,10 +144,10 @@ struct metadata content_encoding_t( system::error_code ec_, std::size_t count_, - encoding encoding_) noexcept + content_coding coding_) noexcept : ec(ec_) , count(count_) - , encoding(encoding_) + , coding(coding_) { } #endif diff --git a/src/fields_base.cpp b/src/fields_base.cpp index 1bba82da..80d12805 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -150,11 +150,6 @@ class fields_base:: std::size_t extra_char, std::size_t extra_field); - void - copy_prefix( - std::size_t n, - std::size_t i) noexcept; - void move_chars( char* dest, @@ -204,27 +199,6 @@ grow( self_.h_.count + extra_field)); } -void -fields_base:: -op_t:: -copy_prefix( - std::size_t n, - std::size_t i) noexcept -{ - // copy first n chars - std::memcpy( - self_.h_.buf, - cbuf_, - n); - // copy first i entries - if(i > 0) - std::memcpy( - self_.h_.tab_() - i, - reinterpret_cast( - buf_ + cap_) - i, - i * sizeof(entry)); -} - void fields_base:: op_t:: @@ -597,55 +571,10 @@ erase( if(id == field::unknown) detail::throw_logic_error(); -#if 1 - auto const end_ = end(); - auto it = find_last(end_, id); - if(it == end_) + auto const i0 = h_.find(id); + if(i0 == h_.count) return 0; - std::size_t n = 1; - auto const begin_ = begin(); - raw_erase(it.i_); - while(it != begin_) - { - --it; - if(it->id == id) - { - raw_erase(it.i_); - ++n; - } - } - h_.on_erase_all(id); - return n; -#else - std::size_t n = 0; - auto it0 = find(id); - auto const end_ = end(); - if(it0 != end_) - { - auto it1 = it0; - std::size_t total = 0; - std::size_t size = 0; - // [it0, it1) run of id - for(;;) - { - size += length(it1.i_); - ++it1; - if(it1 == end_) - goto finish; - if(it1->id != id) - break; - } - std::memmove( - h_.buf + offset(it0.i_), - h_.buf + offset(it1.i_), - h_.size - offset(it2.i_)); - - finish: - h_.size -= size; - h_.count -= n; - } - return n; -#endif + return erase_all_impl(i0, id); } std::size_t @@ -653,49 +582,14 @@ fields_base:: erase( core::string_view name) noexcept { - auto it0 = find(name); - auto const end_ = end(); - if(it0 == end_) + auto const i0 = h_.find(name); + if(i0 == h_.count) return 0; - auto it = end_; - std::size_t n = 1; - auto const id = it0->id; + auto const ft = h_.tab(); + auto const id = ft[i0].id; if(id == field::unknown) - { - // fix self-intersection - name = it0->name; - - for(;;) - { - --it; - if(it == it0) - break; - if(grammar::ci_is_equal( - it->name, name)) - { - raw_erase(it.i_); - ++n; - } - } - raw_erase(it.i_); - } - else - { - for(;;) - { - --it; - if(it == it0) - break; - if(it->id == id) - { - raw_erase(it.i_); - ++n; - } - } - raw_erase(it.i_); - h_.on_erase_all(id); - } - return n; + return erase_all_impl(i0, name); + return erase_all_impl(i0, id); } //------------------------------------------------ @@ -843,9 +737,6 @@ set( return; } - value = rv->value; - bool has_obs_fold = rv->has_obs_fold; - auto const i0 = h_.find(id); if(i0 != h_.count) { @@ -857,15 +748,19 @@ set( h_.size - length(i0); auto const n = ft[i0].nn + 2 + - value.size() + 2; + rv->value.size() + 2; // VFALCO missing overflow check reserve_bytes(n0 + n); } erase_all_impl(i0, id); } - insert_impl_unchecked( - id, to_string(id), value, h_.count, has_obs_fold); + insert_unchecked_impl( + id, + to_string(id), + rv->value, + h_.count, + rv->has_obs_fold); } // erase existing fields with name @@ -888,9 +783,6 @@ set( return; } - value = rv->value; - bool has_obs_fold = rv->has_obs_fold; - auto const i0 = h_.find(name); if(i0 != h_.count) { @@ -903,17 +795,23 @@ set( h_.size - length(i0); auto const n = ft[i0].nn + 2 + - value.size() + 2; + rv->value.size() + 2; // VFALCO missing overflow check reserve_bytes(n0 + n); } // VFALCO simple algorithm but // costs one extra memmove - erase_all_impl(i0, id); + if(id != field::unknown) + erase_all_impl(i0, id); + else + erase_all_impl(i0, name); } - insert_impl_unchecked( + insert_unchecked_impl( string_to_field(name), - name, value, h_.count, has_obs_fold); + name, + rv->value, + h_.count, + rv->has_obs_fold); } //------------------------------------------------ @@ -966,7 +864,7 @@ copy_impl( void fields_base:: -insert_impl_unchecked( +insert_unchecked_impl( field id, core::string_view name, core::string_view value, @@ -1086,8 +984,12 @@ insert_impl( return; } - insert_impl_unchecked( - id, name, rv->value, before, rv->has_obs_fold); + insert_unchecked_impl( + id, + name, + rv->value, + before, + rv->has_obs_fold); } // erase i and update metadata @@ -1098,8 +1000,7 @@ erase_impl( field id) noexcept { raw_erase(i); - if(id != field::unknown) - h_.on_erase(id); + h_.on_erase(id); } //------------------------------------------------ @@ -1157,6 +1058,35 @@ erase_all_impl( return n; } +// erase all fields with name +// when id == field::unknown +std::size_t +fields_base:: +erase_all_impl( + std::size_t i0, + core::string_view name) noexcept +{ + std::size_t n = 1; + std::size_t i = h_.count - 1; + auto const ft = h_.tab(); + auto const* p = h_.cbuf + h_.prefix; + while(i > i0) + { + core::string_view s( + p + ft[i].np, ft[i].nn); + if(s == name) + { + raw_erase(i); + ++n; + } + // go backwards to + // reduce memmoves + --i; + } + raw_erase(i0); + return n; +} + // return i-th field absolute offset std::size_t fields_base:: @@ -1166,10 +1096,8 @@ offset( if(i == 0) return h_.prefix; if(i < h_.count) - return h_.prefix + - h_.tab_()[0-(static_cast(i) + 1)].np; + return h_.prefix + h_.tab()[i].np; // make final CRLF the last "field" - //BOOST_ASSERT(i == h_.count); return h_.size - 2; } diff --git a/src/rfc/transfer_coding_rule.hpp b/src/rfc/transfer_coding_rule.hpp index 6912e83e..f3960175 100644 --- a/src/rfc/transfer_coding_rule.hpp +++ b/src/rfc/transfer_coding_rule.hpp @@ -75,6 +75,7 @@ struct transfer_coding_rule_t } extension; }; + BOOST_HTTP_PROTO_DECL auto parse( char const*& it, diff --git a/test/unit/Jamfile b/test/unit/Jamfile index 5fe55142..ad3d9c43 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -70,7 +70,7 @@ local SOURCES = rfc/quoted_token_rule.cpp rfc/quoted_token_view.cpp rfc/token_rule.cpp - rfc/transfer_encoding_rule.cpp + rfc/transfer_coding_rule.cpp rfc/detail/rules.cpp ; diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index bc10d6be..3a9e50b7 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -1143,6 +1143,20 @@ struct fields_base_test "Server: y\r\n" "\r\n"); + check( + "UnkownId0: w\r\n" + "UnkownId1: x\r\n" + "UnkownId2: y\r\n" + "\r\n", + [](fields_base& f) + { + f.set("UnkownId1", "z"); + }, + "UnkownId0: w\r\n" + "UnkownId2: y\r\n" + "UnkownId1: z\r\n" + "\r\n"); + check( "T: 1\r\n" "Server: xx\r\n" From 3ad33779c4bdd138c0d5f2cc051d8e6d12453a42 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Wed, 23 Jul 2025 16:36:10 +0000 Subject: [PATCH 6/7] fields_view_base has at() observer method --- include/boost/http_proto/fields_view_base.hpp | 30 +++++++++++++++++++ src/fields_view_base.cpp | 24 +++++++++++++++ test/unit/fields_view_base.cpp | 12 ++++++++ 3 files changed, 66 insertions(+) diff --git a/include/boost/http_proto/fields_view_base.hpp b/include/boost/http_proto/fields_view_base.hpp index 8c402a2f..d424af89 100644 --- a/include/boost/http_proto/fields_view_base.hpp +++ b/include/boost/http_proto/fields_view_base.hpp @@ -208,6 +208,36 @@ class fields_view_base return ph_->count; } + /** Return the value of a field, or throws an exception. + + If more than one field with the specified name exists, + the first field defined by insertion order is returned. + + @param id The field name constant. + + @return The field value. + + @throw std::out_of_range if the field is not found. + */ + BOOST_HTTP_PROTO_DECL + core::string_view + at(field id) const; + + /** Return the value of a field, or throws an exception. + + If more than one field with the specified name exists, + the first field defined by insertion order is returned. + + @param name The field name. + + @return The field value. + + @throw std::out_of_range if the field is not found. + */ + BOOST_HTTP_PROTO_DECL + core::string_view + at(core::string_view name) const; + /** Return true if a field exists */ BOOST_HTTP_PROTO_DECL diff --git a/src/fields_view_base.cpp b/src/fields_view_base.cpp index e0c9bcf3..70e346ad 100644 --- a/src/fields_view_base.cpp +++ b/src/fields_view_base.cpp @@ -163,6 +163,30 @@ operator++() noexcept -> // //------------------------------------------------ +core::string_view +fields_view_base:: +at( + field id) const +{ + auto const it = find(id); + if(it == end()) + BOOST_THROW_EXCEPTION( + std::out_of_range{ "field not found" }); + return it->value; +} + +core::string_view +fields_view_base:: +at( + core::string_view name) const +{ + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION( + std::out_of_range{ "field not found" }); + return it->value; +} + bool fields_view_base:: exists( diff --git a/test/unit/fields_view_base.cpp b/test/unit/fields_view_base.cpp index 44748e48..5a2f44b1 100644 --- a/test/unit/fields_view_base.cpp +++ b/test/unit/fields_view_base.cpp @@ -172,6 +172,18 @@ struct fields_view_base_test BOOST_TEST(f.size() == 10); + // at(field) + // at(string_view) + + BOOST_TEST(f.at("x") == "1"); + BOOST_TEST(f.at(field::set_cookie) == "a"); + BOOST_TEST_THROWS( + f.at("accept"), + std::out_of_range); + BOOST_TEST_THROWS( + f.at(field::accept), + std::out_of_range); + // exists(field) // exists(string_view) From 17f5bb3429350907c4a54322e16439b4c8c8985c Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Thu, 24 Jul 2025 11:15:03 +0000 Subject: [PATCH 7/7] field does not have `unkown` value --- include/boost/http_proto/detail/header.hpp | 5 +- include/boost/http_proto/field.hpp | 8 +-- include/boost/http_proto/fields_base.hpp | 43 +++++--------- include/boost/http_proto/fields_view_base.hpp | 7 +-- src/detail/header.cpp | 7 ++- src/field.cpp | 9 +-- src/fields_base.cpp | 56 +++++++++---------- src/fields_view_base.cpp | 10 ++-- test/unit/field.cpp | 3 +- test/unit/fields_base.cpp | 50 ++--------------- test/unit/fields_view_base.cpp | 21 +++---- test/unit/request.cpp | 2 +- test/unit/response.cpp | 1 + test/unit/static_response.cpp | 1 + 14 files changed, 86 insertions(+), 137 deletions(-) diff --git a/include/boost/http_proto/detail/header.hpp b/include/boost/http_proto/detail/header.hpp index f9de1c79..7c54bb63 100644 --- a/include/boost/http_proto/detail/header.hpp +++ b/include/boost/http_proto/detail/header.hpp @@ -63,6 +63,10 @@ struct header std::numeric_limits< offset_type>::max(); + static constexpr + field unknown_field = + static_cast(0); + struct entry { offset_type np; // name pos @@ -212,7 +216,6 @@ struct header static std::size_t count_crlf(core::string_view s) noexcept; - BOOST_HTTP_PROTO_DECL void parse( std::size_t, header_limits const&, diff --git a/include/boost/http_proto/field.hpp b/include/boost/http_proto/field.hpp index db2d9ca0..ccb70012 100644 --- a/include/boost/http_proto/field.hpp +++ b/include/boost/http_proto/field.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -12,6 +13,7 @@ #include #include +#include #include #include #include @@ -21,9 +23,7 @@ namespace http_proto { enum class field : unsigned short { - unknown = 0, // must be zero - - a_im, + a_im = 1, accept, accept_additions, accept_charset, @@ -399,7 +399,7 @@ to_string(field f); @ref field::unknown if there is no match. */ BOOST_HTTP_PROTO_DECL -field +boost::optional string_to_field( core::string_view s) noexcept; diff --git a/include/boost/http_proto/fields_base.hpp b/include/boost/http_proto/fields_base.hpp index 0267e39a..daec3f8c 100644 --- a/include/boost/http_proto/fields_base.hpp +++ b/include/boost/http_proto/fields_base.hpp @@ -203,8 +203,7 @@ class fields_base @par Exception Safety Strong guarantee. - @param id The field name constant, - which may not be @ref field::unknown. + @param id The field name constant. @param value A value, which must be semantically valid for the message. @@ -252,8 +251,7 @@ class fields_base @par Exception Safety Strong guarantee. - @param id The field name constant, - which may not be @ref field::unknown. + @param id The field name constant. @param value A value, which must be semantically valid for the message. @@ -271,10 +269,6 @@ class fields_base core::string_view value, system::error_code& ec) { - // Precondition violation - if(id == field::unknown) - detail::throw_logic_error(); - insert_impl( id, to_string(id), @@ -411,8 +405,7 @@ class fields_base @param before Position to insert before. - @param id The field name constant, - which may not be @ref field::unknown. + @param id The field name constant. @param value A value, which must be semantically valid for the message. @@ -468,8 +461,7 @@ class fields_base @param before Position to insert before. - @param id The field name constant, - which may not be @ref field::unknown. + @param id The field name constant. @param value A value, which must be semantically valid for the message. @@ -488,10 +480,6 @@ class fields_base core::string_view value, system::error_code& ec) { - // Precondition violation - if(id == field::unknown) - detail::throw_logic_error(); - insert_impl( id, to_string(id), @@ -630,18 +618,15 @@ class fields_base @par Exception Safety Throws nothing. - @return An iterator to the inserted - element. + @return An iterator to one past the + removed element. @param it An iterator to the element to erase. */ + BOOST_HTTP_PROTO_DECL iterator - erase(iterator it) noexcept - { - erase_impl(it.i_, it->id); - return it; - } + erase(iterator it) noexcept; /** Erase headers @@ -661,12 +646,11 @@ class fields_base @return The number of headers erased. - @param id The field name constant, - which may not be @ref field::unknown. + @param id The field name constant. */ BOOST_HTTP_PROTO_DECL std::size_t - erase(field id); + erase(field id) noexcept; /** Erase all matching fields @@ -823,8 +807,7 @@ class fields_base @par Complexity - @param id The field name constant, - which may not be @ref field::unknown. + @param id The field name constant. @param value A value, which must be semantically valid for the message. @@ -924,7 +907,7 @@ class fields_base void insert_unchecked_impl( - field id, + optional id, core::string_view name, core::string_view value, std::size_t before, @@ -933,7 +916,7 @@ class fields_base BOOST_HTTP_PROTO_DECL void insert_impl( - field id, + optional id, core::string_view name, core::string_view value, std::size_t before, diff --git a/include/boost/http_proto/fields_view_base.hpp b/include/boost/http_proto/fields_view_base.hpp index d424af89..1080ff6f 100644 --- a/include/boost/http_proto/fields_view_base.hpp +++ b/include/boost/http_proto/fields_view_base.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,8 +16,6 @@ #include #include #include -#include -#include #include namespace boost { @@ -74,7 +73,7 @@ class fields_view_base /**@{*/ struct reference { - field const id; + boost::optional const id; core::string_view const name; core::string_view const value; @@ -98,7 +97,7 @@ class fields_view_base */ struct value_type { - field id; + boost::optional id; std::string name; std::string value; diff --git a/src/detail/header.cpp b/src/detail/header.cpp index be53b743..bb943faf 100644 --- a/src/detail/header.cpp +++ b/src/detail/header.cpp @@ -71,6 +71,10 @@ operator-( //------------------------------------------------ +constexpr field header::unknown_field; + +//------------------------------------------------ + constexpr header:: header(fields_tag) noexcept @@ -1250,7 +1254,8 @@ parse_field( BOOST_ASSERT(h.buf != nullptr); remove_obs_fold(h.buf + h.size, it); } - auto id = string_to_field(rv->name); + auto id = string_to_field(rv->name) + .value_or(header::unknown_field); h.size = static_cast(it - h.cbuf); // add field table entry diff --git a/src/field.cpp b/src/field.cpp index 77182201..0abb8ebd 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -493,7 +494,7 @@ struct field_table } } - field + optional string_to_field( core::string_view s) const noexcept { @@ -505,13 +506,13 @@ struct field_table return static_cast(i); i = map_[j][1]; if(i == 0) - return field::unknown; + return boost::none; i += 255; s2 = by_name_[i]; if(equals(s, s2)) return static_cast(i); - return field::unknown; + return boost::none; } // @@ -558,7 +559,7 @@ to_string(field f) return v.begin()[static_cast(f)]; } -field +boost::optional string_to_field( core::string_view s) noexcept { diff --git a/src/fields_base.cpp b/src/fields_base.cpp index 80d12805..f97a25a3 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -562,15 +562,23 @@ shrink_to_fit() noexcept // //------------------------------------------------ -std::size_t +auto fields_base:: erase( - field id) + iterator it) noexcept -> iterator { - // Precondition violation - if(id == field::unknown) - detail::throw_logic_error(); + auto const id = it->id.value_or( + detail::header::unknown_field); + raw_erase(it.i_); + h_.on_erase(id); + return it; +} +std::size_t +fields_base:: +erase( + field id) noexcept +{ auto const i0 = h_.find(id); if(i0 == h_.count) return 0; @@ -587,7 +595,7 @@ erase( return 0; auto const ft = h_.tab(); auto const id = ft[i0].id; - if(id == field::unknown) + if(id == detail::header::unknown_field) return erase_all_impl(i0, name); return erase_all_impl(i0, id); } @@ -701,14 +709,15 @@ set( h_.size = static_cast< offset_type>(h_.size + dn); } - auto const id = it->id; + auto const id = it->id.value_or( + detail::header::unknown_field); if(h_.is_special(id)) { // replace first char of name // with null to hide metadata char saved = h_.buf[pos0]; auto& e = h_.tab()[i]; - e.id = field::unknown; + e.id = detail::header::unknown_field; h_.buf[pos0] = '\0'; h_.on_erase(id); h_.buf[pos0] = saved; // restore @@ -726,10 +735,6 @@ set( core::string_view value, system::error_code& ec) { - // Precondition violation - if(id == field::unknown) - detail::throw_logic_error(); - auto rv = verify_field_value(value); if(rv.has_error()) { @@ -801,7 +806,7 @@ set( } // VFALCO simple algorithm but // costs one extra memmove - if(id != field::unknown) + if(id != detail::header::unknown_field) erase_all_impl(i0, id); else erase_all_impl(i0, name); @@ -865,7 +870,7 @@ copy_impl( void fields_base:: insert_unchecked_impl( - field id, + optional id, core::string_view name, core::string_view value, std::size_t before, @@ -954,20 +959,20 @@ insert_unchecked_impl( ! value.empty()); e.vn = static_cast< offset_type>(value.size()); - e.id = id; + e.id = id.value_or( + detail::header::unknown_field); // update container h_.count++; h_.size = static_cast< offset_type>(h_.size + n); - if( id != field::unknown) - h_.on_insert(id, value); + h_.on_insert(e.id, value); } void fields_base:: insert_impl( - field id, + optional id, core::string_view name, core::string_view value, std::size_t before, @@ -992,17 +997,6 @@ insert_impl( rv->has_obs_fold); } -// erase i and update metadata -void -fields_base:: -erase_impl( - std::size_t i, - field id) noexcept -{ - raw_erase(i); - h_.on_erase(id); -} - //------------------------------------------------ void @@ -1038,7 +1032,7 @@ erase_all_impl( field id) noexcept { BOOST_ASSERT( - id != field::unknown); + id != detail::header::unknown_field); std::size_t n = 1; std::size_t i = h_.count - 1; auto const ft = h_.tab(); @@ -1059,7 +1053,7 @@ erase_all_impl( } // erase all fields with name -// when id == field::unknown +// when id == detail::header::unknown_field std::size_t fields_base:: erase_all_impl( diff --git a/src/fields_view_base.cpp b/src/fields_view_base.cpp index 70e346ad..bd231bd1 100644 --- a/src/fields_view_base.cpp +++ b/src/fields_view_base.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,7 +15,6 @@ #include #include #include -#include namespace boost { namespace http_proto { @@ -45,7 +45,8 @@ operator*() const noexcept -> auto const* p = ph_->cbuf + ph_->prefix; return { - e.id, + (e.id == detail::header::unknown_field) + ? optional{} : e.id, core::string_view( p + e.np, e.nn), core::string_view( @@ -68,7 +69,8 @@ operator*() const noexcept -> auto const* p = ph_->cbuf + ph_->prefix; return { - e.id, + (e.id == detail::header::unknown_field) + ? optional{} : e.id, core::string_view( p + e.np, e.nn), core::string_view( @@ -126,7 +128,7 @@ operator++() noexcept -> BOOST_ASSERT(i_ < ph_->count); auto const* e = &ph_->tab()[i_]; auto const id = e->id; - if(id != field::unknown) + if(id != detail::header::unknown_field) { ++i_; --e; diff --git a/test/unit/field.cpp b/test/unit/field.cpp index 8706b031..fb4e5cf7 100644 --- a/test/unit/field.cpp +++ b/test/unit/field.cpp @@ -396,7 +396,8 @@ class field_test auto const unknown = [&](core::string_view s) { - BOOST_TEST(string_to_field(s) == field::unknown); + BOOST_TEST( + !string_to_field(s).has_value()); }; unknown(""); unknown("x"); diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index 3a9e50b7..0c9d2875 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -417,17 +417,6 @@ struct fields_base_test }, "\r\n"); - check( - "\r\n", - [](fields_base& f) - { - system::error_code ec; - BOOST_TEST_THROWS( - f.append(field::unknown, "y", ec), - std::logic_error); - }, - "\r\n"); - // append(string_view, string_view) check( @@ -602,19 +591,6 @@ struct fields_base_test "T: 1\r\n" "\r\n"); - check( - "T: 1\r\n" - "\r\n", - [](fields_base& f) - { - system::error_code ec; - BOOST_TEST_THROWS( - f.insert(f.find("T"), field::unknown, "x", ec), - std::logic_error); - }, - "T: 1\r\n" - "\r\n"); - check( "T: 1\r\n" "U: 2\r\n" @@ -901,6 +877,7 @@ struct fields_base_test "T: 2\r\n" "Connection: close\r\n" "T: 3\r\n" + "U: 4\r\n" "\r\n", [](fields_base& f) { @@ -909,6 +886,7 @@ struct fields_base_test }, "Server: Boost\r\n" "Connection: close\r\n" + "U: 4\r\n" "\r\n"); // no matches @@ -931,6 +909,7 @@ struct fields_base_test "X: 1\r\n" "Server: Boost\r\n" "X: 2\r\n" + "Y: 3\r\n" "\r\n", [](fields_base& f) { @@ -939,17 +918,7 @@ struct fields_base_test }, "Connection: keep-alive\r\n" "Server: Boost\r\n" - "\r\n"); - - // unknown field id - check( - "\r\n", - [](fields_base& f) - { - BOOST_TEST_THROWS( - f.erase(field::unknown), - std::logic_error); - }, + "Y: 3\r\n" "\r\n"); } @@ -1050,17 +1019,6 @@ struct fields_base_test "Server: y\r\n" "\r\n"); - check( - "\r\n", - [](fields_base& f) - { - system::error_code ec; - BOOST_TEST_THROWS( - f.set(field::unknown, "y" , ec), - std::logic_error); - }, - "\r\n"); - check( "T: 1\r\n" "Server: x\r\n" diff --git a/test/unit/fields_view_base.cpp b/test/unit/fields_view_base.cpp index 5a2f44b1..0e4b1584 100644 --- a/test/unit/fields_view_base.cpp +++ b/test/unit/fields_view_base.cpp @@ -9,6 +9,7 @@ // Test that header file is self-contained. #include +#include #include #include @@ -44,7 +45,7 @@ struct fields_view_base_test BOOST_TEST(it == f.begin()); BOOST_TEST(it != f.end()); - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "x"); BOOST_TEST_EQ(it->value, "1"); @@ -59,7 +60,7 @@ struct fields_view_base_test BOOST_TEST_EQ(it0->name, "Accept"); BOOST_TEST_EQ(it0->value, "2"); } - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "z"); BOOST_TEST_EQ(it->value, "3"); @@ -67,13 +68,13 @@ struct fields_view_base_test BOOST_TEST_EQ(it, f.end()); --it; - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "z"); BOOST_TEST_EQ(it->value, "3"); { auto it1 = it--; // post-decrement - BOOST_TEST_EQ(it1->id, field::unknown); + BOOST_TEST(!it1->id.has_value()); BOOST_TEST_EQ(it1->name, "z"); BOOST_TEST_EQ(it1->value, "3"); } @@ -82,7 +83,7 @@ struct fields_view_base_test BOOST_TEST_EQ(it->value, "2"); --it; - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "x"); BOOST_TEST_EQ(it->value, "1"); @@ -105,7 +106,7 @@ struct fields_view_base_test BOOST_TEST(it == f.rbegin()); BOOST_TEST(it != f.rend()); - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "z"); BOOST_TEST_EQ(it->value, "3"); @@ -120,7 +121,7 @@ struct fields_view_base_test BOOST_TEST_EQ(it0->name, "Accept"); BOOST_TEST_EQ(it0->value, "2"); } - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "x"); BOOST_TEST_EQ(it->value, "1"); @@ -128,13 +129,13 @@ struct fields_view_base_test BOOST_TEST_EQ(it, f.rend()); --it; - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "x"); BOOST_TEST_EQ(it->value, "1"); { auto it0 = it--; // post-decrement - BOOST_TEST_EQ(it0->id, field::unknown); + BOOST_TEST(!it0->id.has_value()); BOOST_TEST_EQ(it0->name, "x"); BOOST_TEST_EQ(it0->value, "1"); } @@ -143,7 +144,7 @@ struct fields_view_base_test BOOST_TEST_EQ(it->value, "2"); --it; - BOOST_TEST_EQ(it->id, field::unknown); + BOOST_TEST(!it->id.has_value()); BOOST_TEST_EQ(it->name, "z"); BOOST_TEST_EQ(it->value, "3"); diff --git a/test/unit/request.cpp b/test/unit/request.cpp index 7099756e..e64b7a5b 100644 --- a/test/unit/request.cpp +++ b/test/unit/request.cpp @@ -11,11 +11,11 @@ // Test that header file is self-contained. #include +#include #include #include -#include "boost/http_proto/message_base.hpp" #include "test_suite.hpp" namespace boost { diff --git a/test/unit/response.cpp b/test/unit/response.cpp index 13b6a585..dfd5c550 100644 --- a/test/unit/response.cpp +++ b/test/unit/response.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "test_suite.hpp" diff --git a/test/unit/static_response.cpp b/test/unit/static_response.cpp index 083b1f69..8dd3be6e 100644 --- a/test/unit/static_response.cpp +++ b/test/unit/static_response.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "test_suite.hpp"