diff --git a/CHANGELOG b/CHANGELOG index ffce41c6..0eacc6fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ * Remove obsolete RFC2616 functions * Add message swap members and free functions * Add HTTP field value parser containers: ext_list, param_list, token_list +* Fixes for some corner cases in basic_parser_v1 +* Configurable limits on headers and body sizes in basic_parser_v1 API Changes: @@ -14,4 +16,6 @@ API Changes: * "DynamicBuffer","dynabuf" renamed from "Streambuf", "streambuf". See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4478.html#requirements.dynamic_buffers +* basic_parser_v1 adheres to rfc7230 as strictly as possible + -------------------------------------------------------------------------------- diff --git a/TODO.txt b/TODO.txt index 3b66fc11..0282538b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -46,3 +46,11 @@ HTTP: * Check basic_parser_v1 against rfc7230 for leading message whitespace * Fix the order of message constructor parameters: body first then headers (since body is constructed with arguments more often) +* Unit tests for char tables +* Remove status_code() from API when isRequest==true, et. al. +* Permit sending trailers and parameters in chunk-encoding chunks + +Future: + +* SOCKS proxy client and server implementations + diff --git a/doc/quickref.xml b/doc/quickref.xml index 3368eedc..d025b356 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -39,6 +39,11 @@ streambuf_body string_body + Options + + body_max_size + headers_max_size + Type Traits is_Body diff --git a/extras/beast/test/fail_counter.hpp b/extras/beast/test/fail_counter.hpp index 06500461..28e1c90c 100644 --- a/extras/beast/test/fail_counter.hpp +++ b/extras/beast/test/fail_counter.hpp @@ -13,10 +13,78 @@ namespace beast { namespace test { +enum error +{ + fail_error = 1 +}; + +namespace detail { + +class fail_error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "test"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + default: + case error::fail_error: + return "test error"; + } + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition{ev, *this}; + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_error_category() +{ + static fail_error_category const cat{}; + return cat; +} + +} // detail + +inline +error_code +make_error_code(error ev) +{ + return error_code{static_cast(ev), + detail::get_error_category()}; +} + /** A countdown to simulated failure. On the Nth operation, the class will fail with the specified - error code, or the default error code of invalid_argument. + error code, or the default error code of @ref fail_error. */ class fail_counter { @@ -31,10 +99,10 @@ public: @param The 0-based index of the operation to fail on or after. */ explicit - fail_counter(std::size_t n = 0) + fail_counter(std::size_t n, + error_code ev = make_error_code(fail_error)) : n_(n) - , ec_(boost::system::errc::make_error_code( - boost::system::errc::errc_t::invalid_argument)) + , ec_(ev) { } @@ -66,4 +134,14 @@ public: } // test } // beast +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + #endif diff --git a/extras/beast/test/fail_stream.hpp b/extras/beast/test/fail_stream.hpp index 2bbfaf1e..1c3a34e2 100644 --- a/extras/beast/test/fail_stream.hpp +++ b/extras/beast/test/fail_stream.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace beast { namespace test { @@ -26,8 +27,8 @@ namespace test { template class fail_stream { + boost::optional fc_; fail_counter* pfc_; - fail_counter fc_; NextLayer next_layer_; public: @@ -46,8 +47,8 @@ public: template explicit fail_stream(std::size_t n, Args&&... args) - : pfc_(&fc_) - , fc_(n) + : fc_(n) + , pfc_(&*fc_) , next_layer_(std::forward(args)...) { } diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp index c0acbf39..871f04cd 100644 --- a/include/beast/http/basic_parser_v1.hpp +++ b/include/beast/http/basic_parser_v1.hpp @@ -25,17 +25,65 @@ namespace http { namespace parse_flag { enum values { - chunked = 1 << 0, - connection_keep_alive = 1 << 1, - connection_close = 1 << 2, - connection_upgrade = 1 << 3, - trailing = 1 << 4, - upgrade = 1 << 5, - skipbody = 1 << 6, - contentlength = 1 << 7 + chunked = 1, + connection_keep_alive = 2, + connection_close = 4, + connection_upgrade = 8, + trailing = 16, + upgrade = 32, + skipbody = 64, + contentlength = 128 }; } // parse_flag +/** Headers maximum size option. + + Sets the maximum number of cumulative bytes allowed + including all header octets. A value of zero indicates + no limit on the number of header octets + + The default headers maximum size is 16KB (16,384 bytes). + + @note Objects of this type are passed to @ref set_option. +*/ +struct headers_max_size +{ + std::size_t value; + + explicit + headers_max_size(std::size_t v) + : value(v) + { + } +}; + +/** Body maximum size option. + + Sets the maximum number of cumulative bytes allowed including + all body octets. Octets in chunk-encoded bodies are counted + after decoding. A value of zero indicates no limit on + the number of body octets. + + The default body maximum size for requests is 4MB (four + megabytes or 4,194,304 bytes) and unlimited for responses. + + @note Objects of this type are passed to @ref set_option. +*/ +struct body_max_size +{ + std::size_t value; + + explicit + body_max_size(std::size_t v) + : value(v) + { + } +}; + +/// The value returned when no content length is known or applicable. +static std::uint64_t constexpr no_content_length = + std::numeric_limits::max(); + /** A parser for decoding HTTP/1 wire format messages. This parser is designed to efficiently parse messages in the @@ -85,7 +133,7 @@ enum values Called for each piece of the current header value. - @li `int on_headers(error_code&)` + @li `int on_headers(std::uint64_t content_length, error_code&)` Called when all the headers have been parsed successfully. @@ -126,90 +174,12 @@ enum values presented with request or response message. */ template -class basic_parser_v1 +class basic_parser_v1 : public detail::parser_base { private: using self = basic_parser_v1; typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); - static std::uint64_t constexpr no_content_length = - std::numeric_limits::max(); - - enum state : std::uint8_t - { - s_closed = 1, - - s_req_start, - s_req_method_start, - s_req_method, - s_req_space_before_url, - s_req_url_start, - s_req_url, - s_req_http_start, - s_req_http_H, - s_req_http_HT, - s_req_http_HTT, - s_req_http_HTTP, - s_req_major_start, - s_req_major, - s_req_minor_start, - s_req_minor, - s_req_line_end, - - s_res_start, - s_res_H, - s_res_HT, - s_res_HTT, - s_res_HTTP, - s_res_major_start, - s_res_major, - s_res_minor_start, - s_res_minor, - s_res_status_code_start, - s_res_status_code, - s_res_status_start, - s_res_status, - s_res_line_almost_done, - s_res_line_done, - - s_header_field_start, - s_header_field, - s_header_value_start, - s_header_value_discard_lWs0, - s_header_value_discard_ws0, - s_header_value_almost_done0, - s_header_value_text_start, - s_header_value_discard_lWs, - s_header_value_discard_ws, - s_header_value_text, - s_header_value_almost_done, - - s_headers_almost_done, - s_headers_done, - - s_chunk_size_start, - s_chunk_size, - s_chunk_parameters, - s_chunk_size_almost_done, - - // states below do not count towards - // the limit on the size of the message - - s_body_identity0, - s_body_identity, - s_body_identity_eof0, - s_body_identity_eof, - - s_chunk_data_start, - s_chunk_data, - s_chunk_data_almost_done, - s_chunk_data_done, - - s_complete, - s_restart, - s_closed_complete - }; - enum field_state : std::uint8_t { h_general = 0, @@ -224,25 +194,36 @@ private: h_matching_upgrade, h_connection, + h_content_length0, h_content_length, + h_content_length_ows, h_transfer_encoding, h_upgrade, h_matching_transfer_encoding_chunked, - h_matching_connection_token_start, + h_matching_transfer_encoding_general, h_matching_connection_keep_alive, h_matching_connection_close, h_matching_connection_upgrade, - h_matching_connection_token, h_transfer_encoding_chunked, + h_transfer_encoding_chunked_ows, + h_connection_keep_alive, + h_connection_keep_alive_ows, h_connection_close, + h_connection_close_ows, h_connection_upgrade, + h_connection_upgrade_ows, + h_connection_token, + h_connection_token_ows }; + std::size_t h_max_; + std::size_t h_left_; + std::size_t b_max_; + std::size_t b_left_; std::uint64_t content_length_; - std::uint64_t nread_; pmf_t cb_; state s_ : 8; unsigned flags_ : 8; @@ -260,10 +241,42 @@ public: /// Copy assignment. basic_parser_v1& operator=(basic_parser_v1 const&) = default; - /// Constructor - basic_parser_v1() + /// Default constructor + basic_parser_v1(); + + /** Set options on the parser. + + @param args One or more parser options to set. + */ +#if GENERATING_DOCS + template + void + set_option(Args&&... args) +#else + template + void + set_option(A1&& a1, A2&& a2, An&&... an) +#endif { - init(std::integral_constant{}); + set_option(std::forward(a1)); + set_option(std::forward(a2), + std::forward(an)...); + } + + /// Set the headers maximum size option + void + set_option(headers_max_size const& o) + { + h_max_ = o.value; + h_left_ = h_max_; + } + + /// Set the body maximum size option + void + set_option(body_max_size const& o) + { + b_max_ = o.value; + b_left_ = b_max_; } /// Returns internal flags associated with the parser. @@ -413,17 +426,48 @@ private: } void - init(std::true_type) + reset(std::true_type) { s_ = s_req_start; } void - init(std::false_type) + reset(std::false_type) { s_ = s_res_start; } + void + reset() + { + h_left_ = h_max_; + b_left_ = b_max_; + reset(std::integral_constant{}); + } + + void + init(std::true_type) + { + // 16KB max headers, 4MB max body + h_max_ = 16 * 1024; + b_max_ = 4 * 1024 * 1024; + } + + void + init(std::false_type) + { + // 16KB max headers, unlimited body + h_max_ = 16 * 1024; + b_max_ = 0; + } + + void + init() + { + init(std::integral_constant{}); + reset(); + } + bool needs_eof(std::true_type) const; @@ -584,7 +628,7 @@ private: { template().on_headers( - std::declval()))>> + std::declval(), std::declval()))>> static R check(int); template static std::false_type check(...); @@ -661,8 +705,16 @@ private: void call_on_method(error_code& ec, boost::string_ref const& s) { - call_on_method(ec, s, std::integral_constant::value>{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_method(ec, s, std::integral_constant::value>{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_uri(error_code& ec, @@ -678,8 +730,16 @@ private: void call_on_uri(error_code& ec, boost::string_ref const& s) { - call_on_uri(ec, s, std::integral_constant::value>{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_uri(ec, s, std::integral_constant::value>{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_reason(error_code& ec, @@ -695,8 +755,16 @@ private: void call_on_reason(error_code& ec, boost::string_ref const& s) { - call_on_reason(ec, s, std::integral_constant::value>{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_reason(ec, s, std::integral_constant::value>{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_request(error_code& ec, std::true_type) @@ -742,7 +810,15 @@ private: void call_on_field(error_code& ec, boost::string_ref const& s) { - call_on_field(ec, s, has_on_field{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_field(ec, s, has_on_field{}); + } + else + { + ec = parse_error::headers_too_big; + } } void call_on_value(error_code& ec, @@ -758,22 +834,32 @@ private: void call_on_value(error_code& ec, boost::string_ref const& s) { - call_on_value(ec, s, has_on_value{}); + if(! h_max_ || s.size() <= h_left_) + { + h_left_ -= s.size(); + call_on_value(ec, s, has_on_value{}); + } + else + { + ec = parse_error::headers_too_big; + } } - int call_on_headers(error_code& ec, std::true_type) + int call_on_headers(error_code& ec, + std::uint64_t content_length, std::true_type) { - return impl().on_headers(ec); + return impl().on_headers(content_length, ec); } - int call_on_headers(error_code& ec, std::false_type) + int call_on_headers(error_code& ec, std::uint64_t, std::false_type) { return 0; } int call_on_headers(error_code& ec) { - return call_on_headers(ec, has_on_headers{}); + return call_on_headers(ec, content_length_, + has_on_headers{}); } void call_on_body(error_code& ec, @@ -789,7 +875,15 @@ private: void call_on_body(error_code& ec, boost::string_ref const& s) { - call_on_body(ec, s, has_on_body{}); + if(! b_max_ || s.size() <= b_left_) + { + b_left_ -= s.size(); + call_on_body(ec, s, has_on_body{}); + } + else + { + ec = parse_error::body_too_big; + } } void call_on_complete(error_code& ec, std::true_type) diff --git a/include/beast/http/detail/basic_parser_v1.hpp b/include/beast/http/detail/basic_parser_v1.hpp index 7568296d..196089bc 100644 --- a/include/beast/http/detail/basic_parser_v1.hpp +++ b/include/beast/http/detail/basic_parser_v1.hpp @@ -17,137 +17,6 @@ namespace beast { namespace http { namespace detail { -// '0'...'9' -inline -bool -is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -inline -bool -is_token(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 - }}; - return tab[static_cast(c)] != 0; -} - -inline -bool -is_text(char c) -{ - // TEXT = - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 - }}; - return tab[static_cast(c)] != 0; -} - -// converts to lower case, -// returns 0 if not a valid token char -// -inline -char -to_field_char(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0 - }}; - return tab[static_cast(c)]; -} - -// converts to lower case, -// returns 0 if not a valid text char -// -inline -char -to_value_char(char c) -{ - // TEXT = - static std::array constexpr tab = {{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32 - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48 - 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64 - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80 - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96 - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112 - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128 - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144 - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160 - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176 - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192 - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208 - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 - }}; - return static_cast(tab[static_cast(c)]); -} - -inline -std::int8_t -unhex(char c) -{ - static std::array constexpr tab = {{ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 112 - }}; - return tab[static_cast(c)]; -} - template struct parser_str_t { @@ -196,6 +65,82 @@ parser_str_t<_>::transfer_encoding[18]; using parser_str = parser_str_t<>; +class parser_base +{ +protected: + enum state : std::uint8_t + { + s_dead = 1, + + s_req_start, + s_req_method0, + s_req_method, + s_req_url0, + s_req_url, + s_req_http, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_major, + s_req_dot, + s_req_minor, + s_req_cr, + s_req_lf, + + s_res_start, + s_res_H, + s_res_HT, + s_res_HTT, + s_res_HTTP, + s_res_major, + s_res_dot, + s_res_minor, + s_res_space_1, + s_res_status0, + s_res_status1, + s_res_status2, + s_res_space_2, + s_res_reason0, + s_res_reason, + s_res_line_lf, + s_res_line_done, + + s_header_name0, + s_header_name, + s_header_value0_lf, + s_header_value0_almost_done, + s_header_value0, + s_header_value, + s_header_value_lf, + s_header_value_almost_done, + s_header_value_unfold, + + s_headers_almost_done, + s_headers_done, + + s_chunk_size0, + s_chunk_size, + s_chunk_ext_name0, + s_chunk_ext_name, + s_chunk_ext_val, + s_chunk_size_lf, + s_chunk_data0, + s_chunk_data, + s_chunk_data_cr, + s_chunk_data_lf, + + s_body_identity0, + s_body_identity, + s_body_identity_eof0, + s_body_identity_eof, + + s_complete, + s_restart, + s_closed_complete + }; +}; + } // detail } // http } // beast diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp index 64272fdb..dc9285a5 100644 --- a/include/beast/http/detail/rfc7230.hpp +++ b/include/beast/http/detail/rfc7230.hpp @@ -17,6 +17,56 @@ namespace beast { namespace http { namespace detail { +inline +bool +is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +inline +bool +is_alpha(char c) +{ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 48 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 80 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 112 + }}; + return tab[static_cast(c)] != 0; +} + +inline +bool +is_text(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)] != 0; +} + inline bool is_tchar(char c) @@ -27,7 +77,7 @@ is_tchar(char c) "^" | "_" | "`" | "|" | "~" | DIGIT | ALPHA */ - static std::array constexpr tab = {{ + static std::array constexpr tab = {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 @@ -37,7 +87,7 @@ is_tchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 }}; - return tab[static_cast(c)]; + return tab[static_cast(c)] != 0; } inline @@ -97,6 +147,79 @@ is_qpchar(char c) return tab[static_cast(c)]; } +// converts to lower case, +// returns 0 if not a valid token char +// +inline +char +to_field_char(char c) +{ + /* token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0 + }}; + return tab[static_cast(c)]; +} + +// converts to lower case, +// returns 0 if not a valid text char +// +inline +char +to_value_char(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48 + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80 + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112 + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128 + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144 + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160 + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176 + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192 + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208 + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 + }}; + return static_cast(tab[static_cast(c)]); +} + +inline +std::int8_t +unhex(char c) +{ + static std::array constexpr tab = {{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 112 + }}; + return tab[static_cast(c)]; +} + template void skip_ows(FwdIt& it, FwdIt const& end) diff --git a/include/beast/http/impl/basic_parser_v1.ipp b/include/beast/http/impl/basic_parser_v1.ipp index 4e6a6776..918c52cf 100644 --- a/include/beast/http/impl/basic_parser_v1.ipp +++ b/include/beast/http/impl/basic_parser_v1.ipp @@ -5,21 +5,57 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +/* + This code is a modified version of nodejs/http-parser, copyright above: + https://github.com/nodejs/http-parser +*/ + #ifndef BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP #define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP +#include #include #include namespace beast { namespace http { +template +basic_parser_v1:: +basic_parser_v1() +{ + init(); +} + template bool basic_parser_v1:: keep_alive() const { - if(http_major_ > 0 && http_minor_ > 0) + if(http_major_ >= 1 && http_minor_ >= 1) { if(flags_ & parse_flag::connection_close) return false; @@ -32,8 +68,6 @@ keep_alive() const return ! needs_eof(); } -// Implementation inspired by nodejs/http-parser - template template typename std::enable_if< @@ -61,18 +95,18 @@ basic_parser_v1:: write(boost::asio::const_buffer const& buffer, error_code& ec) { using beast::http::detail::is_digit; - using beast::http::detail::is_token; + using beast::http::detail::is_tchar; using beast::http::detail::is_text; using beast::http::detail::to_field_char; using beast::http::detail::to_value_char; using beast::http::detail::unhex; using boost::asio::buffer_cast; using boost::asio::buffer_size; - + auto const data = buffer_cast(buffer); auto const size = buffer_size(buffer); - if(size == 0 && s_ != s_closed) + if(size == 0 && s_ != s_dead) return 0; auto begin = @@ -86,12 +120,12 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) auto err = [&](parse_error ev) { ec = ev; - s_ = s_closed; + s_ = s_dead; return used(); }; auto errc = [&] { - s_ = s_closed; + s_ = s_dead; return used(); }; auto piece = [&] @@ -118,7 +152,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) redo: switch(s_) { - case s_closed: + case s_dead: case s_closed_complete: return err(parse_error::connection_closed); break; @@ -127,36 +161,33 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) flags_ = 0; cb_ = nullptr; content_length_ = no_content_length; - s_ = s_req_method_start; + s_ = s_req_method0; goto redo; - case s_req_method_start: - if(! is_token(ch)) + case s_req_method0: + if(! is_tchar(ch)) return err(parse_error::bad_method); call_on_start(ec); if(ec) return errc(); - cb_ = &self::call_on_method; + assert(! cb_); + cb(&self::call_on_method); s_ = s_req_method; break; case s_req_method: - if(! is_token(ch)) + if(ch == ' ') { if(cb(nullptr)) return errc(); - s_ = s_req_space_before_url; - goto redo; + s_ = s_req_url0; + break; } + if(! is_tchar(ch)) + return err(parse_error::bad_method); break; - case s_req_space_before_url: - if(ch != ' ') - return err(parse_error::bad_request); - s_ = s_req_url_start; - break; - - case s_req_url_start: + case s_req_url0: { if(ch == ' ') return err(parse_error::bad_uri); @@ -174,7 +205,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) { if(cb(nullptr)) return errc(); - s_ = s_req_http_start; + s_ = s_req_http; break; } // VFALCO TODO Better checking for valid URL characters @@ -182,7 +213,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) return err(parse_error::bad_uri); break; - case s_req_http_start: + case s_req_http: if(ch != 'H') return err(parse_error::bad_version); s_ = s_req_http_H; @@ -209,80 +240,56 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_req_http_HTTP: if(ch != '/') return err(parse_error::bad_version); - s_ = s_req_major_start; - break; - - case s_req_major_start: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; s_ = s_req_major; break; case s_req_major: - if(ch == '.') - { - s_ = s_req_minor_start; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_major_ = 10 * http_major_ + ch - '0'; - if(http_major_ > 999) - return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_req_dot; break; - case s_req_minor_start: - if(! is_digit(ch)) + case s_req_dot: + if(ch != '.') return err(parse_error::bad_version); - http_minor_ = ch - '0'; s_ = s_req_minor; break; case s_req_minor: - if(ch == '\r') - { - s_ = s_req_line_end; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_minor_ = 10 * http_minor_ + ch - '0'; - if(http_minor_ > 999) - return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_req_cr; break; - case s_req_line_end: + case s_req_cr: + if(ch != '\r') + return err(parse_error::bad_version); + s_ = s_req_lf; + break; + + case s_req_lf: if(ch != '\n') return err(parse_error::bad_crlf); call_on_request(ec); if(ec) return errc(); - s_ = s_header_field_start; + s_ = s_header_name0; break; - //-------------------------------------------- + //---------------------------------------------------------------------- case s_res_start: flags_ = 0; cb_ = nullptr; content_length_ = no_content_length; - switch(ch) - { - case 'H': - call_on_start(ec); - if(ec) - return errc(); - s_ = s_res_H; - break; - // VFALCO NOTE this allows whitespace at the beginning, - // need to check rfc7230 - case '\r': - case '\n': - break; - default: + if(ch != 'H') return err(parse_error::bad_version); - } + call_on_start(ec); + if(ec) + return errc(); + s_ = s_res_H; break; case s_res_H: @@ -306,118 +313,88 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_res_HTTP: if(ch != '/') return err(parse_error::bad_version); - s_ = s_res_major_start; - break; - - case s_res_major_start: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; s_ = s_res_major; break; case s_res_major: - if(ch == '.') - { - s_ = s_res_minor_start; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_major_ = 10 * http_major_ + ch - '0'; - if(http_major_ > 999) - return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_res_dot; break; - case s_res_minor_start: - if(! is_digit(ch)) + case s_res_dot: + if(ch != '.') return err(parse_error::bad_version); - http_minor_ = ch - '0'; s_ = s_res_minor; break; case s_res_minor: - if(ch == ' ') - { - s_ = s_res_status_code_start; - break; - } if(! is_digit(ch)) return err(parse_error::bad_version); - http_minor_ = 10 * http_minor_ + ch - '0'; - if(http_minor_ > 999) - return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_res_space_1; break; - case s_res_status_code_start: + case s_res_space_1: + if(ch != ' ') + return err(parse_error::bad_version); + s_ = s_res_status0; + break; + + case s_res_status0: if(! is_digit(ch)) - { - if(ch == ' ') - break; - return err(parse_error::bad_status_code); - } + return err(parse_error::bad_status); status_code_ = ch - '0'; - s_ = s_res_status_code; + s_ = s_res_status1; break; - case s_res_status_code: + case s_res_status1: if(! is_digit(ch)) - { - switch(ch) - { - case ' ': s_ = s_res_status_start; break; - case '\r': s_ = s_res_line_almost_done; break; - case '\n': s_ = s_header_field_start; break; - default: - return err(parse_error::bad_status_code); - } - break; - } + return err(parse_error::bad_status); status_code_ = status_code_ * 10 + ch - '0'; - if(status_code_ > 999) - return err(parse_error::bad_status_code); + s_ = s_res_status2; break; - case s_res_status_start: + case s_res_status2: + if(! is_digit(ch)) + return err(parse_error::bad_status); + status_code_ = status_code_ * 10 + ch - '0'; + s_ = s_res_space_2; + break; + + case s_res_space_2: + if(ch != ' ') + return err(parse_error::bad_status); + s_ = s_res_reason0; + break; + + case s_res_reason0: if(ch == '\r') { - s_ = s_res_line_almost_done; - break; - } - // VFALCO Is this up to spec? - if(ch == '\n') - { - s_ = s_header_field_start; + s_ = s_res_line_lf; break; } if(! is_text(ch)) - return err(parse_error::bad_status); - if(cb(&self::call_on_reason)) - return errc(); - pos_ = 0; - s_ = s_res_status; + return err(parse_error::bad_reason); + assert(! cb_); + cb(&self::call_on_reason); + s_ = s_res_reason; break; - case s_res_status: + case s_res_reason: if(ch == '\r') { if(cb(nullptr)) return errc(); - s_ = s_res_line_almost_done; - break; - } - if(ch == '\n') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_field_start; + s_ = s_res_line_lf; break; } if(! is_text(ch)) - return err(parse_error::bad_status); + return err(parse_error::bad_reason); break; - case s_res_line_almost_done: + case s_res_line_lf: if(ch != '\n') return err(parse_error::bad_crlf); s_ = s_res_line_done; @@ -427,15 +404,12 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) call_on_response(ec); if(ec) return errc(); - s_ = s_header_field_start; + s_ = s_header_name0; goto redo; - //-------------------------------------------- + //---------------------------------------------------------------------- - // message-header = field-name ":" [ field-value ] - // field-name = token - - case s_header_field_start: + case s_header_name0: { if(ch == '\r') { @@ -457,21 +431,23 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) } assert(! cb_); cb(&self::call_on_field); - s_ = s_header_field; + s_ = s_header_name; break; } - case s_header_field: + case s_header_name: { for(; p != end; ++p) { ch = *p; auto c = to_field_char(ch); - if(! c) - break; + if(! c) + break; switch(fs_) { - case h_general: break; + default: + case h_general: + break; case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break; case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break; case h_CON: @@ -487,8 +463,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_connection: ++pos_; - if(pos_ >= sizeof(detail::parser_str::connection)-1 || - c != detail::parser_str::connection[pos_]) + if(c != detail::parser_str::connection[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::connection)-2) fs_ = h_connection; @@ -496,8 +471,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_proxy_connection: ++pos_; - if(pos_ >= sizeof(detail::parser_str::proxy_connection)-1 || - c != detail::parser_str::proxy_connection[pos_]) + if(c != detail::parser_str::proxy_connection[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2) fs_ = h_connection; @@ -505,22 +479,19 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_content_length: ++pos_; - if(pos_ >= sizeof(detail::parser_str::content_length)-1 || - c != detail::parser_str::content_length[pos_]) + if(c != detail::parser_str::content_length[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::content_length)-2) { if(flags_ & parse_flag::contentlength) return err(parse_error::bad_content_length); - fs_ = h_content_length; - flags_ |= parse_flag::contentlength; + fs_ = h_content_length0; } break; case h_matching_transfer_encoding: ++pos_; - if(pos_ >= sizeof(detail::parser_str::transfer_encoding)-1 || - c != detail::parser_str::transfer_encoding[pos_]) + if(c != detail::parser_str::transfer_encoding[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2) fs_ = h_transfer_encoding; @@ -528,22 +499,18 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case h_matching_upgrade: ++pos_; - if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || - c != detail::parser_str::upgrade[pos_]) + if(c != detail::parser_str::upgrade[pos_]) fs_ = h_general; else if(pos_ == sizeof(detail::parser_str::upgrade)-2) fs_ = h_upgrade; break; case h_connection: - case h_content_length: + case h_content_length0: case h_transfer_encoding: case h_upgrade: - // VFALCO Do we allow a space here? fs_ = h_general; break; - default: - break; } } if(p == end) @@ -555,111 +522,39 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) { if(cb(nullptr)) return errc(); - s_ = s_header_value_start; + s_ = s_header_value0; break; } return err(parse_error::bad_field); } - - // field-value = *( field-content | LWS ) - // field-content = *TEXT - // LWS = [CRLF] 1*( SP | HT ) - - case s_header_value_start: - if(ch == '\r') - { - s_ = s_header_value_discard_lWs0; - break; - } - if(ch == ' ' || ch == '\t') - { - s_ = s_header_value_discard_ws0; - break; - } - s_ = s_header_value_text_start; - goto redo; - - case s_header_value_discard_ws0: + /* + header-field = field-name ":" OWS field-value OWS + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + */ + case s_header_value0: if(ch == ' ' || ch == '\t') break; if(ch == '\r') { - s_ = s_header_value_discard_lWs0; + s_ = s_header_value0_lf; break; } - s_ = s_header_value_text_start; - goto redo; - - case s_header_value_discard_lWs0: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value_almost_done0; - break; - - case s_header_value_almost_done0: - if(ch == ' ' || ch == '\t') + if(fs_ == h_content_length0) { - s_ = s_header_value_discard_ws0; - break; + content_length_ = 0; + flags_ |= parse_flag::contentlength; } - call_on_value(ec, boost::string_ref{"", 0}); - if(ec) - return errc(); - s_ = s_header_field_start; - goto redo; + assert(! cb_); + cb(&self::call_on_value); + s_ = s_header_value; + // fall through - case s_header_value_text_start: - { - auto const c = to_value_char(ch); - if(! c) - return err(parse_error::bad_value); - switch(fs_) - { - case h_upgrade: - flags_ |= parse_flag::upgrade; - fs_ = h_general; - break; - - case h_transfer_encoding: - if(c == 'c') - fs_ = h_matching_transfer_encoding_chunked; - else - fs_ = h_general; - break; - - case h_content_length: - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - content_length_ = ch - '0'; - break; - - case h_connection: - switch(c) - { - case 'k': fs_ = h_matching_connection_keep_alive; break; - case 'c': fs_ = h_matching_connection_close; break; - case 'u': fs_ = h_matching_connection_upgrade; break; - default: - fs_ = h_matching_connection_token; - break; - } - break; - - case h_matching_connection_token_start: - break; - - default: - fs_ = h_general; - break; - } - pos_ = 0; - if(cb(&self::call_on_value)) - return errc(); - s_ = s_header_value_text; - break; - } - - case s_header_value_text: + case s_header_value: { for(; p != end; ++p) { @@ -668,7 +563,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) { if(cb(nullptr)) return errc(); - s_ = s_header_value_discard_lWs; + s_ = s_header_value_lf; break; } auto const c = to_value_char(ch); @@ -677,16 +572,165 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) switch(fs_) { case h_general: + default: break; case h_connection: - case h_transfer_encoding: - assert(0); + switch(c) + { + case 'k': + pos_ = 0; + fs_ = h_matching_connection_keep_alive; + break; + case 'c': + pos_ = 0; + fs_ = h_matching_connection_close; + break; + case 'u': + pos_ = 0; + fs_ = h_matching_connection_upgrade; + break; + default: + if(ch == ' ' || ch == '\t' || ch == ',') + break; + if(! is_tchar(ch)) + return err(parse_error::bad_value); + fs_ = h_connection_token; + break; + } + break; + + case h_matching_connection_keep_alive: + ++pos_; + if(c != detail::parser_str::keep_alive[pos_]) + fs_ = h_connection_token; + else if(pos_ == sizeof(detail::parser_str::keep_alive)- 2) + fs_ = h_connection_keep_alive; + break; + + case h_matching_connection_close: + ++pos_; + if(c != detail::parser_str::close[pos_]) + fs_ = h_connection_token; + else if(pos_ == sizeof(detail::parser_str::close)-2) + fs_ = h_connection_close; + break; + + case h_matching_connection_upgrade: + ++pos_; + if(c != detail::parser_str::upgrade[pos_]) + fs_ = h_connection_token; + else if(pos_ == sizeof(detail::parser_str::upgrade)-2) + fs_ = h_connection_upgrade; + break; + + case h_connection_close: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_close; + } + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_close_ows; + else if(is_tchar(ch)) + fs_ = h_connection_token; + else + return err(parse_error::bad_value); + break; + + case h_connection_close_ows: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_close; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_connection_keep_alive: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_keep_alive; + } + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_keep_alive_ows; + else if(is_tchar(ch)) + fs_ = h_connection_token; + else + return err(parse_error::bad_value); + break; + + case h_connection_keep_alive_ows: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_keep_alive; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_connection_upgrade: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_upgrade; + } + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_upgrade_ows; + else if(is_tchar(ch)) + fs_ = h_connection_token; + else + return err(parse_error::bad_value); + break; + + case h_connection_upgrade_ows: + if(ch == ',') + { + fs_ = h_connection; + flags_ |= parse_flag::connection_upgrade; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_connection_token: + if(ch == ',') + fs_ = h_connection; + else if(ch == ' ' || ch == '\t') + fs_ = h_connection_token_ows; + else if(! is_tchar(ch)) + return err(parse_error::bad_value); + break; + + case h_connection_token_ows: + if(ch == ',') + { + fs_ = h_connection; + break; + } + if(ch == ' ' || ch == '\t') + break; + return err(parse_error::bad_value); + + case h_content_length0: + if(! is_digit(ch)) + return err(parse_error::bad_content_length); + content_length_ = ch - '0'; + fs_ = h_content_length; break; case h_content_length: if(ch == ' ' || ch == '\t') + { + fs_ = h_content_length_ows; break; + } if(! is_digit(ch)) return err(parse_error::bad_content_length); if(content_length_ > (no_content_length - 10) / 10) @@ -695,92 +739,44 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) content_length_ * 10 + ch - '0'; break; + case h_content_length_ows: + if(ch != ' ' && ch != '\t') + return err(parse_error::bad_content_length); + break; + + case h_transfer_encoding: + if(c == 'c') + { + pos_ = 0; + fs_ = h_matching_transfer_encoding_chunked; + } + else if(c != ' ' && c != '\t' && c != ',') + { + fs_ = h_matching_transfer_encoding_general; + } + break; + case h_matching_transfer_encoding_chunked: ++pos_; - if(pos_ >= sizeof(detail::parser_str::chunked)-1 || - c != detail::parser_str::chunked[pos_]) - fs_ = h_general; + if(c != detail::parser_str::chunked[pos_]) + fs_ = h_matching_transfer_encoding_general; else if(pos_ == sizeof(detail::parser_str::chunked)-2) fs_ = h_transfer_encoding_chunked; break; - case h_matching_connection_token_start: - switch(c) - { - case 'k': fs_ = h_matching_connection_keep_alive; break; - case 'c': fs_ = h_matching_connection_close; break; - case 'u': fs_ = h_matching_connection_upgrade; break; - default: - if(is_token(c)) - fs_ = h_matching_connection_token; - else if(ch == ' ' || ch == '\t') - { } - else - fs_ = h_general; - break; - } - break; - - case h_matching_connection_keep_alive: - ++pos_; - if(pos_ >= sizeof(detail::parser_str::keep_alive)-1 || - c != detail::parser_str::keep_alive[pos_]) - fs_ = h_matching_connection_token; - else if (pos_ == sizeof(detail::parser_str::keep_alive)- 2) - fs_ = h_connection_keep_alive; - break; - - case h_matching_connection_close: - ++pos_; - if(pos_ >= sizeof(detail::parser_str::close)-1 || - c != detail::parser_str::close[pos_]) - fs_ = h_matching_connection_token; - else if(pos_ == sizeof(detail::parser_str::close)-2) - fs_ = h_connection_close; - break; - - case h_matching_connection_upgrade: - ++pos_; - if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || - c != detail::parser_str::upgrade[pos_]) - fs_ = h_matching_connection_token; - else if (pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_connection_upgrade; - break; - - case h_matching_connection_token: - if(ch == ',') - { - fs_ = h_matching_connection_token_start; - pos_ = 0; - } + case h_matching_transfer_encoding_general: + if(c == ',') + fs_ = h_transfer_encoding; break; case h_transfer_encoding_chunked: - if(ch != ' ' && ch != '\t') - fs_ = h_general; + if(c != ' ' && c != '\t' && c != ',') + fs_ = h_transfer_encoding; break; - case h_connection_keep_alive: - case h_connection_close: - case h_connection_upgrade: - if(ch ==',') - { - if(fs_ == h_connection_keep_alive) - flags_ |= parse_flag::connection_keep_alive; - else if(fs_ == h_connection_close) - flags_ |= parse_flag::connection_close; - else if(fs_ == h_connection_upgrade) - flags_ |= parse_flag::connection_upgrade; - fs_ = h_matching_connection_token_start; - pos_ = 0; - } - else if(ch != ' ' && ch != '\t') - { - fs_ = h_matching_connection_token; - } - break; - default: + case h_upgrade: + flags_ |= parse_flag::upgrade; + fs_ = h_general; break; } } @@ -789,25 +785,30 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) break; } - case s_header_value_discard_ws: - if(ch == ' ' || ch == '\t') - break; - if(ch == '\r') - { - s_ = s_header_value_discard_lWs; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_value); - call_on_value(ec, boost::string_ref(" ", 1)); - if(ec) - return errc(); - if(cb(&self::call_on_value)) - return errc(); - s_ = s_header_value_text; + case s_header_value0_lf: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_header_value0_almost_done; break; - case s_header_value_discard_lWs: + case s_header_value0_almost_done: + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value0; + break; + } + if(fs_ == h_content_length0) + return err(parse_error::bad_content_length); + if(fs_ == h_upgrade) + flags_ |= parse_flag::upgrade; + assert(! cb_); + call_on_value(ec, boost::string_ref{"", 0}); + if(ec) + return errc(); + s_ = s_header_name0; + goto redo; + + case s_header_value_lf: if(ch != '\n') return err(parse_error::bad_crlf); s_ = s_header_value_almost_done; @@ -816,19 +817,72 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_header_value_almost_done: if(ch == ' ' || ch == '\t') { - s_ = s_header_value_discard_ws; + switch(fs_) + { + case h_matching_connection_keep_alive: + case h_matching_connection_close: + case h_matching_connection_upgrade: + fs_ = h_connection_token_ows; + break; + + case h_connection_close: + fs_ = h_connection_close_ows; + break; + + case h_connection_keep_alive: + fs_ = h_connection_keep_alive_ows; + break; + + case h_connection_upgrade: + fs_ = h_connection_upgrade_ows; + break; + + case h_content_length: + fs_ = h_content_length_ows; + break; + + case h_matching_transfer_encoding_chunked: + fs_ = h_matching_transfer_encoding_general; + break; + + default: + break; + } + call_on_value(ec, boost::string_ref(" ", 1)); + s_ = s_header_value_unfold; break; } switch(fs_) { - case h_connection_keep_alive: flags_ |= parse_flag::connection_keep_alive; break; - case h_connection_close: flags_ |= parse_flag::connection_close; break; - case h_transfer_encoding_chunked: flags_ |= parse_flag::chunked; break; - case h_connection_upgrade: flags_ |= parse_flag::connection_upgrade; break; + case h_connection_keep_alive: + case h_connection_keep_alive_ows: + flags_ |= parse_flag::connection_keep_alive; + break; + case h_connection_close: + case h_connection_close_ows: + flags_ |= parse_flag::connection_close; + break; + + case h_connection_upgrade: + case h_connection_upgrade_ows: + flags_ |= parse_flag::connection_upgrade; + break; + + case h_transfer_encoding_chunked: + case h_transfer_encoding_chunked_ows: + flags_ |= parse_flag::chunked; + break; + default: break; } - s_ = s_header_field_start; + s_ = s_header_name0; + goto redo; + + case s_header_value_unfold: + assert(! cb_); + cb(&self::call_on_value); + s_ = s_header_value; goto redo; case s_headers_almost_done: @@ -850,9 +904,14 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) return errc(); switch(maybe_skip) { - case 0: break; - case 2: upgrade_ = true; // fall through - case 1: flags_ |= parse_flag::skipbody; break; + case 0: + break; + case 2: + upgrade_ = true; + // fall through + case 1: + flags_ |= parse_flag::skipbody; + break; default: return err(parse_error::bad_on_headers_rv); } @@ -879,13 +938,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) } else if(flags_ & parse_flag::chunked) { - s_ = s_chunk_size_start; + s_ = s_chunk_size0; break; } - else if(content_length_ == 0) - { - s_ = s_complete; - } else if(content_length_ != no_content_length) { s_ = s_body_identity0; @@ -907,7 +962,7 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) assert(! cb_); cb(&self::call_on_body); s_ = s_body_identity; - goto redo; // VFALCO fall through? + // fall through case s_body_identity: { @@ -932,13 +987,13 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) assert(! cb_); cb(&self::call_on_body); s_ = s_body_identity_eof; - goto redo; // VFALCO fall through? + // fall through case s_body_identity_eof: p = end - 1; break; - case s_chunk_size_start: + case s_chunk_size0: { auto v = unhex(ch); if(v == -1) @@ -947,23 +1002,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) s_ = s_chunk_size; break; } + case s_chunk_size: { if(ch == '\r') { - s_ = s_chunk_size_almost_done; + s_ = s_chunk_size_lf; + break; + } + if(ch == ';') + { + s_ = s_chunk_ext_name0; break; } auto v = unhex(ch); if(v == -1) - { - if(ch == ';' || ch == ' ') - { - s_ = s_chunk_parameters; - break; - } return err(parse_error::invalid_chunk_size); - } if(content_length_ > (no_content_length - 16) / 16) return err(parse_error::bad_content_length); content_length_ = @@ -971,29 +1025,54 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) break; } - case s_chunk_parameters: + case s_chunk_ext_name0: + if(! is_tchar(ch)) + return err(parse_error::invalid_ext_name); + s_ = s_chunk_ext_name; + break; + + case s_chunk_ext_name: if(ch == '\r') { - s_ = s_chunk_size_almost_done; + s_ = s_chunk_size_lf; + break; + } + if(ch == '=') + { + s_ = s_chunk_ext_val; + break; + } + if(ch == ';') + { + s_ = s_chunk_ext_name0; + break; + } + if(! is_tchar(ch)) + return err(parse_error::invalid_ext_name); + break; + + case s_chunk_ext_val: + if(ch == '\r') + { + s_ = s_chunk_size_lf; break; } break; - case s_chunk_size_almost_done: + case s_chunk_size_lf: if(ch != '\n') return err(parse_error::bad_crlf); - nread_ = 0; if(content_length_ == 0) { flags_ |= parse_flag::trailing; - s_ = s_header_field_start; + s_ = s_header_name0; break; } //call_chunk_header(ec); if(ec) return errc(); - s_ = s_chunk_data_start; + s_ = s_chunk_data0; break; - case s_chunk_data_start: + case s_chunk_data0: assert(! cb_); cb(&self::call_on_body); s_ = s_chunk_data; @@ -1009,23 +1088,22 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) content_length_ -= n; p += n - 1; if(content_length_ == 0) - s_ = s_chunk_data_almost_done; + s_ = s_chunk_data_cr; break; } - case s_chunk_data_almost_done: + case s_chunk_data_cr: if(ch != '\r') return err(parse_error::bad_crlf); if(cb(nullptr)) return errc(); - s_ = s_chunk_data_done; + s_ = s_chunk_data_lf; break; - case s_chunk_data_done: + case s_chunk_data_lf: if(ch != '\n') return err(parse_error::bad_crlf); - nread_ = 0; - s_ = s_chunk_size_start; + s_ = s_chunk_size0; break; case s_complete: @@ -1040,9 +1118,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_restart: if(keep_alive()) - init(std::integral_constant{}); + reset(); else - s_ = s_closed; + s_ = s_dead; goto redo; } } @@ -1066,7 +1144,7 @@ write_eof(error_code& ec) s_ = s_closed_complete; break; - case s_closed: + case s_dead: case s_closed_complete: break; @@ -1076,14 +1154,14 @@ write_eof(error_code& ec) call_on_complete(ec); if(ec) { - s_ = s_closed; + s_ = s_dead; break; } s_ = s_closed_complete; break; default: - s_ = s_closed; + s_ = s_dead; ec = parse_error::short_read; break; } @@ -1103,18 +1181,15 @@ basic_parser_v1:: needs_eof(std::false_type) const { // See RFC 2616 section 4.4 - if (status_code_ / 100 == 1 || // 1xx e.g. Continue - status_code_ == 204 || // No Content - status_code_ == 304 || // Not Modified - flags_ & parse_flag::skipbody) // response to a HEAD request - { + if( status_code_ / 100 == 1 || // 1xx e.g. Continue + status_code_ == 204 || // No Content + status_code_ == 304 || // Not Modified + flags_ & parse_flag::skipbody) // response to a HEAD request return false; - } - if((flags_ & parse_flag::chunked) || content_length_ != no_content_length) - { + if((flags_ & parse_flag::chunked) || + content_length_ != no_content_length) return false; - } return true; } diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp index c60d3a7c..c98934dc 100644 --- a/include/beast/http/parse_error.hpp +++ b/include/beast/http/parse_error.hpp @@ -23,8 +23,8 @@ enum class parse_error bad_crlf, bad_request, - bad_status_code, bad_status, + bad_reason, bad_field, bad_value, @@ -33,7 +33,11 @@ enum class parse_error bad_on_headers_rv, invalid_chunk_size, + invalid_ext_name, + invalid_ext_val, + headers_too_big, + body_too_big, short_read, general @@ -60,7 +64,7 @@ public: return "bad method"; case parse_error::bad_uri: - return "bad Request-URI"; + return "bad request-target"; case parse_error::bad_version: return "bad HTTP-Version"; @@ -69,13 +73,13 @@ public: return "missing CRLF"; case parse_error::bad_request: - return "bad Request-Line"; - - case parse_error::bad_status_code: - return "bad Status-Code"; + return "bad reason-phrase"; case parse_error::bad_status: - return "bad Status-Line"; + return "bad status-code"; + + case parse_error::bad_reason: + return "bad reason-phrase"; case parse_error::bad_field: return "bad field token"; @@ -95,6 +99,18 @@ public: case parse_error::invalid_chunk_size: return "invalid chunk size"; + case parse_error::invalid_ext_name: + return "invalid ext name"; + + case parse_error::invalid_ext_val: + return "invalid ext val"; + + case parse_error::headers_too_big: + return "headers size limit exceeded"; + + case parse_error::body_too_big: + return "body size limit exceeded"; + case parse_error::short_read: return "unexpected end of data"; diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp index c99c2156..0909c298 100644 --- a/include/beast/http/parser_v1.hpp +++ b/include/beast/http/parser_v1.hpp @@ -172,7 +172,7 @@ private: m_.reason = std::move(this->reason_); } - int on_headers(error_code&) + int on_headers(std::uint64_t, error_code&) { flush(); m_.version = 10 * this->http_major() + this->http_minor(); diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index df4b5ed3..0d9f9da2 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -6,6 +6,8 @@ GroupSources(test/http "/") add_executable (http-tests ${BEAST_INCLUDES} + message_fuzz.hpp + fail_parser.hpp ../../extras/beast/unit_test/main.cpp basic_dynabuf_body.cpp basic_headers.cpp @@ -35,6 +37,7 @@ endif() add_executable (bench-tests ${BEAST_INCLUDES} + nodejs_parser.hpp ../../extras/beast/unit_test/main.cpp nodejs_parser.cpp parser_bench.cpp diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp index b1bd2376..b8b207b6 100644 --- a/test/http/basic_parser_v1.cpp +++ b/test/http/basic_parser_v1.cpp @@ -8,10 +8,9 @@ // Test that header file is self-contained. #include -#include "message_fuzz.hpp" +#include "fail_parser.hpp" -#include -#include +#include #include #include #include @@ -91,7 +90,7 @@ public: { value = true; } - int on_headers(error_code&) + int on_headers(std::uint64_t, error_code&) { headers = true; return 0; @@ -106,201 +105,7 @@ public: } }; - //-------------------------------------------------------------------------- - - static - std::string - escaped_string(boost::string_ref const& s) - { - std::string out; - out.reserve(s.size()); - char const* p = s.data(); - while(p != s.end()) - { - if(*p == '\r') - out.append("\\r"); - else if(*p == '\n') - out.append("\\n"); - else if (*p == '\t') - out.append("\\t"); - else - out.append(p, 1); - ++p; - } - return out; - } - - template - struct null_parser : basic_parser_v1> - { - }; - - static - std::string - str(boost::string_ref const& s) - { - return std::string{s.data(), s.size()}; - } - - template - class test_parser : - public basic_parser_v1> - { - std::string field_; - std::string value_; - - void check() - { - if(! value_.empty()) - { - fields.emplace(field_, str(detail::trim(value_))); - field_.clear(); - value_.clear(); - } - } - - public: - std::map fields; - std::string body; - - void on_field(boost::string_ref const& s, error_code&) - { - check(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - } - - int on_headers(error_code&) - { - check(); - return 0; - } - - void on_body(boost::string_ref const& s, error_code&) - { - body.append(s.data(), s.size()); - } - }; - - // Parse the entire input buffer as a valid message, - // then parse in two pieces of all possible lengths. - // - template - void - parse(boost::string_ref const& m, F&& f) - { - using boost::asio::buffer; - for(;;) - { - error_code ec; - Parser p; - p.write(buffer(m.data(), m.size()), ec); - if(! expect(! ec, ec.message())) - break; - if(p.needs_eof()) - { - p.write_eof(ec); - if(! expect(! ec, ec.message())) - break; - } - if(expect(p.complete())) - f(p); - break; - } - for(std::size_t i = 1; i < m.size() - 1; ++i) - { - error_code ec; - Parser p; - p.write(buffer(&m[0], i), ec); - if(! expect(! ec, ec.message())) - continue; - if(! p.complete()) - { - p.write(buffer(&m[i], m.size() - i), ec); - if(! expect(! ec, ec.message())) - continue; - } - if(! p.complete() && p.needs_eof()) - { - p.write_eof(ec); - if(! expect(! ec, ec.message())) - continue; - } - if(! expect(p.complete())) - continue; - f(p); - } - } - - // Parse with an expected error code - // - template - void - parse_ev(boost::string_ref const& m, parse_error ev) - { - using boost::asio::buffer; - { - error_code ec; - null_parser p; - p.write(buffer(m.data(), m.size()), ec); - if(expect(! p.complete())) - expect(ec == ev, ec.message()); - } - for(std::size_t i = 1; i < m.size() - 1; ++i) - { - error_code ec; - null_parser p; - p.write(buffer(&m[0], i), ec); - if(! expect(! p.complete())) - continue; - if(ec) - { - expect(ec == ev, ec.message()); - continue; - } - p.write(buffer(&m[i], m.size() - i), ec); - if(! expect(! p.complete())) - continue; - if(! expect(ec == ev, ec.message())) - continue; - } - } - - // Parse a valid message with expected version - // - template - void - version(boost::string_ref const& m, - unsigned major, unsigned minor) - { - parse>(m, - [&](null_parser const& p) - { - expect(p.http_major() == major); - expect(p.http_minor() == minor); - }); - } - - // Parse a valid message with expected flags mask - // - void - checkf(boost::string_ref const& m, std::uint8_t mask) - { - parse>(m, - [&](null_parser const& p) - { - expect(p.flags() & mask); - }); - } - - //-------------------------------------------------------------------------- - - // Check all callbacks invoked + // Check that all callbacks are invoked void testCallbacks() { @@ -352,439 +157,1012 @@ public: } } + //-------------------------------------------------------------------------- + + template + static void - testVersion() - { - version ("GET / HTTP/0.0\r\n\r\n", 0, 0); - version ("GET / HTTP/0.1\r\n\r\n", 0, 1); - version ("GET / HTTP/0.9\r\n\r\n", 0, 9); - version ("GET / HTTP/1.0\r\n\r\n", 1, 0); - version ("GET / HTTP/1.1\r\n\r\n", 1, 1); - version ("GET / HTTP/9.9\r\n\r\n", 9, 9); - version ("GET / HTTP/999.999\r\n\r\n", 999, 999); - parse_ev("GET / HTTP/1000.0\r\n\r\n", parse_error::bad_version); - parse_ev("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version); - parse_ev("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version); - parse_ev("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version); - - version ("HTTP/0.0 200 OK\r\n\r\n", 0, 0); - version ("HTTP/0.1 200 OK\r\n\r\n", 0, 1); - version ("HTTP/0.9 200 OK\r\n\r\n", 0, 9); - version ("HTTP/1.0 200 OK\r\n\r\n", 1, 0); - version ("HTTP/1.1 200 OK\r\n\r\n", 1, 1); - version ("HTTP/9.9 200 OK\r\n\r\n", 9, 9); - version ("HTTP/999.999 200 OK\r\n\r\n", 999, 999); - parse_ev("HTTP/1000.0 200 OK\r\n\r\n", parse_error::bad_version); - parse_ev("HTTP/0.1000 200 OK\r\n\r\n", parse_error::bad_version); - parse_ev("HTTP/99999999999999999999.0 200 OK\r\n\r\n", parse_error::bad_version); - parse_ev("HTTP/0.99999999999999999999 200 OK\r\n\r\n", parse_error::bad_version); - } - - void testConnection(std::string const& token, - std::uint8_t flag) - { - checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection:\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: \t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + " \r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + " \t\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t \r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: \r\n" " " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" " " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: \r\n" "\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" "\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X, " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X,\t" + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X,\t " + token + "\r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X," + token + " \r\n\r\n", flag); - checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag); - } - - void testContentLength() - { - std::size_t const length = 0; - std::string const length_s = - std::to_string(length); - - checkf("GET / HTTP/1.1\r\nContent-Length:"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length:\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: \t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \t\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t \r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); - } - - void testTransferEncoding() - { - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \t\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t \r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); - checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked ); - } - - void testFlags() - { - testConnection("keep-alive", parse_flag::connection_keep_alive); - testConnection("close", parse_flag::connection_close); - testConnection("upgrade", parse_flag::connection_upgrade); - - checkf("GET / HTTP/1.1\r\nConnection: close, win\r\n\r\n", parse_flag::connection_close); - checkf("GET / HTTP/1.1\r\nConnection: keep-alive, win\r\n\r\n", parse_flag::connection_keep_alive); - checkf("GET / HTTP/1.1\r\nConnection: upgrade, win\r\n\r\n", parse_flag::connection_upgrade); - - testContentLength(); - - testTransferEncoding(); - - checkf( - "GET / HTTP/1.1\r\n" - "Upgrade: x\r\n" - "\r\n", - parse_flag::upgrade - ); - - parse_ev( - "GET / HTTP/1.1\r\n" - "Transfer-Encoding:chunked\r\n" - "Content-Length: 0\r\n" - "Proxy-Connection: close\r\n" - "\r\n", parse_error::illegal_content_length); - } - - void testHeaders() - { - parse>( - "GET / HTTP/1.0\r\n" - "Conniving: yes\r\n" - "Content-Lengthening: yes\r\n" - "Transfer-Encoding: deflate\r\n" - "Connection: sweep\r\n" - "\r\n", - [](null_parser const&) - { - }); - - parse_ev( - "GET / HTTP/1.0\r\n" - "Content-Length: 1\r\n" - "Content-Length: 2\r\n" - "\r\n", - parse_error::bad_content_length); - - parse_ev( - "GET / HTTP/1.0\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "fffffffffffffffff\r\n" - "0\r\n\r\n", - parse_error::bad_content_length); - - parse_ev("GET / HTTP/1.0\r\nContent-Length: 1e9\r\n\r\n", - parse_error::bad_content_length); - - parse_ev("GET / HTTP/1.0\r\nContent-Length: 99999999999999999999999\r\n\r\n", - parse_error::bad_content_length); - } - - void testUpgrade() - { - using boost::asio::buffer; - null_parser p; - boost::string_ref s = - "GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n"; - error_code ec; - p.write(buffer(s.data(), s.size()), ec); - if(! expect(! ec, ec.message())) - return; - expect(p.complete()); - expect(p.upgrade()); - } - - void testBad() - { - parse_ev(" ", parse_error::bad_method); - parse_ev(" G", parse_error::bad_method); - parse_ev("G:", parse_error::bad_request); - parse_ev("GET /", parse_error::bad_uri); - parse_ev("GET / X", parse_error::bad_version); - parse_ev("GET / HX", parse_error::bad_version); - parse_ev("GET / HTTX", parse_error::bad_version); - parse_ev("GET / HTTPX", parse_error::bad_version); - parse_ev("GET / HTTP/.", parse_error::bad_version); - parse_ev("GET / HTTP/1000", parse_error::bad_version); - parse_ev("GET / HTTP/1. ", parse_error::bad_version); - parse_ev("GET / HTTP/1.1000", parse_error::bad_version); - parse_ev("GET / HTTP/1.1\r ", parse_error::bad_crlf); - parse_ev("GET / HTTP/1.1\r\nf :", parse_error::bad_field); - } - - void testInvalidMatrix() + for_split(boost::string_ref const& s, F const& f) { using boost::asio::buffer; using boost::asio::buffer_copy; - std::string s; - - for(std::size_t n = 0;; ++n) + for(std::size_t i = 0; i < s.size(); ++i) { - // Create a request and set one octet to an invalid char - s = - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 00\r\n" - "\r\n"; - auto const len = s.size(); - if(n < len) - { - s[n] = 0; - for(std::size_t m = 1; m < len - 1; ++m) - { - // Use separately allocated buffers so - // address sanitizer has something to chew on. - // - std::unique_ptr p1(new char[m]); - std::unique_ptr p2(new char[len - m]); - auto const b1 = buffer(p1.get(), m); - auto const b2 = buffer(p2.get(), len - m); - buffer_copy(b1, buffer(s.data(), m)); - buffer_copy(b2, buffer(s.data() + m, len - m)); - null_parser p; - error_code ec; - p.write(b1, ec); - if(ec) - { - pass(); - continue; - } - p.write(b2, ec); - expect(ec); - } - } - else - { - null_parser p; - error_code ec; - p.write(buffer(s.data(), s.size()), ec); - expect(! ec, ec.message()); - break; - } - } - - for(std::size_t n = 0;; ++n) - { - // Create a response and set one octet to an invalid char - s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "0\r\n\r\n"; - auto const len = s.size(); - if(n < len) - { - s[n] = 0; - for(std::size_t m = 1; m < len - 1; ++m) - { - null_parser p; - error_code ec; - p.write(buffer(s.data(), m), ec); - if(ec) - { - pass(); - continue; - } - p.write(buffer(s.data() + m, len - m), ec); - expect(ec); - } - } - else - { - null_parser p; - error_code ec; - p.write(buffer(s.data(), s.size()), ec); - expect(! ec, ec.message()); - break; - } + // Use separately allocated buffers so + // address sanitizer has something to chew on. + // + auto const n1 = s.size() - i; + auto const n2 = i; + std::unique_ptr p1(new char[n1]); + std::unique_ptr p2(new char[n2]); + buffer_copy(buffer(p1.get(), n1), buffer(s.data(), n1)); + buffer_copy(buffer(p2.get(), n2), buffer(s.data() + n1, n2)); + f( + boost::string_ref{p1.get(), n1}, + boost::string_ref{p2.get(), n2}); } } + struct none + { + template + void + operator()(Parser const&) const + { + } + }; + + template void - testRandomReq(std::size_t N) + good(int onBodyRv, std::string const& s, F const& f) { using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - message_fuzz mg; - for(std::size_t i = 0; i < N; ++i) - { - std::string s; + for_split(s, + [&](boost::string_ref const& s1, boost::string_ref const& s2) { - streambuf sb; - mg.request(sb); - s.reserve(buffer_size(sb.data())); - for(auto const& b : sb.data()) - s.append(buffer_cast(b), - buffer_size(b)); - } - null_parser p; - for(std::size_t j = 1; j < s.size() - 1; ++j) - { - error_code ec; - p.write(buffer(&s[0], j), ec); - if(! expect(! ec, ec.message())) + static std::size_t constexpr Limit = 200; + std::size_t n; + for(n = 0; n < Limit; ++n) { - log << escaped_string(s); + test::fail_counter fc(n); + fail_parser p(fc); + p.on_body_rv(onBodyRv); + error_code ec; + p.write(buffer(s1.data(), s1.size()), ec); + if(ec == test::fail_error) + continue; + if(! expect(! ec)) + break; + if(! expect(s2.empty() || ! p.complete())) + break; + p.write(buffer(s2.data(), s2.size()), ec); + if(ec == test::fail_error) + continue; + if(! expect(! ec)) + break; + p.write_eof(ec); + if(ec == test::fail_error) + continue; + if(! expect(! ec)) + break; + expect(p.complete()); + f(p); break; } - if(! p.complete()) + expect(n < Limit); + }); + } + + template + void + good(std::string const& s, F const& f = {}) + { + return good(0, s, f); + } + + template + void + bad(int onBodyRv, std::string const& s, error_code ev) + { + using boost::asio::buffer; + for_split(s, + [&](boost::string_ref const& s1, boost::string_ref const& s2) + { + static std::size_t constexpr Limit = 200; + std::size_t n; + for(n = 0; n < Limit; ++n) { - p.write(buffer(&s[j], s.size() - j), ec); - if(! expect(! ec, ec.message())) + test::fail_counter fc(n); + fail_parser p(fc); + p.on_body_rv(onBodyRv); + error_code ec; + p.write(buffer(s1.data(), s1.size()), ec); + if(ec == test::fail_error) + continue; + if(ec) { - log << escaped_string(s); + expect((ec && ! ev) || ec == ev); break; } - } - if(! expect(p.complete())) + if(! expect(! p.complete())) + break; + if(! s2.empty()) + { + p.write(buffer(s2.data(), s2.size()), ec); + if(ec == test::fail_error) + continue; + if(ec) + { + expect((ec && ! ev) || ec == ev); + break; + } + if(! expect(! p.complete())) + break; + } + p.write_eof(ec); + if(ec == test::fail_error) + continue; + expect(! p.complete()); + expect((ec && ! ev) || ec == ev); break; - if(! p.keep_alive()) - { - p.~null_parser(); - new(&p) null_parser{}; } - } + expect(n < Limit); + }); + } + + template + void + bad(std::string const& s, error_code ev = {}) + { + return bad(0, s, ev); + } + + //-------------------------------------------------------------------------- + + class version + { + suite& s_; + unsigned major_; + unsigned minor_; + + public: + version(suite& s, unsigned major, unsigned minor) + : s_(s) + , major_(major) + , minor_(minor) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.http_major() == major_); + s_.expect(p.http_minor() == minor_); + } + }; + + class status + { + suite& s_; + unsigned code_; + public: + status(suite& s, int code) + : s_(s) + , code_(code) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.status_code() == code_); + } + }; + + void testRequestLine() + { + /* + request-line = method SP request-target SP HTTP-version CRLF + method = token + request-target = origin-form / absolute-form / authority-form / asterisk-form + HTTP-version = "HTTP/" DIGIT "." DIGIT + */ + good("GET /x HTTP/1.0\r\n\r\n"); + good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); + good("GET / HTTP/1.0\r\n\r\n", version{*this, 1, 0}); + good("G / HTTP/1.1\r\n\r\n", version{*this, 1, 1}); + // VFALCO TODO various forms of good request-target (uri) + good("GET / HTTP/0.1\r\n\r\n", version{*this, 0, 1}); + good("GET / HTTP/2.3\r\n\r\n", version{*this, 2, 3}); + good("GET / HTTP/4.5\r\n\r\n", version{*this, 4, 5}); + good("GET / HTTP/6.7\r\n\r\n", version{*this, 6, 7}); + good("GET / HTTP/8.9\r\n\r\n", version{*this, 8, 9}); + + bad("\tGET / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); + bad("GET\x01 / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); + bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); + bad("GET \x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); + bad("GET /\x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); + // VFALCO TODO various forms of bad request-target (uri) + bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / _TTP/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / H_TP/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HT_P/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTT_/1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP_1.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/01.2\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/3.45\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/67.89\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/x.0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.x\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0 \r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1_0\r\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0\n" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0\n\r" "\r\n", parse_error::bad_version); + bad("GET / HTTP/1.0\r\r\n" "\r\n", parse_error::bad_crlf); + + // write a bad request line in 2 pieces + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buffer_cat( + buf("GET / "), buf("_TTP/1.1\r\n"), + buf("\r\n") + ), ec); + expect(ec == parse_error::bad_version); } } - void - testRandomResp(std::size_t N) + void testStatusLine() { - using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - message_fuzz mg; - for(std::size_t i = 0; i < N; ++i) + /* + status-line = HTTP-version SP status-code SP reason-phrase CRLF + HTTP-version = "HTTP/" DIGIT "." DIGIT + status-code = 3DIGIT + reason-phrase = *( HTAB / SP / VCHAR / obs-text ) + */ + good("HTTP/0.1 200 OK\r\n" "\r\n", version{*this, 0, 1}); + good("HTTP/2.3 200 OK\r\n" "\r\n", version{*this, 2, 3}); + good("HTTP/4.5 200 OK\r\n" "\r\n", version{*this, 4, 5}); + good("HTTP/6.7 200 OK\r\n" "\r\n", version{*this, 6, 7}); + good("HTTP/8.9 200 OK\r\n" "\r\n", version{*this, 8, 9}); + good("HTTP/1.0 000 OK\r\n" "\r\n", status{*this, 0}); + good("HTTP/1.1 012 OK\r\n" "\r\n", status{*this, 12}); + good("HTTP/1.0 345 OK\r\n" "\r\n", status{*this, 345}); + good("HTTP/1.0 678 OK\r\n" "\r\n", status{*this, 678}); + good("HTTP/1.0 999 OK\r\n" "\r\n", status{*this, 999}); + good("HTTP/1.0 200 \tX\r\n" "\r\n", version{*this, 1, 0}); + good("HTTP/1.1 200 X\r\n" "\r\n", version{*this, 1, 1}); + good("HTTP/1.0 200 \r\n" "\r\n"); + good("HTTP/1.1 200 X \r\n" "\r\n"); + good("HTTP/1.1 200 X\t\r\n" "\r\n"); + good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); + good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); + + bad("\rHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("\nHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad(" HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("_TTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("H_TP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HT_P/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTT_/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP_1.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/01.2 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/3.45 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/67.89 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/x.0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/1.x 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/1_0 200 OK\r\n" "\r\n", parse_error::bad_version); + bad("HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 0 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 12 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 3456 OK\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 200\r\n" "\r\n", parse_error::bad_status); + bad("HTTP/1.0 200 \n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 \x01\r\n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 \x7f\r\n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 OK\n" "\r\n", parse_error::bad_reason); + bad("HTTP/1.0 200 OK\r\r\n" "\r\n", parse_error::bad_crlf); + } + + //-------------------------------------------------------------------------- + + void testHeaders() + { + /* + header-field = field-name ":" OWS field-value OWS + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + */ + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("f:\r\n")); + good(m("f: \r\n")); + good(m("f:\t\r\n")); + good(m("f: \t\r\n")); + good(m("f: v\r\n")); + good(m("f:\tv\r\n")); + good(m("f:\tv \r\n")); + good(m("f:\tv\t\r\n")); + good(m("f:\tv\t \r\n")); + good(m("f:\r\n \r\n")); + good(m("f:v\r\n")); + good(m("f: v\r\n u\r\n")); + good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); + good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); + + bad(m(" f: v\r\n"), parse_error::bad_field); + bad(m("\tf: v\r\n"), parse_error::bad_field); + bad(m("f : v\r\n"), parse_error::bad_field); + bad(m("f\t: v\r\n"), parse_error::bad_field); + bad(m("f: \n\r\n"), parse_error::bad_value); + bad(m("f: v\r \r\n"), parse_error::bad_crlf); + bad(m("f: \r v\r\n"), parse_error::bad_crlf); + bad("GET / HTTP/1.1\r\n\r \n", parse_error::bad_crlf); + } + + //-------------------------------------------------------------------------- + + class flags + { + suite& s_; + std::size_t value_; + + public: + flags(suite& s, std::size_t value) + : s_(s) + , value_(value) { - std::string s; - { - streambuf sb; - mg.response(sb); - s.reserve(buffer_size(sb.data())); - for(auto const& b : sb.data()) - s.append(buffer_cast(b), - buffer_size(b)); - } - null_parser p; - for(std::size_t j = 1; j < s.size() - 1; ++j) - { - error_code ec; - p.write(buffer(&s[0], j), ec); - if(! expect(! ec, ec.message())) - { - log << escaped_string(s); - break; - } - if(! p.complete()) - { - p.write(buffer(&s[j], s.size() - j), ec); - if(! expect(! ec, ec.message())) - { - log << escaped_string(s); - break; - } - } - if(! expect(p.complete())) - break; - if(! p.keep_alive()) - { - p.~null_parser(); - new(&p) null_parser{}; - } - } } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.flags() == value_); + } + }; + + class keepalive_f + { + suite& s_; + bool value_; + + public: + keepalive_f(suite& s, bool value) + : s_(s) + , value_(value) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.keep_alive() == value_); + } + }; + + void testConnectionHeader() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + auto const cn = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; + }; + auto const keepalive = + [&](bool v) + { + return keepalive_f{*this, v}; + }; + + good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn(",close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn(" close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("\tclose\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close,\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\t\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn(" ,\t,,close,, ,\t,,\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("\r\n close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\r\n \r\n"), flags{*this, parse_flag::connection_close}); + good(cn("any,close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close,any\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("any\r\n ,close\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close\r\n ,any\r\n"), flags{*this, parse_flag::connection_close}); + good(cn("close,close\r\n"), flags{*this, parse_flag::connection_close}); // weird but allowed + + good(cn("keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t \r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t ,x\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("\r\n keep-alive \t\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n \t \r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\r\n \r\n"), flags{*this, parse_flag::connection_keep_alive}); + + good(cn("upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t \r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t ,x\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("\r\n upgrade \t\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n \t \r\n"), flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\r\n \r\n"), flags{*this, parse_flag::connection_upgrade}); + + good(cn("close,keep-alive\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + good(cn("upgrade,keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + good(cn("upgrade,\r\n keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + good(cn("close,keep-alive,upgrade\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + good("GET / HTTP/1.1\r\n\r\n", keepalive(true)); + good("GET / HTTP/1.0\r\n\r\n", keepalive(false)); + good("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", keepalive(true)); + good("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", keepalive(false)); + + good(cn("x\r\n"), flags{*this, 0}); + good(cn("x,y\r\n"), flags{*this, 0}); + good(cn("x ,y\r\n"), flags{*this, 0}); + good(cn("x\t,y\r\n"), flags{*this, 0}); + good(cn("keep\r\n"), flags{*this, 0}); + good(cn(",keep\r\n"), flags{*this, 0}); + good(cn(" keep\r\n"), flags{*this, 0}); + good(cn("\tnone\r\n"), flags{*this, 0}); + good(cn("keep,\r\n"), flags{*this, 0}); + good(cn("keep\t\r\n"), flags{*this, 0}); + good(cn("keep\r\n"), flags{*this, 0}); + good(cn(" ,\t,,keep,, ,\t,,\r\n"), flags{*this, 0}); + good(cn("\r\n keep\r\n"), flags{*this, 0}); + good(cn("keep\r\n \r\n"), flags{*this, 0}); + good(cn("closet\r\n"), flags{*this, 0}); + good(cn(",closet\r\n"), flags{*this, 0}); + good(cn(" closet\r\n"), flags{*this, 0}); + good(cn("\tcloset\r\n"), flags{*this, 0}); + good(cn("closet,\r\n"), flags{*this, 0}); + good(cn("closet\t\r\n"), flags{*this, 0}); + good(cn("closet\r\n"), flags{*this, 0}); + good(cn(" ,\t,,closet,, ,\t,,\r\n"), flags{*this, 0}); + good(cn("\r\n closet\r\n"), flags{*this, 0}); + good(cn("closet\r\n \r\n"), flags{*this, 0}); + good(cn("clog\r\n"), flags{*this, 0}); + good(cn("key\r\n"), flags{*this, 0}); + good(cn("uptown\r\n"), flags{*this, 0}); + good(cn("keeper\r\n \r\n"), flags{*this, 0}); + good(cn("keep-alively\r\n \r\n"), flags{*this, 0}); + good(cn("up\r\n \r\n"), flags{*this, 0}); + good(cn("upgrader\r\n \r\n"), flags{*this, 0}); + good(cn("none\r\n"), flags{*this, 0}); + good(cn("\r\n none\r\n"), flags{*this, 0}); + + good(m("ConnectioX: close\r\n"), flags{*this, 0}); + good(m("Condor: close\r\n"), flags{*this, 0}); + good(m("Connect: close\r\n"), flags{*this, 0}); + good(m("Connections: close\r\n"), flags{*this, 0}); + + good(m("Proxy-Connection: close\r\n"), flags{*this, parse_flag::connection_close}); + good(m("Proxy-Connection: keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); + good(m("Proxy-Connection: upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); + good(m("Proxy-ConnectioX: none\r\n"), flags{*this, 0}); + good(m("Proxy-Connections: 1\r\n"), flags{*this, 0}); + good(m("Proxy-Connotes: see-also\r\n"), flags{*this, 0}); + + bad(cn("["), parse_error::bad_value); + bad(cn("\"\r\n"), parse_error::bad_value); + bad(cn("close[\r\n"), parse_error::bad_value); + bad(cn("close [\r\n"), parse_error::bad_value); + bad(cn("close, upgrade [\r\n"), parse_error::bad_value); + bad(cn("upgrade[]\r\n"), parse_error::bad_value); + bad(cn("keep\r\n -alive\r\n"), parse_error::bad_value); + bad(cn("keep-alive[\r\n"), parse_error::bad_value); + bad(cn("keep-alive []\r\n"), parse_error::bad_value); + bad(cn("no[ne]\r\n"), parse_error::bad_value); + } + + void testContentLengthHeader() + { + auto const length = + [&](std::string const& s, std::uint64_t v) + { + good(1, s, + [&](fail_parser const& p) + { + expect(p.content_length() == v); + if(v != no_content_length) + expect(p.flags() & parse_flag::contentlength); + }); + }; + auto const c = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; + }; + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + + length(c("0\r\n"), 0); + length(c("00\r\n"), 0); + length(c("1\r\n"), 1); + length(c("01\r\n"), 1); + length(c("9\r\n"), 9); + length(c("123456789\r\n"), 123456789); + length(c("42 \r\n"), 42); + length(c("42\t\r\n"), 42); + length(c("42 \t \r\n"), 42); + length(c("42\r\n \t \r\n"), 42); + + good(m("Content-LengtX: 0\r\n"), flags{*this, 0}); + good(m("Content-Lengths: many\r\n"), flags{*this, 0}); + good(m("Content: full\r\n"), flags{*this, 0}); + + bad(c("\r\n"), parse_error::bad_content_length); + bad(c("18446744073709551616\r\n"), parse_error::bad_content_length); + bad(c("0 0\r\n"), parse_error::bad_content_length); + bad(c("0 1\r\n"), parse_error::bad_content_length); + bad(c(",\r\n"), parse_error::bad_content_length); + bad(c("0,\r\n"), parse_error::bad_content_length); + bad(m( + "Content-Length: 0\r\nContent-Length: 0\r\n"), parse_error::bad_content_length); + } + + void testTransferEncodingHeader() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + auto const ce = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; + }; + auto const te = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; + }; + good(ce("chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked\t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked \t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(" chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("\tchunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked ,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked, \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(", chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(" ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("chunked\r\n \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("\r\n ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("gzip, chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("gzip, chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + good(ce("gzip, \r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + + // Technically invalid but beyond the parser's scope to detect + good(ce("custom;key=\",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); + + good(te("gzip\r\n"), flags{*this, 0}); + good(te("chunked, gzip\r\n"), flags{*this, 0}); + good(te("chunked\r\n , gzip\r\n"), flags{*this, 0}); + good(te("chunked,\r\n gzip\r\n"), flags{*this, 0}); + good(te("chunked,\r\n ,gzip\r\n"), flags{*this, 0}); + good(te("bigchunked\r\n"), flags{*this, 0}); + good(te("chunk\r\n ked\r\n"), flags{*this, 0}); + good(te("bar\r\n ley chunked\r\n"), flags{*this, 0}); + good(te("barley\r\n chunked\r\n"), flags{*this, 0}); + + good(m("Transfer-EncodinX: none\r\n"), flags{*this, 0}); + good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); + good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); + + bad(1, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", parse_error::illegal_content_length); + } + + void testUpgradeHeader() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("Upgrade:\r\n"), flags{*this, parse_flag::upgrade}); + good(m("Upgrade: \r\n"), flags{*this, parse_flag::upgrade}); + good(m("Upgrade: yes\r\n"), flags{*this, parse_flag::upgrade}); + + good(m("Up: yes\r\n"), flags{*this, 0}); + good(m("UpgradX: none\r\n"), flags{*this, 0}); + good(m("Upgrades: 2\r\n"), flags{*this, 0}); + good(m("Upsample: 4x\r\n"), flags{*this, 0}); + + good( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](fail_parser const& p) + { + expect(p.upgrade()); + }); + } + + //-------------------------------------------------------------------------- + + class body_f + { + suite& s_; + std::string const& body_; + + public: + body_f(body_f&&) = default; + + body_f(suite& s, std::string const& v) + : s_(s) + , body_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.expect(p.body == body_); + } + }; + + template + static + boost::asio::const_buffers_1 + buf(char const (&s)[N]) + { + return { s, N-1 }; } void testBody() { - auto match = - [&](std::string const& body) + using boost::asio::buffer; + auto const body = + [&](std::string const& s) + { + return body_f{*this, s}; + }; + good( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "1", + body("1")); + + good( + "HTTP/1.0 200 OK\r\n" + "\r\n" + "hello", + body("hello")); + + // on_body returns 2, meaning upgrade + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.on_body_rv(2); + p.write(buf( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + } + + // write the body in 3 pieces + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890")), ec); + expect(! ec); + expect(p.complete()); + expect(! p.needs_eof()); + p.write_eof(ec); + expect(! ec); + p.write_eof(ec); + expect(! ec); + p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); + expect(ec == parse_error::connection_closed); + } + + // request without Content-Length or + // Transfer-Encoding: chunked has no body. + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + + // response without Content-Length or + // Transfer-Encoding: chunked requires eof. + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "HTTP/1.0 200 OK\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.complete()); + expect(p.needs_eof()); + p.write(buf( + "hello" + ), ec); + expect(! ec); + expect(! p.complete()); + expect(p.needs_eof()); + p.write_eof(ec); + expect(! ec); + expect(p.complete()); + p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); + expect(ec == parse_error::connection_closed); + } + + // 304 "Not Modified" response does not require eof + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "HTTP/1.0 304 Not Modified\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + + // Chunked response does not require eof + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(! p.complete()); + p.write(buf( + "0\r\n\r\n" + ), ec); + expect(! ec); + expect(! p.needs_eof()); + expect(p.complete()); + } + + // restart: 1.0 assumes Connection: close + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(ec == parse_error::connection_closed); + } + + // restart: 1.1 assumes Connection: keep-alive + { + error_code ec; + test::fail_counter fc(1000); + fail_parser p(fc); + p.write(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + p.write(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), ec); + expect(! ec); + expect(p.complete()); + } + + bad(3, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "\r\n*", parse_error::bad_on_headers_rv); + + bad(0, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n", + parse_error::short_read); + } + + void testChunkedBody() + { + auto const body = + [&](std::string const& s) + { + return body_f{*this, s}; + }; + auto const ce = + [](std::string const& s) { return - [&](test_parser const& p) - { - expect(p.body == body); - }; + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + s; }; - parse>( - "GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1")); + /* + chunked-body = *chunk + last-chunk + trailer-part + CRLF + chunk = chunk-size [ chunk-ext ] CRLF + chunk-data CRLF + chunk-size = 1*HEXDIG + last-chunk = 1*("0") [ chunk-ext ] CRLF + chunk-data = 1*OCTET ; a sequence of chunk-size octets + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string + trailer-part = *( header-field CRLF ) + */ + good(ce( + "1;xy\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123")); + good(ce( + "1;x\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match("")); + good(ce( + "1;x;y\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "1\r\n" - "a\r\n" - "0\r\n" - "\r\n", match("a")); + good(ce( + "1;i;j=2;k=\"3\"\r\n*\r\n" "0\r\n\r\n" + ), body("*")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "2\r\n" - "ab\r\n" - "0\r\n" - "\r\n", match("ab")); + good(ce( + "1\r\n" "a\r\n" "0\r\n" "\r\n" + ), body("a")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "2\r\n" - "ab\r\n" - "1\r\n" - "c\r\n" - "0\r\n" - "\r\n", match("abc")); + good(ce( + "2\r\n" "ab\r\n" "0\r\n" "\r\n" + ), body("ab")); - parse>( - "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - "10\r\n" - "1234567890123456\r\n" - "0\r\n" - "\r\n", match("1234567890123456")); + good(ce( + "2\r\n" "ab\r\n" "1\r\n" "c\r\n" "0\r\n" "\r\n" + ), body("abc")); + + good(ce( + "10\r\n" "1234567890123456\r\n" "0\r\n" "\r\n" + ), body("1234567890123456")); + + bad(ce("ffffffffffffffff0\r\n0\r\n\r\n"), parse_error::bad_content_length); + bad(ce("g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); + bad(ce("0g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); + bad(ce("0\r_\n"), parse_error::bad_crlf); + bad(ce("1\r\n*_\r\n"), parse_error::bad_crlf); + bad(ce("1\r\n*\r_\n"), parse_error::bad_crlf); + bad(ce("1;,x\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); + bad(ce("1;x,\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); + } + + void testLimits() + { + std::size_t n; + static std::size_t constexpr Limit = 100; + { + for(n = 1; n < Limit; ++n) + { + test::fail_counter fc(1000); + fail_parser p(fc); + p.set_option(headers_max_size{n}); + error_code ec; + p.write(buf( + "GET / HTTP/1.1\r\n" + "User-Agent: beast\r\n" + "\r\n" + ), ec); + if(! ec) + break; + expect(ec == parse_error::headers_too_big); + } + expect(n < Limit); + } + { + for(n = 1; n < Limit; ++n) + { + test::fail_counter fc(1000); + fail_parser p(fc); + p.set_option(headers_max_size{n}); + error_code ec; + p.write(buf( + "HTTP/1.1 200 OK\r\n" + "Server: beast\r\n" + "Content-Length: 4\r\n" + "\r\n" + "****" + ), ec); + if(! ec) + break; + expect(ec == parse_error::headers_too_big); + } + expect(n < Limit); + } + { + test::fail_counter fc(1000); + fail_parser p(fc); + p.set_option(body_max_size{2}); + error_code ec; + p.write(buf( + "HTTP/1.1 200 OK\r\n" + "Server: beast\r\n" + "Content-Length: 4\r\n" + "\r\n" + "****" + ), ec); + expect(ec == parse_error::body_too_big); + } } void run() override { testCallbacks(); - testVersion(); - testFlags(); + testRequestLine(); + testStatusLine(); testHeaders(); - testUpgrade(); - testBad(); - testInvalidMatrix(); - testRandomReq(100); - testRandomResp(100); + testConnectionHeader(); + testContentLengthHeader(); + testTransferEncodingHeader(); + testUpgradeHeader(); testBody(); + testChunkedBody(); + testLimits(); } }; diff --git a/test/http/fail_parser.hpp b/test/http/fail_parser.hpp new file mode 100644 index 00000000..003992a2 --- /dev/null +++ b/test/http/fail_parser.hpp @@ -0,0 +1,112 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot 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) +// + +#ifndef BEAST_HTTP_TEST_FAIL_PARSER_HPP +#define BEAST_HTTP_TEST_FAIL_PARSER_HPP + +#include +#include + +namespace beast { +namespace http { + +template +class fail_parser + : public basic_parser_v1> +{ + test::fail_counter& fc_; + std::uint64_t content_length_ = no_content_length; + int body_rv_ = 0; + +public: + std::string body; + + template + explicit + fail_parser(test::fail_counter& fc, Args&&... args) + : fc_(fc) + { + } + + void + on_body_rv(int rv) + { + body_rv_ = rv; + } + + // valid on successful parse + std::uint64_t + content_length() const + { + return content_length_; + } + + void on_start(error_code& ec) + { + fc_.fail(ec); + } + + void on_method(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_uri(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_reason(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_request(error_code& ec) + { + fc_.fail(ec); + } + + void on_response(error_code& ec) + { + fc_.fail(ec); + } + + void on_field(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + void on_value(boost::string_ref const&, error_code& ec) + { + fc_.fail(ec); + } + + int on_headers(std::uint64_t content_length, error_code& ec) + { + if(fc_.fail(ec)) + return 0; + content_length_ = content_length; + return body_rv_; + } + + void on_body(boost::string_ref const& s, error_code& ec) + { + if(fc_.fail(ec)) + return; + body.append(s.data(), s.size()); + } + + void on_complete(error_code& ec) + { + fc_.fail(ec); + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp index 69a46dd7..ad7f4729 100644 --- a/test/http/parse_error.cpp +++ b/test/http/parse_error.cpp @@ -38,8 +38,8 @@ public: check("http", parse_error::bad_version); check("http", parse_error::bad_crlf); check("http", parse_error::bad_request); - check("http", parse_error::bad_status_code); check("http", parse_error::bad_status); + check("http", parse_error::bad_reason); check("http", parse_error::bad_field); check("http", parse_error::bad_value); check("http", parse_error::bad_content_length); diff --git a/test/http/read.cpp b/test/http/read.cpp index 5b8280ce..d1e6c054 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -8,6 +8,8 @@ // Test that header file is self-contained. #include +#include "fail_parser.hpp" + #include #include #include @@ -24,77 +26,6 @@ class read_test , public test::enable_yield_to { public: - template - class fail_parser - : public basic_parser_v1> - { - test::fail_counter& fc_; - - public: - template - explicit - fail_parser(test::fail_counter& fc, Args&&... args) - : fc_(fc) - { - } - - void on_start(error_code& ec) - { - fc_.fail(ec); - } - - void on_method(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_uri(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_reason(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_request(error_code& ec) - { - fc_.fail(ec); - } - - void on_response(error_code& ec) - { - fc_.fail(ec); - } - - void on_field(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_value(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - int on_headers(error_code& ec) - { - fc_.fail(ec); - return 0; - } - - void on_body(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_complete(error_code& ec) - { - fc_.fail(ec); - } - }; - template void failMatrix(const char* s, yield_context do_yield) {