mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 14:35:52 +00:00
Parser concept, fixes:
A new concept Parser is introduced with routines to read from a stream into the parser. This solves a problem with the old read interface where messages must be default constructible and move assignable. Parser fixes: * Fix detect invalid reason-phrase octets * Fix write_eof to set the 'complete' state on success * Fix consider parse complete if eof received on empty body WebSocket: * Increase coverage
This commit is contained in:
@@ -49,6 +49,7 @@ public:
|
||||
cb_req_checker, cb_res_checker>::type
|
||||
|
||||
{
|
||||
bool start = false;
|
||||
bool field = false;
|
||||
bool value = false;
|
||||
bool headers = false;
|
||||
@@ -58,6 +59,10 @@ public:
|
||||
private:
|
||||
friend class basic_parser_v1<isRequest, cb_checker<isRequest>>;
|
||||
|
||||
void on_start(error_code&)
|
||||
{
|
||||
this->start = true;
|
||||
}
|
||||
void on_method(boost::string_ref const&, error_code&)
|
||||
{
|
||||
this->method = true;
|
||||
@@ -101,68 +106,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<bool isRequest>
|
||||
struct cb_fail
|
||||
: public basic_parser_v1<isRequest, cb_fail<isRequest>>
|
||||
|
||||
{
|
||||
std::size_t n_;
|
||||
|
||||
void fail(error_code& ec)
|
||||
{
|
||||
if(n_ > 0)
|
||||
--n_;
|
||||
if(! n_)
|
||||
ec = boost::system::errc::make_error_code(
|
||||
boost::system::errc::invalid_argument);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class basic_parser_v1<isRequest, cb_checker<isRequest>>;
|
||||
|
||||
void on_method(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_uri(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_reason(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_request(error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_response(error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_field(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_value(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
int on_headers(error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
return 0;
|
||||
}
|
||||
void on_body(boost::string_ref const&, error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
void on_complete(error_code& ec)
|
||||
{
|
||||
fail(ec);
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static
|
||||
@@ -238,99 +181,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
testFail()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
static std::size_t constexpr limit = 100;
|
||||
std::size_t n = 1;
|
||||
for(; n < limit; ++n)
|
||||
{
|
||||
error_code ec;
|
||||
basic_parser_v1<true, cb_fail<true>> p;
|
||||
p.write(buffer(s), ec);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
expect(n < limit);
|
||||
}
|
||||
{
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
static std::size_t constexpr limit = 100;
|
||||
std::size_t n = 1;
|
||||
for(; n < limit; ++n)
|
||||
{
|
||||
error_code ec;
|
||||
basic_parser_v1<false, cb_fail<false>> p;
|
||||
p.write(buffer(s), ec);
|
||||
if(! ec)
|
||||
break;
|
||||
}
|
||||
expect(n < limit);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCallbacks()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
cb_checker<true> p;
|
||||
error_code ec;
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
if( expect(! ec))
|
||||
{
|
||||
expect(p.method);
|
||||
expect(p.uri);
|
||||
expect(p.request);
|
||||
expect(p.field);
|
||||
expect(p.value);
|
||||
expect(p.headers);
|
||||
expect(p.body);
|
||||
expect(p.complete);
|
||||
}
|
||||
}
|
||||
{
|
||||
cb_checker<false> p;
|
||||
error_code ec;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
if( expect(! ec))
|
||||
{
|
||||
expect(p.reason);
|
||||
expect(p.response);
|
||||
expect(p.field);
|
||||
expect(p.value);
|
||||
expect(p.headers);
|
||||
expect(p.body);
|
||||
expect(p.complete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the entire input buffer as a valid message,
|
||||
// then parse in two pieces of all possible lengths.
|
||||
//
|
||||
@@ -339,13 +189,22 @@ public:
|
||||
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()))
|
||||
if(expect(! ec, ec.message()))
|
||||
f(p);
|
||||
f(p);
|
||||
break;
|
||||
}
|
||||
for(std::size_t i = 1; i < m.size() - 1; ++i)
|
||||
{
|
||||
@@ -354,18 +213,21 @@ public:
|
||||
p.write(buffer(&m[0], i), ec);
|
||||
if(! expect(! ec, ec.message()))
|
||||
continue;
|
||||
if(p.complete())
|
||||
{
|
||||
f(p);
|
||||
}
|
||||
else
|
||||
if(! p.complete())
|
||||
{
|
||||
p.write(buffer(&m[i], m.size() - i), ec);
|
||||
if(! expect(! ec, ec.message()))
|
||||
continue;
|
||||
expect(p.complete());
|
||||
f(p);
|
||||
}
|
||||
if(! p.complete() && p.needs_eof())
|
||||
{
|
||||
p.write_eof(ec);
|
||||
if(! expect(! ec, ec.message()))
|
||||
continue;
|
||||
}
|
||||
if(! expect(p.complete()))
|
||||
continue;
|
||||
f(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,8 +265,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// Parse a valid message with expected version
|
||||
//
|
||||
template<bool isRequest>
|
||||
@@ -432,24 +292,89 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// Check all callbacks invoked
|
||||
void
|
||||
testCallbacks()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
{
|
||||
cb_checker<true> p;
|
||||
error_code ec;
|
||||
std::string const s =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
if(expect(! ec))
|
||||
{
|
||||
expect(p.start);
|
||||
expect(p.method);
|
||||
expect(p.uri);
|
||||
expect(p.request);
|
||||
expect(p.field);
|
||||
expect(p.value);
|
||||
expect(p.headers);
|
||||
expect(p.body);
|
||||
expect(p.complete);
|
||||
}
|
||||
}
|
||||
{
|
||||
cb_checker<false> p;
|
||||
error_code ec;
|
||||
std::string const s =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Server: test\r\n"
|
||||
"Content-Length: 1\r\n"
|
||||
"\r\n"
|
||||
"*";
|
||||
p.write(buffer(s), ec);
|
||||
if(expect(! ec))
|
||||
{
|
||||
expect(p.start);
|
||||
expect(p.reason);
|
||||
expect(p.response);
|
||||
expect(p.field);
|
||||
expect(p.value);
|
||||
expect(p.headers);
|
||||
expect(p.body);
|
||||
expect(p.complete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testVersion()
|
||||
{
|
||||
version<true>("GET / HTTP/0.0\r\n\r\n", 0, 0);
|
||||
version<true>("GET / HTTP/0.1\r\n\r\n", 0, 1);
|
||||
version<true>("GET / HTTP/0.9\r\n\r\n", 0, 9);
|
||||
version<true>("GET / HTTP/1.0\r\n\r\n", 1, 0);
|
||||
version<true>("GET / HTTP/1.1\r\n\r\n", 1, 1);
|
||||
version<true>("GET / HTTP/9.9\r\n\r\n", 9, 9);
|
||||
version<true>("GET / HTTP/999.999\r\n\r\n", 999, 999);
|
||||
version <true>("GET / HTTP/0.0\r\n\r\n", 0, 0);
|
||||
version <true>("GET / HTTP/0.1\r\n\r\n", 0, 1);
|
||||
version <true>("GET / HTTP/0.9\r\n\r\n", 0, 9);
|
||||
version <true>("GET / HTTP/1.0\r\n\r\n", 1, 0);
|
||||
version <true>("GET / HTTP/1.1\r\n\r\n", 1, 1);
|
||||
version <true>("GET / HTTP/9.9\r\n\r\n", 9, 9);
|
||||
version <true>("GET / HTTP/999.999\r\n\r\n", 999, 999);
|
||||
parse_ev<true>("GET / HTTP/1000.0\r\n\r\n", parse_error::bad_version);
|
||||
parse_ev<true>("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version);
|
||||
parse_ev<true>("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version);
|
||||
parse_ev<true>("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version);
|
||||
|
||||
version <false>("HTTP/0.0 200 OK\r\n\r\n", 0, 0);
|
||||
version <false>("HTTP/0.1 200 OK\r\n\r\n", 0, 1);
|
||||
version <false>("HTTP/0.9 200 OK\r\n\r\n", 0, 9);
|
||||
version <false>("HTTP/1.0 200 OK\r\n\r\n", 1, 0);
|
||||
version <false>("HTTP/1.1 200 OK\r\n\r\n", 1, 1);
|
||||
version <false>("HTTP/9.9 200 OK\r\n\r\n", 9, 9);
|
||||
version <false>("HTTP/999.999 200 OK\r\n\r\n", 999, 999);
|
||||
parse_ev<false>("HTTP/1000.0 200 OK\r\n\r\n", parse_error::bad_version);
|
||||
parse_ev<false>("HTTP/0.1000 200 OK\r\n\r\n", parse_error::bad_version);
|
||||
parse_ev<false>("HTTP/99999999999999999999.0 200 OK\r\n\r\n", parse_error::bad_version);
|
||||
parse_ev<false>("HTTP/0.99999999999999999999 200 OK\r\n\r\n", parse_error::bad_version);
|
||||
}
|
||||
|
||||
void
|
||||
testConnection(std::string const& token,
|
||||
void testConnection(std::string const& token,
|
||||
std::uint8_t flag)
|
||||
{
|
||||
checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag);
|
||||
@@ -472,8 +397,7 @@ public:
|
||||
checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag);
|
||||
}
|
||||
|
||||
void
|
||||
testContentLength()
|
||||
void testContentLength()
|
||||
{
|
||||
std::size_t const length = 0;
|
||||
std::string const length_s =
|
||||
@@ -493,8 +417,7 @@ public:
|
||||
checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength);
|
||||
}
|
||||
|
||||
void
|
||||
testTransferEncoding()
|
||||
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);
|
||||
@@ -510,17 +433,15 @@ public:
|
||||
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()
|
||||
void testFlags()
|
||||
{
|
||||
testConnection("keep-alive",
|
||||
parse_flag::connection_keep_alive);
|
||||
testConnection("keep-alive", parse_flag::connection_keep_alive);
|
||||
testConnection("close", parse_flag::connection_close);
|
||||
testConnection("upgrade", parse_flag::connection_upgrade);
|
||||
|
||||
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();
|
||||
|
||||
@@ -537,11 +458,46 @@ public:
|
||||
"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
|
||||
testUpgrade()
|
||||
void testHeaders()
|
||||
{
|
||||
parse<null_parser<true>>(
|
||||
"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<true> const&)
|
||||
{
|
||||
});
|
||||
|
||||
parse_ev<true>(
|
||||
"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<true>(
|
||||
"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<true>("GET / HTTP/1.0\r\nContent-Length: 1e9\r\n\r\n",
|
||||
parse_error::bad_content_length);
|
||||
|
||||
parse_ev<true>("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<true> p;
|
||||
@@ -576,11 +532,10 @@ public:
|
||||
void testInvalidMatrix()
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
static std::size_t constexpr limit = 200;
|
||||
std::string s;
|
||||
std::size_t n;
|
||||
|
||||
for(n = 0; n < limit; ++n)
|
||||
for(n = 0;; ++n)
|
||||
{
|
||||
// Create a request and set one octet to an invalid char
|
||||
s =
|
||||
@@ -590,61 +545,69 @@ public:
|
||||
"Content-Length: 00\r\n"
|
||||
"\r\n";
|
||||
auto const len = s.size();
|
||||
if(n >= s.size())
|
||||
if(n < len)
|
||||
{
|
||||
pass();
|
||||
break;
|
||||
s[n] = 0;
|
||||
for(std::size_t m = 1; m < len - 1; ++m)
|
||||
{
|
||||
null_parser<true> 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);
|
||||
}
|
||||
}
|
||||
s[n] = 0;
|
||||
for(std::size_t m = 1; m < len - 1; ++m)
|
||||
else
|
||||
{
|
||||
null_parser<true> 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);
|
||||
p.write(buffer(s.data(), s.size()), ec);
|
||||
expect(! ec, ec.message());
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(n < limit);
|
||||
|
||||
for(n = 0; n < limit; ++n)
|
||||
for(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"
|
||||
"Transer-Encoding: chunked\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"10\r\n"
|
||||
"****************\r\n"
|
||||
"0\r\n\r\n";
|
||||
auto const len = s.size();
|
||||
if(n >= s.size())
|
||||
if(n < len)
|
||||
{
|
||||
pass();
|
||||
s[n] = 0;
|
||||
for(std::size_t m = 1; m < len - 1; ++m)
|
||||
{
|
||||
null_parser<false> 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<false> p;
|
||||
error_code ec;
|
||||
p.write(buffer(s.data(), s.size()), ec);
|
||||
expect(! ec, ec.message());
|
||||
break;
|
||||
}
|
||||
s[n] = 0;
|
||||
for(std::size_t m = 1; m < len - 1; ++m)
|
||||
{
|
||||
null_parser<true> 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);
|
||||
}
|
||||
}
|
||||
expect(n < limit);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -754,24 +717,30 @@ public:
|
||||
expect(p.body == body);
|
||||
};
|
||||
};
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1"));
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123"));
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match(""));
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
"1\r\n"
|
||||
"a\r\n"
|
||||
"0\r\n"
|
||||
"\r\n", match("a"));
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
"2\r\n"
|
||||
"ab\r\n"
|
||||
"0\r\n"
|
||||
"\r\n", match("ab"));
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
"2\r\n"
|
||||
@@ -780,6 +749,7 @@ public:
|
||||
"c\r\n"
|
||||
"0\r\n"
|
||||
"\r\n", match("abc"));
|
||||
|
||||
parse<test_parser<true>>(
|
||||
"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
"10\r\n"
|
||||
@@ -790,10 +760,10 @@ public:
|
||||
|
||||
void run() override
|
||||
{
|
||||
testFail();
|
||||
testCallbacks();
|
||||
testVersion();
|
||||
testFlags();
|
||||
testHeaders();
|
||||
testUpgrade();
|
||||
testBad();
|
||||
testInvalidMatrix();
|
||||
|
||||
Reference in New Issue
Block a user