rfc7230 compliance, limits, and tests for basic_parser_v1:

New parser set_option function for controlling independent size limits
on headers and body. By default request and response parsers are set up
with reasonable limits to prevent resource exhaustion attacks.

* Parser adheres strictly to rfc7230
* Increased test coverage
* Headers and body maximum size limit options
This commit is contained in:
Vinnie Falco
2016-06-03 11:40:55 -04:00
parent e6d7ef35fc
commit 589e18c199
16 changed files with 2093 additions and 1320 deletions

View File

@@ -6,6 +6,8 @@
* Remove obsolete RFC2616 functions * Remove obsolete RFC2616 functions
* Add message swap members and free functions * Add message swap members and free functions
* Add HTTP field value parser containers: ext_list, param_list, token_list * 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: API Changes:
@@ -14,4 +16,6 @@ API Changes:
* "DynamicBuffer","dynabuf" renamed from "Streambuf", "streambuf". See: * "DynamicBuffer","dynabuf" renamed from "Streambuf", "streambuf". See:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4478.html#requirements.dynamic_buffers 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
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -46,3 +46,11 @@ HTTP:
* Check basic_parser_v1 against rfc7230 for leading message whitespace * Check basic_parser_v1 against rfc7230 for leading message whitespace
* Fix the order of message constructor parameters: * Fix the order of message constructor parameters:
body first then headers (since body is constructed with arguments more often) 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

View File

@@ -39,6 +39,11 @@
<member><link linkend="beast.ref.http__streambuf_body">streambuf_body</link></member> <member><link linkend="beast.ref.http__streambuf_body">streambuf_body</link></member>
<member><link linkend="beast.ref.http__string_body">string_body</link></member> <member><link linkend="beast.ref.http__string_body">string_body</link></member>
</simplelist> </simplelist>
<bridgehead renderas="sect3">Options</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.http__body_max_size">body_max_size</link></member>
<member><link linkend="beast.ref.http__headers_max_size">headers_max_size</link></member>
</simplelist>
<bridgehead renderas="sect3">Type Traits</bridgehead> <bridgehead renderas="sect3">Type Traits</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.http__is_Body">is_Body</link></member> <member><link linkend="beast.ref.http__is_Body">is_Body</link></member>

View File

@@ -13,10 +13,78 @@
namespace beast { namespace beast {
namespace test { 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<error>(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<int>(ev),
detail::get_error_category()};
}
/** A countdown to simulated failure. /** A countdown to simulated failure.
On the Nth operation, the class will fail with the specified 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 class fail_counter
{ {
@@ -31,10 +99,10 @@ public:
@param The 0-based index of the operation to fail on or after. @param The 0-based index of the operation to fail on or after.
*/ */
explicit explicit
fail_counter(std::size_t n = 0) fail_counter(std::size_t n,
error_code ev = make_error_code(fail_error))
: n_(n) : n_(n)
, ec_(boost::system::errc::make_error_code( , ec_(ev)
boost::system::errc::errc_t::invalid_argument))
{ {
} }
@@ -66,4 +134,14 @@ public:
} // test } // test
} // beast } // beast
namespace boost {
namespace system {
template<>
struct is_error_code_enum<beast::test::error>
{
static bool const value = true;
};
} // system
} // boost
#endif #endif

View File

