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
* 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
--------------------------------------------------------------------------------

View File

@@ -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

View File

@@ -39,6 +39,11 @@
<member><link linkend="beast.ref.http__streambuf_body">streambuf_body</link></member>
<member><link linkend="beast.ref.http__string_body">string_body</link></member>
</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>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.http__is_Body">is_Body</link></member>

View File

@@ -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<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.
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<beast::test::error>
{
static bool const value = true;
};
} // system
} // boost
#endif

View File

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

View File

@@ -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<std::uint64_t>::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<bool isRequest, class Derived>
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<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
{
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<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.
@@ -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<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
needs_eof(std::true_type) const;
@@ -584,7 +628,7 @@ private:
{
template<class T, class R = std::is_same<int,
decltype(std::declval<T>().on_headers(
std::declval<error_code&>()))>>
std::declval<std::uint64_t>(), std::declval<error_code&>()))>>
static R check(int);
template <class>
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<bool,
isRequest && has_on_method<Derived>::value>{});
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_method(ec, s, std::integral_constant<bool,
isRequest && has_on_method<Derived>::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<bool,
isRequest && has_on_uri<Derived>::value>{});
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_uri(ec, s, std::integral_constant<bool,
isRequest && has_on_uri<Derived>::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<bool,
! isRequest && has_on_reason<Derived>::value>{});
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_reason(ec, s, std::integral_constant<bool,
! isRequest && has_on_reason<Derived>::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<Derived>{});
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_field(ec, s, has_on_field<Derived>{});
}
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<Derived>{});
if(! h_max_ || s.size() <= h_left_)
{
h_left_ -= s.size();
call_on_value(ec, s, has_on_value<Derived>{});
}
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<Derived>{});
return call_on_headers(ec, content_length_,
has_on_headers<Derived>{});
}
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<Derived>{});
if(! b_max_ || s.size() <= b_left_)
{
b_left_ -= s.size();
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)

View File

@@ -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*<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>
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

View File

@@ -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<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
bool
is_tchar(char c)
@@ -27,7 +77,7 @@ is_tchar(char c)
"^" | "_" | "`" | "|" | "~" |
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, // 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<std::uint8_t>(c)];
return tab[static_cast<std::uint8_t>(c)] != 0;
}
inline
@@ -97,6 +147,79 @@ is_qpchar(char 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>
void
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_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";

View File

@@ -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();

View File

@@ -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

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_crlf);
check("http", parse_error::bad_request);
check("http", parse_error::bad_status_code);
check("http", parse_error::bad_status);
check("http", parse_error::bad_reason);
check("http", parse_error::bad_field);
check("http", parse_error::bad_value);
check("http", parse_error::bad_content_length);

View File

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