diff --git a/src/field.cpp b/src/field.cpp index 77182201..59d369ac 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -109,7 +109,7 @@ struct field_table /* From: - + https://www.iana.org/assignments/message-headers/message-headers.xhtml */ field_table() @@ -519,7 +519,7 @@ struct field_table // using const_iterator = - array_type::const_iterator; + array_type::const_iterator; std::size_t size() const diff --git a/src/fields_base.cpp b/src/fields_base.cpp index 0c85d7f2..e067e951 100644 --- a/src/fields_base.cpp +++ b/src/fields_base.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include +#include #include #include @@ -477,7 +479,11 @@ set( auto rv = grammar::parse( value, detail::field_value); if( rv.has_error() ) + { + if( rv.error() == urls::grammar::error::leftover ) + return error::bad_field_value; return rv.error(); + } auto val = rv.value(); value = val.value; @@ -601,7 +607,11 @@ set( auto rv = grammar::parse( value, detail::field_value); if( rv.has_error() ) + { + if( rv.error() == urls::grammar::error::leftover ) + return error::bad_field_value; return rv.error(); + } auto val = rv.value(); value = val.value; @@ -637,15 +647,25 @@ set( core::string_view name, core::string_view value) { - auto rv1 = grammar::parse( - name, detail::field_name); - if( rv1.has_error() ) - return rv1.error(); + { + auto rv = grammar::parse( + name, detail::field_name); + if( rv.has_error() ) + { + if( rv.error() == urls::grammar::error::leftover ) + return error::bad_field_name; + return rv.error(); + } + } auto rv = grammar::parse( value, detail::field_value); if( rv.has_error() ) + { + if( rv.error() == urls::grammar::error::leftover ) + return error::bad_field_value; return rv.error(); + } auto val = rv.value(); value = val.value; @@ -821,17 +841,29 @@ insert_impl( core::string_view value, std::size_t before) { - auto rv1 = grammar::parse( - name, detail::field_name); - if( rv1.has_error() ) - return rv1.error(); + { + auto rv = grammar::parse( + name, detail::field_name); + if( rv.has_error() ) + { + if( rv.error() == urls::grammar::error::leftover ) + return error::bad_field_name; + return rv.error(); + } + } - auto rv2 = grammar::parse( + auto rv = grammar::parse( value, detail::field_value); - if( rv2.has_error() ) - return rv2.error(); + if( rv.has_error() ) + { + if( rv.error() == urls::grammar::error::leftover) + return error::bad_field_value; + if( rv.error() == condition::need_more_input ) + return error::bad_field_value; + return rv.error(); + } - auto val = rv2.value(); + auto val = rv.value(); insert_impl_unchecked( id, name, val.value, before, val.has_obs_fold); diff --git a/src/rfc/detail/rules.cpp b/src/rfc/detail/rules.cpp index 52902fc4..d4108864 100644 --- a/src/rfc/detail/rules.cpp +++ b/src/rfc/detail/rules.cpp @@ -7,15 +7,19 @@ // Official repository: https://github.com/cppalliance/http_proto // -#include -#include #include + +#include +#include #include #include +#include #include #include #include +#include +#include namespace boost { namespace http_proto { @@ -35,7 +39,7 @@ struct ws_field_vchar_t } }; -constexpr ws_field_vchar_t ws_field_vchar{}; +// constexpr ws_field_vchar_t ws_field_vchar{}; auto crlf_rule_t:: @@ -201,23 +205,30 @@ parse( auto field_name_t:: parse( - char const *&it, - char const *end) const noexcept -> + char const*& it, + char const* end) const noexcept -> system::result { if( it == end ) - BOOST_HTTP_PROTO_RETURN_EC(grammar::error::need_more); + BOOST_HTTP_PROTO_RETURN_EC( + grammar::error::need_more); value_type v; - auto s0 = it; - while( it < end ) + + auto begin = it; + auto rv = grammar::parse( + it, end, token_rule); + if( rv.has_error() || (it != end) ) { - auto ch = *it; - if (! tchars(ch) ) - BOOST_HTTP_PROTO_RETURN_EC(error::bad_field_name); - ++it; + if( it != begin ) + { + v.name = core::string_view(begin, it - begin); + return v; + } + return error::bad_field_name; } - v.name = core::string_view(s0, it - end); + + v.name = core::string_view(begin, end - begin); return v; } @@ -265,23 +276,39 @@ parse( { // too short to contain valid obs-fold sequence if( end - it < 3 ) + { BOOST_HTTP_PROTO_RETURN_EC( grammar::error::need_more); + } if( it[1] != '\n' ) + { + if( s0 ) goto done; BOOST_HTTP_PROTO_RETURN_EC( http_proto::error::bad_field_value); + } if(! ws(it[2]) ) { + if( s0 ) goto done; + + if( !s0 ) + { + s0 = it; + s1 = s0; + goto done; + } + // only report this to the user when we can reasonably // determine that a header was smuggled inside our `value` // which happens when we see `crlf tchar` as // `field-name = 1*tchar` if( tchars(it[2]) ) + { BOOST_HTTP_PROTO_RETURN_EC( http_proto::error::bad_field_smuggle); + } BOOST_HTTP_PROTO_RETURN_EC( http_proto::error::bad_field_value); @@ -292,8 +319,12 @@ parse( } if(! is_field_vchar(ch) ) + { + if( s0 ) goto done; + BOOST_HTTP_PROTO_RETURN_EC( http_proto::error::bad_field_value); + } if(! s0 ) s0 = it; @@ -302,6 +333,7 @@ parse( s1 = it; } +done: v.value = core::string_view(s0, s1 - s0); return v; } @@ -339,121 +371,21 @@ parse( } value_type v; + auto rv = grammar::parse( + it, end, grammar::tuple_rule( + field_name, + grammar::delim_rule(':'), + field_value, + crlf_rule)); - // field name - { - auto rv = grammar::parse( - it, end, grammar::tuple_rule( - token_rule, - grammar::squelch( - grammar::delim_rule(':')))); - if(! rv) - return rv.error(); - v.name = rv.value(); - } + if( rv.has_error() ) + return rv.error(); - // consume all obs-fold until field char or end of field: - // - // HTTP-message = start-line *( header-field CRLF ) CRLF [ message-body ] - // header-field = field-name ":" OWS field-value OWS - // field-value = *( field-content / obs-fold ) - // field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - // obs-fold = CRLF 1*( SP / HTAB ) - // - for(;;) - { - skip_ows(it, end); - if(it == end) - { - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::need_more); - } - if(*it != '\r') - { - // start of value - break; - } - ++it; - if(it == end) - { - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::need_more); - } - if(*it != '\n') - { - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::mismatch); - } - ++it; - if(it == end) - { - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::need_more); - } - // FIXME: this should be a loop of some kind as we've detected a valid - // CRLF at this stage but need to account for the ABNF specifying: - // obs-fold = CRLF 1*( SP / HTAB ) - if(*it != ' ' && - *it != '\t') - { - // because we saw a CRLF and didn't see the required SP / HTAB, - // we know we have a zero length field value - v.value = core::string_view(it, 0); - return v; - } - // eat obs-fold - ++it; - v.has_obs_fold = true; - } + auto val = rv.value(); + v.name = std::get<0>(val).name; + v.value = std::get<2>(val).value; + v.has_obs_fold = std::get<2>(val).has_obs_fold; - char const* s0 = it; // start of value - for(;;) - { - auto rv = grammar::parse( - it, end, grammar::tuple_rule( - grammar::token_rule( - ws_field_vchar), - crlf_rule)); - if(! rv) - return rv.error(); - if(it == end) - { - BOOST_HTTP_PROTO_RETURN_EC( - grammar::error::need_more); - } - if( *it != ' ' && - *it != '\t') - { - // end of field - break; - } - // *it will match field_value_rule - v.has_obs_fold = true; - } - - v.value = core::string_view(s0, (it - s0) - 2); - BOOST_ASSERT(! v.value.empty()); - //BOOST_ASSERT(! ws(t.v.value.front())); - - // remove trailing SP,HTAB,CR,LF - auto p = &v.value.back(); - for(;;) - { - switch(*p) - { - case ' ': case '\t': - case '\r': case '\n': - --p; - continue; - default: - ++p; - goto done; - } - } -done: - v.value = core::string_view( - v.value.data(), - p - v.value.data()); return v; } diff --git a/test/unit/fields_base.cpp b/test/unit/fields_base.cpp index 765060ce..ca3e29b3 100644 --- a/test/unit/fields_base.cpp +++ b/test/unit/fields_base.cpp @@ -10,6 +10,7 @@ // Test that header file is self-contained. #include +#include #include #include #include @@ -445,12 +446,14 @@ struct fields_base_test // ends with invalid obs-fold rv = f.append("X", "AB\r\n C \r\n"); BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == condition::need_more_input); + BOOST_TEST(rv.error() == error::bad_field_value); + // BOOST_TEST(rv.error() == condition::need_more_input); // 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); + BOOST_TEST(rv.error() == error::bad_field_value); + // BOOST_TEST(rv.error() == error::bad_field_smuggle); // empty field name rv = f.append("", "ABC"); @@ -878,7 +881,8 @@ struct fields_base_test rv = f.set(f.find("T"), "abcdefghijk\r\nlmnopqrstuvwxyz"); BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + BOOST_TEST(rv.error() == error::bad_field_value); + // BOOST_TEST(rv.error() == error::bad_field_smuggle); }); // set(field, string_view) @@ -949,7 +953,8 @@ struct fields_base_test { 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); + BOOST_TEST(rv.error() == error::bad_field_value); + // BOOST_TEST(rv.error() == error::bad_field_smuggle); rv = f.set(field::server, "yz\r\n\x01\x02\x03"); BOOST_TEST(rv.has_error()); @@ -1043,7 +1048,8 @@ struct fields_base_test rv = f.set("valid", "\r\ninvalid string"); BOOST_TEST(rv.has_error()); - BOOST_TEST(rv.error() == error::bad_field_smuggle); + BOOST_TEST(rv.error() == error::bad_field_value); + // BOOST_TEST(rv.error() == error::bad_field_smuggle); rv = f.set("valid", "invalid\x01\x02\r\nstring"); BOOST_TEST(rv.has_error());