@@ -14,6 +14,7 @@
#include <beast/core/detail/get_lowest_layer.hpp> #include <beast/core/detail/get_lowest_layer.hpp>
#include <beast/websocket/teardown.hpp> #include <beast/websocket/teardown.hpp>
#include <beast/test/fail_counter.hpp> #include <beast/test/fail_counter.hpp>
#include <boost/optional.hpp>
namespace beast { namespace beast {
namespace test { namespace test {
@@ -26,8 +27,8 @@ namespace test {
template<class NextLayer> template<class NextLayer>
class fail_stream class fail_stream
{ {
boost::optional<fail_counter> fc_;
fail_counter* pfc_; fail_counter* pfc_;
fail_counter fc_;
NextLayer next_layer_; NextLayer next_layer_;
public: public:
@@ -46,8 +47,8 @@ public:
template<class... Args> template<class... Args>
explicit explicit
fail_stream(std::size_t n, Args&&... args) fail_stream(std::size_t n, Args&&... args)
: pfc_(&fc_) : fc_(n)
, fc_(n) , pfc_(&*fc_)
, next_layer_(std::forward<Args>(args)...) , next_layer_(std::forward<Args>(args)...)
{ {
} }

View File

@@ -25,17 +25,65 @@ namespace http {
namespace parse_flag { namespace parse_flag {
enum values enum values
{ {
chunked = 1 << 0, chunked = 1,
connection_keep_alive = 1 << 1, connection_keep_alive = 2,
connection_close = 1 << 2, connection_close = 4,
connection_upgrade = 1 << 3, connection_upgrade = 8,
trailing = 1 << 4, trailing = 16,
upgrade = 1 << 5, upgrade = 32,
skipbody = 1 << 6, skipbody = 64,
contentlength = 1 << 7 contentlength = 128
}; };
} // parse_flag } // 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<std::uint64_t>::max();
/** A parser for decoding HTTP/1 wire format messages. /** A parser for decoding HTTP/1 wire format messages.
This parser is designed to efficiently parse messages in the 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. 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. Called when all the headers have been parsed successfully.
@@ -126,90 +174,12 @@ enum values
presented with request or response message. presented with request or response message.
*/ */
template<bool isRequest, class Derived> template<bool isRequest, class Derived>
class basic_parser_v1 class basic_parser_v1 : public detail::parser_base
{ {
private: private:
using self = basic_parser_v1; using self = basic_parser_v1;
typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); typedef void(self::*pmf_t)(error_code&, boost::string_ref const&);
static std::uint64_t constexpr no_content_length =
std::numeric_limits<std::uint64_t>::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 enum field_state : std::uint8_t
{ {
h_general = 0, h_general = 0,
@@ -224,25 +194,36 @@ private:
h_matching_upgrade, h_matching_upgrade,
h_connection, h_connection,
h_content_length0,
h_content_length, h_content_length,
h_content_length_ows,
h_transfer_encoding, h_transfer_encoding,
h_upgrade, h_upgrade,
h_matching_transfer_encoding_chunked, h_matching_transfer_encoding_chunked,
h_matching_connection_token_start, h_matching_transfer_encoding_general,
h_matching_connection_keep_alive, h_matching_connection_keep_alive,
h_matching_connection_close, h_matching_connection_close,
h_matching_connection_upgrade, h_matching_connection_upgrade,
h_matching_connection_token,
h_transfer_encoding_chunked, h_transfer_encoding_chunked,
h_transfer_encoding_chunked_ows,
h_connection_keep_alive, h_connection_keep_alive,
h_connection_keep_alive_ows,
h_connection_close, h_connection_close,
h_connection_close_ows,
h_connection_upgrade, 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 content_length_;
std::uint64_t nread_;
pmf_t cb_; pmf_t cb_;
state s_ : 8; state s_ : 8;
unsigned flags_ : 8; unsigned flags_ : 8;
@@ -260,10 +241,42 @@ public:
/// Copy assignment. /// Copy assignment.
basic_parser_v1& operator=(basic_parser_v1 const&) = default; basic_parser_v1& operator=(basic_parser_v1 const&) = default;
/// Constructor /// Default constructor
basic_parser_v1() basic_parser_v1();
/** Set options on the parser.
@param args One or more parser options to set.
*/
#if GENERATING_DOCS
template<class... Args>
void
set_option(Args&&... args)
#else
template<class A1, class A2, class... An>
void
set_option(A1&& a1, A2&& a2, An&&... an)
#endif
{ {
init(std::integral_constant<bool, isRequest>{}); set_option(std::forward<A1>(a1));
set_option(std::forward<A2>(a2),
std::forward<An>(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. /// Returns internal flags associated with the parser.
@@ -413,17 +426,48 @@ private:
} }
void void
init(std::true_type) reset(std::true_type)
{ {
s_ = s_req_start; s_ = s_req_start;
} }
void void
init(std::false_type) reset(std::false_type)
{ {
s_ = s_res_start; s_ = s_res_start;
} }
void
reset()
{
h_left_ = h_max_;
b_left_ = b_max_;
reset(std::integral_constant<bool, isRequest>{});
}
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<bool, isRequest>{});
reset();
}
bool bool
needs_eof(std::true_type) const; needs_eof(std::true_type) const;
@@ -584,7 +628,7 @@ private:
{ {
template<class T, class R = std::is_same<int, template<class T, class R = std::is_same<int,
decltype(std::declval<T>().on_headers( decltype(std::declval<T>().on_headers(
std::declval<error_code&>()))>> std::declval<std::uint64_t>(), std::declval<error_code&>()))>>
static R check(int); static R check(int);
template <class> template <class>
static std::false_type check(...); static std::false_type check(...);
@@ -661,9 +705,17 @@ private:
void call_on_method(error_code& ec, void call_on_method(error_code& ec,
boost::string_ref const& s) boost::string_ref const& s)
{ {
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_method(ec, s, std::integral_constant<bool, call_on_method(ec, s, std::integral_constant<bool,
isRequest && has_on_method<Derived>::value>{}); isRequest && has_on_method<Derived>::value>{});
} }
else
{
ec = parse_error::headers_too_big;
}
}
void call_on_uri(error_code& ec, void call_on_uri(error_code& ec,
boost::string_ref const& s, std::true_type) boost::string_ref const& s, std::true_type)
@@ -678,9 +730,17 @@ private:
void call_on_uri(error_code& ec, boost::string_ref const& s) void call_on_uri(error_code& ec, boost::string_ref const& s)
{ {
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_uri(ec, s, std::integral_constant<bool, call_on_uri(ec, s, std::integral_constant<bool,
isRequest && has_on_uri<Derived>::value>{}); isRequest && has_on_uri<Derived>::value>{});
} }
else
{
ec = parse_error::headers_too_big;
}
}
void call_on_reason(error_code& ec, void call_on_reason(error_code& ec,
boost::string_ref const& s, std::true_type) boost::string_ref const& s, std::true_type)
@@ -695,9 +755,17 @@ private:
void call_on_reason(error_code& ec, boost::string_ref const& s) void call_on_reason(error_code& ec, boost::string_ref const& s)
{ {
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_reason(ec, s, std::integral_constant<bool, call_on_reason(ec, s, std::integral_constant<bool,
! isRequest && has_on_reason<Derived>::value>{}); ! isRequest && has_on_reason<Derived>::value>{});
} }
else
{
ec = parse_error::headers_too_big;
}
}
void call_on_request(error_code& ec, std::true_type) void call_on_request(error_code& ec, std::true_type)
{ {
@@ -742,8 +810,16 @@ private:
void call_on_field(error_code& ec, boost::string_ref const& s) void call_on_field(error_code& ec, boost::string_ref const& s)
{ {
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_field(ec, s, has_on_field<Derived>{}); call_on_field(ec, s, has_on_field<Derived>{});
} }
else
{
ec = parse_error::headers_too_big;
}
}
void call_on_value(error_code& ec, void call_on_value(error_code& ec,
boost::string_ref const& s, std::true_type) boost::string_ref const& s, std::true_type)
@@ -758,22 +834,32 @@ private:
void call_on_value(error_code& ec, boost::string_ref const& s) void call_on_value(error_code& ec, boost::string_ref const& s)
{ {
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_value(ec, s, has_on_value<Derived>{}); call_on_value(ec, s, has_on_value<Derived>{});
} }
else
int call_on_headers(error_code& ec, std::true_type)
{ {
return impl().on_headers(ec); ec = parse_error::headers_too_big;
}
} }
int call_on_headers(error_code& ec, std::false_type) int call_on_headers(error_code& ec,
std::uint64_t content_length, std::true_type)
{
return impl().on_headers(content_length, ec);
}
int call_on_headers(error_code& ec, std::uint64_t, std::false_type)
{ {
return 0; return 0;
} }
int call_on_headers(error_code& ec) int call_on_headers(error_code& ec)
{ {
return call_on_headers(ec, has_on_headers<Derived>{}); return call_on_headers(ec, content_length_,
has_on_headers<Derived>{});
} }
void call_on_body(error_code& ec, void call_on_body(error_code& ec,
@@ -789,8 +875,16 @@ private:
void call_on_body(error_code& ec, boost::string_ref const& s) void call_on_body(error_code& ec, boost::string_ref const& s)
{ {
if(! b_max_ || s.size() <= b_left_)
{
b_left_ -= s.size();
call_on_body(ec, s, has_on_body<Derived>{}); call_on_body(ec, s, has_on_body<Derived>{});
} }
else
{
ec = parse_error::body_too_big;
}
}
void call_on_complete(error_code& ec, std::true_type) void call_on_complete(error_code& ec, std::true_type)
{ {

View File

@@ -17,137 +17,6 @@ namespace beast {
namespace http { namespace http {
namespace detail { namespace detail {
// '0'...'9'
inline
bool
is_digit(char c)
{
return c >= '0' && c <= '9';
}
inline
bool
is_token(char c)
{
/* token = 1*<any CHAR except CTLs or separators>
CHAR = <any US-ASCII character (octets 0 - 127)>
sep = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
*/
static std::array<char, 256> 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<std::uint8_t>(c)] != 0;
}
inline
bool
is_text(char c)
{
// TEXT = <any OCTET except CTLs, but including LWS>
static std::array<char, 256> 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<std::uint8_t>(c)] != 0;
}
// converts to lower case,
// returns 0 if not a valid token char
//
inline
char
to_field_char(char c)
{
/* token = 1*<any CHAR except CTLs or separators>
CHAR = <any US-ASCII character (octets 0 - 127)>
sep = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
*/
static std::array<char, 256> 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<std::uint8_t>(c)];
}
// converts to lower case,
// returns 0 if not a valid text char
//
inline
char
to_value_char(char c)
{
// TEXT = <any OCTET except CTLs, but including LWS>
static std::array<std::uint8_t, 256> 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<char>(tab[static_cast<std::uint8_t>(c)]);
}
inline
std::int8_t
unhex(char c)
{
static std::array<std::int8_t, 256> 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<std::uint8_t>(c)];
}
template<class = void> template<class = void>
struct parser_str_t struct parser_str_t
{ {
@@ -196,6 +65,82 @@ parser_str_t<_>::transfer_encoding[18];
using parser_str = parser_str_t<>; 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 } // detail
} // http } // http
} // beast } // beast

View File

@@ -17,6 +17,56 @@ namespace beast {
namespace http { namespace http {
namespace detail { namespace detail {
inline
bool
is_digit(char c)
{
return c >= '0' && c <= '9';
}
inline
bool
is_alpha(char c)
{
static std::array<char, 256> 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<std::uint8_t>(c)] != 0;
}
inline
bool
is_text(char c)
{
// TEXT = <any OCTET except CTLs, but including LWS>
static std::array<char, 256> 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<std::uint8_t>(c)] != 0;
}
inline inline
bool bool
is_tchar(char c) is_tchar(char c)
@@ -27,7 +77,7 @@ is_tchar(char c)
"^" | "_" | "`" | "|" | "~" | "^" | "_" | "`" | "|" | "~" |
DIGIT | ALPHA DIGIT | ALPHA
*/ */
static std::array<bool, 256> constexpr tab = {{ static std::array<char, 256> 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, 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, // 16
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 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, 1, 1, 1, 1, 1, // 96
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112
}}; }};
return tab[static_cast<std::uint8_t>(c)]; return tab[static_cast<std::uint8_t>(c)] != 0;
} }
inline inline
@@ -97,6 +147,79 @@ is_qpchar(char c)
return tab[static_cast<std::uint8_t>(c)]; return tab[static_cast<std::uint8_t>(c)];
} }
// converts to lower case,
// returns 0 if not a valid token char
//
inline
char
to_field_char(char c)
{
/* token = 1*<any CHAR except CTLs or separators>
CHAR = <any US-ASCII character (octets 0 - 127)>
sep = "(" | ")" | "<" | ">" | "@"
| "," | ";" | ":" | "\" | <">
| "/" | "[" | "]" | "?" | "="
| "{" | "}" | SP | HT
*/
static std::array<char, 256> 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<std::uint8_t>(c)];
}
// converts to lower case,
// returns 0 if not a valid text char
//
inline
char
to_value_char(char c)
{
// TEXT = <any OCTET except CTLs, but including LWS>
static std::array<std::uint8_t, 256> 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<char>(tab[static_cast<std::uint8_t>(c)]);
}
inline
std::int8_t
unhex(char c)
{
static std::array<std::int8_t, 256> 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<std::uint8_t>(c)];
}
template<class FwdIt> template<class FwdIt>
void void
skip_ows(FwdIt& it, FwdIt const& end) skip_ows(FwdIt& it, FwdIt const& end)

File diff suppressed because it is too large Load Diff

View File

@@ -23,8 +23,8 @@ enum class parse_error
bad_crlf, bad_crlf,
bad_request, bad_request,
bad_status_code,
bad_status, bad_status,
bad_reason,
bad_field, bad_field,
bad_value, bad_value,
@@ -33,7 +33,11 @@ enum class parse_error
bad_on_headers_rv, bad_on_headers_rv,
invalid_chunk_size, invalid_chunk_size,
invalid_ext_name,
invalid_ext_val,
headers_too_big,
body_too_big,
short_read, short_read,
general general
@@ -60,7 +64,7 @@ public:
return "bad method"; return "bad method";
case parse_error::bad_uri: case parse_error::bad_uri:
return "bad Request-URI"; return "bad request-target";
case parse_error::bad_version: case parse_error::bad_version:
return "bad HTTP-Version"; return "bad HTTP-Version";
@@ -69,13 +73,13 @@ public:
return "missing CRLF"; return "missing CRLF";
case parse_error::bad_request: case parse_error::bad_request:
return "bad Request-Line"; return "bad reason-phrase";
case parse_error::bad_status_code:
return "bad Status-Code";
case parse_error::bad_status: 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: case parse_error::bad_field:
return "bad field token"; return "bad field token";
@@ -95,6 +99,18 @@ public:
case parse_error::invalid_chunk_size: case parse_error::invalid_chunk_size:
return "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: case parse_error::short_read:
return "unexpected end of data"; return "unexpected end of data";

View File

@@ -172,7 +172,7 @@ private:
m_.reason = std::move(this->reason_); m_.reason = std::move(this->reason_);
} }
int on_headers(error_code&) int on_headers(std::uint64_t, error_code&)
{ {
flush(); flush();
m_.version = 10 * this->http_major() + this->http_minor(); m_.version = 10 * this->http_major() + this->http_minor();

View File

@@ -6,6 +6,8 @@ GroupSources(test/http "/")
add_executable (http-tests add_executable (http-tests
${BEAST_INCLUDES} ${BEAST_INCLUDES}
message_fuzz.hpp
fail_parser.hpp
../../extras/beast/unit_test/main.cpp ../../extras/beast/unit_test/main.cpp
basic_dynabuf_body.cpp basic_dynabuf_body.cpp
basic_headers.cpp basic_headers.cpp
@@ -35,6 +37,7 @@ endif()
add_executable (bench-tests add_executable (bench-tests
${BEAST_INCLUDES} ${BEAST_INCLUDES}
nodejs_parser.hpp
../../extras/beast/unit_test/main.cpp ../../extras/beast/unit_test/main.cpp
nodejs_parser.cpp nodejs_parser.cpp
parser_bench.cpp parser_bench.cpp

File diff suppressed because it is too large Load Diff

112
test/http/fail_parser.hpp Normal file
View File

@@ -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 <beast/http/basic_parser_v1.hpp>
#include <beast/test/fail_counter.hpp>
namespace beast {
namespace http {
template<bool isRequest>
class fail_parser
: public basic_parser_v1<isRequest, fail_parser<isRequest>>
{
test::fail_counter& fc_;
std::uint64_t content_length_ = no_content_length;
int body_rv_ = 0;
public:
std::string body;
template<class... Args>
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

View File

@@ -38,8 +38,8 @@ public:
check("http", parse_error::bad_version); check("http", parse_error::bad_version);
check("http", parse_error::bad_crlf); check("http", parse_error::bad_crlf);
check("http", parse_error::bad_request); check("http", parse_error::bad_request);
check("http", parse_error::bad_status_code);
check("http", parse_error::bad_status); check("http", parse_error::bad_status);
check("http", parse_error::bad_reason);
check("http", parse_error::bad_field); check("http", parse_error::bad_field);
check("http", parse_error::bad_value); check("http", parse_error::bad_value);
check("http", parse_error::bad_content_length); check("http", parse_error::bad_content_length);

View File

@@ -8,6 +8,8 @@
// Test that header file is self-contained. // Test that header file is self-contained.
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
#include "fail_parser.hpp"
#include <beast/http/headers.hpp> #include <beast/http/headers.hpp>
#include <beast/http/streambuf_body.hpp> #include <beast/http/streambuf_body.hpp>
#include <beast/test/fail_stream.hpp> #include <beast/test/fail_stream.hpp>
@@ -24,77 +26,6 @@ class read_test
, public test::enable_yield_to , public test::enable_yield_to
{ {
public: public:
template<bool isRequest>
class fail_parser
: public basic_parser_v1<isRequest, fail_parser<isRequest>>
{
test::fail_counter& fc_;
public:
template<class... Args>
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<bool isRequest> template<bool isRequest>
void failMatrix(const char* s, yield_context do_yield) void failMatrix(const char* s, yield_context do_yield)
{ {