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