New HTTP interfaces (API Change):

fix #123
fix #154
fix #265

This completely replaces the HTTP parser used to read and
parse incoming messages. The new version is faster than
the old one, and written in fewer lines. A summary of
changes:

* parse and async_parse renamed to read and async_read

* basic_parser is optimized for linear buffers,
  use flat_streambuf for the best performance with these
  functions:

  - http::read
  - http::read_some
  - http::async_read
  - http::async_read_some

* The overloads of read() and async_read() which take
  just a header have been removed, since they would
  throw away important parse metadata.

* The derived class callbacks for basic_parser have
  been streamlined. All strings passed to callbacks
  are presented in their entirety, instead of being
  provided in pieces.

These changes allow use-cases that were previously
difficult or impossible, such as:

- Efficient relaying

- Late body type commitment

- Expect: 100-continue handling
This commit is contained in:
Vinnie Falco
2017-07-20 08:12:15 -07:00
parent 34830a1780
commit e8be3fd7d3
74 changed files with 7106 additions and 5972 deletions
+5 -6
View File
@@ -49,15 +49,14 @@ unit-test http-tests :
../extras/beast/unit_test/main.cpp
http/basic_dynabuf_body.cpp
http/basic_fields.cpp
http/basic_parser_v1.cpp
http/basic_parser.cpp
http/concepts.cpp
http/empty_body.cpp
http/design.cpp
http/error.cpp
http/fields.cpp
http/header_parser_v1.cpp
http/header_parser.cpp
http/message.cpp
http/parse.cpp
http/parse_error.cpp
http/parser_v1.cpp
http/message_parser.cpp
http/read.cpp
http/reason.cpp
http/rfc7230.cpp
+6 -7
View File
@@ -8,19 +8,18 @@ add_executable (http-tests
${BEAST_INCLUDES}
${EXTRAS_INCLUDES}
message_fuzz.hpp
fail_parser.hpp
test_parser.hpp
../../extras/beast/unit_test/main.cpp
basic_dynabuf_body.cpp
basic_fields.cpp
basic_parser_v1.cpp
basic_parser.cpp
concepts.cpp
empty_body.cpp
design.cpp
error.cpp
fields.cpp
header_parser_v1.cpp
header_parser.cpp
message.cpp
parse.cpp
parse_error.cpp
parser_v1.cpp
message_parser.cpp
read.cpp
reason.cpp
rfc7230.cpp
+960
View File
@@ -0,0 +1,960 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/basic_parser.hpp>
#include "test_parser.hpp"
#include <beast/core/buffer_cat.hpp>
#include <beast/core/consuming_buffers.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/unit_test/suite.hpp>
namespace beast {
namespace http {
class basic_parser_test : public beast::unit_test::suite
{
public:
enum parse_flag
{
chunked = 1,
connection_keep_alive = 2,
connection_close = 4,
connection_upgrade = 8,
upgrade = 16,
};
class expect_version
{
suite& s_;
int version_;
public:
expect_version(suite& s, int version)
: s_(s)
, version_(version)
{
}
template<class Parser>
void
operator()(Parser const& p) const
{
s_.BEAST_EXPECT(p.version == version_);
}
};
class expect_status
{
suite& s_;
int status_;
public:
expect_status(suite& s, int status)
: s_(s)
, status_(status)
{
}
template<class Parser>
void
operator()(Parser const& p) const
{
s_.BEAST_EXPECT(p.status == status_);
}
};
class expect_flags
{
suite& s_;
unsigned flags_;
public:
expect_flags(suite& s, unsigned flags)
: s_(s)
, flags_(flags)
{
}
template<class Parser>
void
operator()(Parser const& p) const
{
if(flags_ & parse_flag::chunked)
s_.BEAST_EXPECT(p.is_chunked());
if(flags_ & parse_flag::connection_keep_alive)
s_.BEAST_EXPECT(p.is_keep_alive());
if(flags_ & parse_flag::connection_close)
s_.BEAST_EXPECT(! p.is_keep_alive());
if(flags_ & parse_flag::upgrade)
s_.BEAST_EXPECT(! p.is_upgrade());
}
};
class expect_keepalive
{
suite& s_;
bool v_;
public:
expect_keepalive(suite& s, bool v)
: s_(s)
, v_(v)
{
}
template<class Parser>
void
operator()(Parser const& p) const
{
s_.BEAST_EXPECT(p.is_keep_alive() == v_);
}
};
class expect_body
{
suite& s_;
std::string const& body_;
public:
expect_body(expect_body&&) = default;
expect_body(suite& s, std::string const& v)
: s_(s)
, body_(v)
{
}
template<class Parser>
void
operator()(Parser const& p) const
{
s_.BEAST_EXPECT(p.body == body_);
}
};
template<std::size_t N>
static
boost::asio::const_buffers_1
buf(char const (&s)[N])
{
return {s, N-1};
}
template<
bool isRequest, bool isDirect, class Derived>
static
std::size_t
feed(boost::asio::const_buffer buffer,
basic_parser<isRequest, isDirect, Derived>& parser,
error_code& ec)
{
using boost::asio::const_buffers_1;
std::size_t used = 0;
for(;;)
{
auto const n = parser.write(
const_buffers_1{buffer}, ec);
if(ec)
return 0;
if(n == 0)
break;
buffer = buffer + n;
used += n;
if(parser.is_complete())
break;
if(buffer_size(buffer) == 0)
break;
}
return used;
}
template<class ConstBufferSequence,
bool isRequest, bool isDirect, class Derived>
static
std::size_t
feed(ConstBufferSequence const& buffers,
basic_parser<isRequest, isDirect, Derived>& parser,
error_code& ec)
{
using boost::asio::buffer_size;
consuming_buffers<
ConstBufferSequence> cb{buffers};
std::size_t used = 0;
for(;;)
{
auto const n =
parser.write(cb, ec);
if(ec)
return 0;
if(n == 0)
break;
cb.consume(n);
used += n;
if(parser.is_complete())
break;
if(buffer_size(cb) == 0)
break;
}
return used;
}
template<
bool isRequest, bool isDirect, class Derived>
static
std::size_t
feed(boost::asio::const_buffers_1 buffers,
basic_parser<isRequest, isDirect, Derived>& parser,
error_code& ec)
{
return feed(*buffers.begin(), parser, ec);
}
template<bool isRequest, class Pred>
void
good(boost::string_ref const& s,
Pred const& pred, bool skipBody = false)
{
using boost::asio::buffer;
test_parser<isRequest> p;
if(skipBody)
p.skip_body();
error_code ec;
auto const n = feed(buffer(
s.data(), s.size()), p, ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
return;
if(! BEAST_EXPECT(n == s.size()))
return;
if(p.state() == parse_state::body_to_eof)
p.write_eof(ec);
if(BEAST_EXPECTS(! ec, ec.message()))
pred(p);
}
template<bool isRequest>
void
good(boost::string_ref const& s)
{
good<isRequest>(s,
[](test_parser<isRequest> const&)
{
});
}
template<bool isRequest>
void
bad(boost::string_ref const& s,
error_code const& ev, bool skipBody = false)
{
using boost::asio::buffer;
test_parser<isRequest> p;
if(skipBody)
p.skip_body();
error_code ec;
feed(buffer(
s.data(), s.size()), p, ec);
if(! ec && ev)
p.write_eof(ec);
BEAST_EXPECTS(ec == ev, ec.message());
}
void
testFlatten()
{
using boost::asio::buffer;
{
std::string const s =
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
for(std::size_t i = 1;
i < s.size() - 1; ++i)
{
auto const b1 =
buffer(s.data(), i);
auto const b2 = buffer(
s.data() + i, s.size() - i);
test_parser<true> p;
error_code ec;
feed(b1, p, ec);
BEAST_EXPECTS(! ec, ec.message());
feed(buffer_cat(b1, b2), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
}
{
std::string const s =
"HTTP/1.1 200 OK\r\n"
"\r\n";
for(std::size_t i = 1;
i < s.size() - 1; ++i)
{
auto const b1 =
buffer(s.data(), i);
auto const b2 = buffer(
s.data() + i, s.size() - i);
test_parser<false> p;
error_code ec;
feed(b1, p, ec);
BEAST_EXPECTS(! ec, ec.message());
ec = {};
feed(buffer_cat(b1, b2), p, ec);
BEAST_EXPECTS(! ec, ec.message());
p.write_eof(ec);
}
}
}
// Check that all callbacks are invoked
void
testCallbacks()
{
using boost::asio::buffer;
{
test_parser<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"
"*";
feed(buffer(s), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
BEAST_EXPECT(p.got_on_begin);
BEAST_EXPECT(p.got_on_field);
BEAST_EXPECT(p.got_on_header);
BEAST_EXPECT(p.got_on_body);
BEAST_EXPECT(! p.got_on_chunk);
BEAST_EXPECT(p.got_on_complete);
}
{
test_parser<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"
"*";
feed(buffer(s), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
BEAST_EXPECT(p.got_on_begin);
BEAST_EXPECT(p.got_on_field);
BEAST_EXPECT(p.got_on_header);
BEAST_EXPECT(p.got_on_body);
BEAST_EXPECT(! p.got_on_chunk);
BEAST_EXPECT(p.got_on_complete);
}
}
void
testRequestLine()
{
good<true>("GET /x HTTP/1.0\r\n\r\n");
good<true>("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n");
good<true>("GET / HTTP/1.0\r\n\r\n", expect_version{*this, 10});
good<true>("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11});
// VFALCO TODO various forms of good request-target (uri)
good<true>("GET / HTTP/0.1\r\n\r\n", expect_version{*this, 1});
good<true>("GET / HTTP/2.3\r\n\r\n", expect_version{*this, 23});
good<true>("GET / HTTP/4.5\r\n\r\n", expect_version{*this, 45});
good<true>("GET / HTTP/6.7\r\n\r\n", expect_version{*this, 67});
good<true>("GET / HTTP/8.9\r\n\r\n", expect_version{*this, 89});
bad<true>("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method);
bad<true>("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method);
bad<true>("GET / HTTP/1.0\r\n" "\r\n", error::bad_path);
bad<true>("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_path);
bad<true>("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_path);
// VFALCO TODO various forms of bad request-target (uri)
bad<true>("GET / HTTP/1.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / _TTP/1.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / H_TP/1.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / HT_P/1.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTT_/1.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP_1.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/01.2\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/3.45\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/67.89\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/x.0\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/1.x\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/1_0\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version);
bad<true>("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_line_ending);
bad<true>("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_line_ending);
}
void
testStatusLine()
{
good<false>("HTTP/0.1 200 OK\r\n" "\r\n", expect_version{*this, 1});
good<false>("HTTP/2.3 200 OK\r\n" "\r\n", expect_version{*this, 23});
good<false>("HTTP/4.5 200 OK\r\n" "\r\n", expect_version{*this, 45});
good<false>("HTTP/6.7 200 OK\r\n" "\r\n", expect_version{*this, 67});
good<false>("HTTP/8.9 200 OK\r\n" "\r\n", expect_version{*this, 89});
good<false>("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0});
good<false>("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12});
good<false>("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345});
good<false>("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678});
good<false>("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999});
good<false>("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10});
good<false>("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11});
good<false>("HTTP/1.0 200 \r\n" "\r\n");
good<false>("HTTP/1.1 200 X \r\n" "\r\n");
good<false>("HTTP/1.1 200 X\t\r\n" "\r\n");
good<false>("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n");
good<false>("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n");
bad<false>("\rHTTP/1.0 200 OK\r\n" "\r\n", error::bad_line_ending);
bad<false>("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version);
bad<false>("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status);
bad<false>("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status);
bad<false>("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status);
bad<false>("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status);
bad<false>("HTTP/1.0 200\r\n" "\r\n", error::bad_status);
bad<false>("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason);
bad<false>("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason);
bad<false>("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason);
bad<false>("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason);
bad<false>("HTTP/1.0 200 OK\r\r\n" "\r\n", error::bad_line_ending);
}
void
testFields()
{
auto const m =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\n" + s + "\r\n";
};
good<true>(m("f:\r\n"));
good<true>(m("f: \r\n"));
good<true>(m("f:\t\r\n"));
good<true>(m("f: \t\r\n"));
good<true>(m("f: v\r\n"));
good<true>(m("f:\tv\r\n"));
good<true>(m("f:\tv \r\n"));
good<true>(m("f:\tv\t\r\n"));
good<true>(m("f:\tv\t \r\n"));
good<true>(m("f:\r\n \r\n"));
good<true>(m("f:v\r\n"));
good<true>(m("f: v\r\n u\r\n"));
good<true>(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n"));
good<true>(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n"));
bad<true>(m(" f: v\r\n"), error::bad_field);
bad<true>(m("\tf: v\r\n"), error::bad_field);
bad<true>(m("f : v\r\n"), error::bad_field);
bad<true>(m("f\t: v\r\n"), error::bad_field);
bad<true>(m("f: \n\r\n"), error::bad_value);
bad<true>(m("f: v\r \r\n"), error::bad_line_ending);
bad<true>(m("f: \r v\r\n"), error::bad_line_ending);
bad<true>("GET / HTTP/1.1\r\n\r \n\r\n\r\n",error::bad_line_ending);
}
void
testConnectionField()
{
auto const m =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\n" + s + "\r\n";
};
auto const cn =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n";
};
#if 0
auto const keepalive =
[&](bool v)
{
//return keepalive_f{*this, v};
return true;
};
#endif
good<true>(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed
good<true>(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade});
// VFALCO What's up with these?
//good<true>(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive});
good<true>(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive});
good<true>(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive});
//good<true>(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade});
good<true>("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true));
good<true>("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false));
good<true>("GET / HTTP/1.0\r\n"
"Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true));
good<true>("GET / HTTP/1.1\r\n"
"Connection: close\r\n\r\n", expect_keepalive(*this, false));
good<true>(cn("x\r\n"), expect_flags{*this, 0});
good<true>(cn("x,y\r\n"), expect_flags{*this, 0});
good<true>(cn("x ,y\r\n"), expect_flags{*this, 0});
good<true>(cn("x\t,y\r\n"), expect_flags{*this, 0});
good<true>(cn("keep\r\n"), expect_flags{*this, 0});
good<true>(cn(",keep\r\n"), expect_flags{*this, 0});
good<true>(cn(" keep\r\n"), expect_flags{*this, 0});
good<true>(cn("\tnone\r\n"), expect_flags{*this, 0});
good<true>(cn("keep,\r\n"), expect_flags{*this, 0});
good<true>(cn("keep\t\r\n"), expect_flags{*this, 0});
good<true>(cn("keep\r\n"), expect_flags{*this, 0});
good<true>(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0});
good<true>(cn("\r\n keep\r\n"), expect_flags{*this, 0});
good<true>(cn("keep\r\n \r\n"), expect_flags{*this, 0});
good<true>(cn("closet\r\n"), expect_flags{*this, 0});
good<true>(cn(",closet\r\n"), expect_flags{*this, 0});
good<true>(cn(" closet\r\n"), expect_flags{*this, 0});
good<true>(cn("\tcloset\r\n"), expect_flags{*this, 0});
good<true>(cn("closet,\r\n"), expect_flags{*this, 0});
good<true>(cn("closet\t\r\n"), expect_flags{*this, 0});
good<true>(cn("closet\r\n"), expect_flags{*this, 0});
good<true>(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0});
good<true>(cn("\r\n closet\r\n"), expect_flags{*this, 0});
good<true>(cn("closet\r\n \r\n"), expect_flags{*this, 0});
good<true>(cn("clog\r\n"), expect_flags{*this, 0});
good<true>(cn("key\r\n"), expect_flags{*this, 0});
good<true>(cn("uptown\r\n"), expect_flags{*this, 0});
good<true>(cn("keeper\r\n \r\n"), expect_flags{*this, 0});
good<true>(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0});
good<true>(cn("up\r\n \r\n"), expect_flags{*this, 0});
good<true>(cn("upgrader\r\n \r\n"), expect_flags{*this, 0});
good<true>(cn("none\r\n"), expect_flags{*this, 0});
good<true>(cn("\r\n none\r\n"), expect_flags{*this, 0});
good<true>(m("ConnectioX: close\r\n"), expect_flags{*this, 0});
good<true>(m("Condor: close\r\n"), expect_flags{*this, 0});
good<true>(m("Connect: close\r\n"), expect_flags{*this, 0});
good<true>(m("Connections: close\r\n"), expect_flags{*this, 0});
good<true>(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close});
good<true>(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive});
good<true>(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade});
good<true>(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0});
good<true>(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0});
good<true>(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0});
bad<true>(cn("[\r\n"), error::bad_value);
bad<true>(cn("close[\r\n"), error::bad_value);
bad<true>(cn("close [\r\n"), error::bad_value);
bad<true>(cn("close, upgrade [\r\n"), error::bad_value);
bad<true>(cn("upgrade[]\r\n"), error::bad_value);
bad<true>(cn("keep\r\n -alive\r\n"), error::bad_value);
bad<true>(cn("keep-alive[\r\n"), error::bad_value);
bad<true>(cn("keep-alive []\r\n"), error::bad_value);
bad<true>(cn("no[ne]\r\n"), error::bad_value);
}
void
testContentLengthField()
{
auto const length =
[&](std::string const& s, std::uint64_t v)
{
good<true>(s,
[&](test_parser<true> const& p)
{
BEAST_EXPECT(p.content_length());
BEAST_EXPECT(p.content_length() && *p.content_length() == v);
}, true);
};
auto const c =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n";
};
auto const m =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\n" + s + "\r\n";
};
length(c("0\r\n"), 0);
length(c("00\r\n"), 0);
length(c("1\r\n"), 1);
length(c("01\r\n"), 1);
length(c("9\r\n"), 9);
length(c("123456789\r\n"), 123456789);
length(c("42 \r\n"), 42);
length(c("42\t\r\n"), 42);
length(c("42 \t \r\n"), 42);
// VFALCO Investigate this failure
//length(c("42\r\n \t \r\n"), 42);
good<true>(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0});
good<true>(m("Content-Lengths: many\r\n"), expect_flags{*this, 0});
good<true>(m("Content: full\r\n"), expect_flags{*this, 0});
bad<true>(c("\r\n"), error::bad_content_length);
bad<true>(c("18446744073709551616\r\n"), error::bad_content_length);
bad<true>(c("0 0\r\n"), error::bad_content_length);
bad<true>(c("0 1\r\n"), error::bad_content_length);
bad<true>(c(",\r\n"), error::bad_content_length);
bad<true>(c("0,\r\n"), error::bad_content_length);
bad<true>(m(
"Content-Length: 0\r\nContent-Length: 0\r\n"), error::bad_content_length);
}
void
testTransferEncodingField()
{
auto const m =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\n" + s + "\r\n";
};
auto const ce =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n";
};
auto const te =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n";
};
good<true>(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked});
// Technically invalid but beyond the parser's scope to detect
// VFALCO Look into this
//good<true>(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked});
good<true>(te("gzip\r\n"), expect_flags{*this, 0});
good<true>(te("chunked, gzip\r\n"), expect_flags{*this, 0});
good<true>(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0});
good<true>(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0});
good<true>(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0});
good<true>(te("bigchunked\r\n"), expect_flags{*this, 0});
good<true>(te("chunk\r\n ked\r\n"), expect_flags{*this, 0});
good<true>(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0});
good<true>(te("barley\r\n chunked\r\n"), expect_flags{*this, 0});
good<true>(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0});
good<true>(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0});
good<true>(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0});
bad<false>(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n", error::bad_transfer_encoding, true);
}
void
testUpgradeField()
{
auto const m =
[](std::string const& s)
{
return "GET / HTTP/1.1\r\n" + s + "\r\n";
};
good<true>(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade});
good<true>(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade});
good<true>(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade});
good<true>(m("Up: yes\r\n"), expect_flags{*this, 0});
good<true>(m("UpgradX: none\r\n"), expect_flags{*this, 0});
good<true>(m("Upgrades: 2\r\n"), expect_flags{*this, 0});
good<true>(m("Upsample: 4x\r\n"), expect_flags{*this, 0});
good<true>(
"GET / HTTP/1.1\r\n"
"Connection: upgrade\r\n"
"Upgrade: WebSocket\r\n"
"\r\n",
[&](test_parser<true> const& p)
{
BEAST_EXPECT(p.is_upgrade());
});
}
void testBody()
{
using boost::asio::buffer;
good<true>(
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n"
"1",
expect_body(*this, "1"));
good<false>(
"HTTP/1.0 200 OK\r\n"
"\r\n"
"hello",
expect_body(*this, "hello"));
// write the body in 3 pieces
{
error_code ec;
test_parser<true> p;
feed(buffer_cat(
buf("GET / HTTP/1.1\r\n"
"Content-Length: 10\r\n"
"\r\n"),
buf("12"),
buf("345"),
buf("67890")),
p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
// request without Content-Length or
// Transfer-Encoding: chunked has no body.
{
error_code ec;
test_parser<true> p;
feed(buf(
"GET / HTTP/1.0\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
{
error_code ec;
test_parser<true> p;
feed(buf(
"GET / HTTP/1.1\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
// response without Content-Length or
// Transfer-Encoding: chunked requires eof.
{
error_code ec;
test_parser<false> p;
feed(buf(
"HTTP/1.0 200 OK\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(! p.is_complete());
BEAST_EXPECT(p.state() == parse_state::body_to_eof);
feed(buf(
"hello"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(! p.is_complete());
BEAST_EXPECT(p.state() == parse_state::body_to_eof);
p.write_eof(ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
// 304 "Not Modified" response does not require eof
{
error_code ec;
test_parser<false> p;
feed(buf(
"HTTP/1.0 304 Not Modified\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
// Chunked response does not require eof
{
error_code ec;
test_parser<false> p;
feed(buf(
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(! p.is_complete());
feed(buf(
"0\r\n\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
// restart: 1.0 assumes Connection: close
{
error_code ec;
test_parser<true> p;
feed(buf(
"GET / HTTP/1.0\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
// restart: 1.1 assumes Connection: keep-alive
{
error_code ec;
test_parser<true> p;
feed(buf(
"GET / HTTP/1.1\r\n"
"\r\n"
), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
bad<true>(
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n",
error::partial_message);
}
template<bool isRequest>
void
check_header(
test_parser<isRequest> const& p)
{
BEAST_EXPECT(p.got_on_begin);
BEAST_EXPECT(p.got_on_field);
BEAST_EXPECT(p.got_on_header);
BEAST_EXPECT(! p.got_on_body);
BEAST_EXPECT(! p.got_on_chunk);
BEAST_EXPECT(! p.got_on_end_body);
BEAST_EXPECT(! p.got_on_complete);
BEAST_EXPECT(p.state() != parse_state::header);
}
void
testSplit()
{
#if 0
streambuf sb;
sb <<
"POST / HTTP/1.1\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****";
error_code ec;
test_parser<true> p;
p.pause();
auto n = feed(sb.data(), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.got_on_begin);
BEAST_EXPECT(p.got_on_field);
BEAST_EXPECT(p.got_on_header);
BEAST_EXPECT(! p.got_on_body);
BEAST_EXPECT(! p.got_on_chunk);
BEAST_EXPECT(! p.got_on_complete);
BEAST_EXPECT(p.state() != parse_state::header);
BEAST_EXPECT(! p.is_complete());
BEAST_EXPECT(p.body.empty());
sb.consume(n);
p.resume();
n = feed(sb.data(), p, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.got_on_begin);
BEAST_EXPECT(p.got_on_field);
BEAST_EXPECT(p.got_on_header);
BEAST_EXPECT(p.got_on_body);
BEAST_EXPECT(! p.got_on_chunk);
BEAST_EXPECT(p.got_on_complete);
BEAST_EXPECT(p.is_complete());
BEAST_EXPECT(p.body == "*****");
#endif
}
void
run() override
{
testFlatten();
testCallbacks();
testRequestLine();
testStatusLine();
testFields();
testConnectionField();
testContentLengthField();
testTransferEncodingField();
testUpgradeField();
testBody();
testSplit();
}
};
BEAST_DEFINE_TESTSUITE(basic_parser,http,beast);
} // http
} // beast
File diff suppressed because it is too large Load Diff
+534
View File
@@ -0,0 +1,534 @@
//
// Copyright (c) 2013-2017 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)
//
#include <beast/core/flat_streambuf.hpp>
#include <beast/core/prepare_buffers.hpp>
#include <beast/http/chunk_encode.hpp>
#include <beast/http/read.hpp>
#include <beast/http/write.hpp>
#include <beast/http/string_body.hpp>
#include <beast/core/detail/clamp.hpp>
#include <beast/test/string_istream.hpp>
#include <beast/test/string_ostream.hpp>
#include <beast/test/yield_to.hpp>
#include <beast/unit_test/suite.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
namespace beast {
namespace http {
class design_test
: public beast::unit_test::suite
, public beast::test::enable_yield_to
{
public:
//--------------------------------------------------------------------------
/*
Read a message with a direct Reader Body.
*/
struct direct_body
{
using value_type = std::string;
class reader
{
value_type& body_;
std::size_t len_ = 0;
public:
static bool constexpr is_direct = true;
using mutable_buffers_type =
boost::asio::mutable_buffers_1;
template<bool isRequest, class Fields>
explicit
reader(message<isRequest, direct_body, Fields>& m)
: body_(m.body)
{
}
void
init()
{
}
void
init(std::uint64_t content_length)
{
if(content_length >
(std::numeric_limits<std::size_t>::max)())
throw std::length_error(
"Content-Length max exceeded");
body_.reserve(static_cast<
std::size_t>(content_length));
}
mutable_buffers_type
prepare(std::size_t n)
{
body_.resize(len_ + n);
return {&body_[len_], n};
}
void
commit(std::size_t n)
{
if(body_.size() > len_ + n)
body_.resize(len_ + n);
len_ = body_.size();
}
void
finish()
{
body_.resize(len_);
}
};
};
void
testDirectBody()
{
// Content-Length
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
};
message<true, direct_body, fields> m;
flat_streambuf sb{1024};
read(is, sb, m);
BEAST_EXPECT(m.body == "*");
}
// end of file
{
test::string_istream is{ios_,
"HTTP/1.1 200 OK\r\n"
"\r\n" // 19 byte header
"*"
};
message<false, direct_body, fields> m;
flat_streambuf sb{20};
read(is, sb, m);
BEAST_EXPECT(m.body == "*");
}
// chunked
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n"
"*\r\n"
"0\r\n\r\n"
};
message<true, direct_body, fields> m;
flat_streambuf sb{100};
read(is, sb, m);
BEAST_EXPECT(m.body == "*");
}
}
//--------------------------------------------------------------------------
/*
Read a message with an indirect Reader Body.
*/
struct indirect_body
{
using value_type = std::string;
class reader
{
value_type& body_;
public:
static bool constexpr is_direct = false;
using mutable_buffers_type =
boost::asio::null_buffers;
template<bool isRequest, class Fields>
explicit
reader(message<isRequest, indirect_body, Fields>& m)
: body_(m.body)
{
}
void
init(error_code& ec)
{
}
void
init(std::uint64_t content_length,
error_code& ec)
{
}
void
write(boost::string_ref const& s,
error_code& ec)
{
body_.append(s.data(), s.size());
}
void
finish(error_code& ec)
{
}
};
};
void
testIndirectBody()
{
// Content-Length
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
};
message<true, indirect_body, fields> m;
flat_streambuf sb{1024};
read(is, sb, m);
BEAST_EXPECT(m.body == "*");
}
// end of file
{
test::string_istream is{ios_,
"HTTP/1.1 200 OK\r\n"
"\r\n" // 19 byte header
"*"
};
message<false, indirect_body, fields> m;
flat_streambuf sb{20};
read(is, sb, m);
BEAST_EXPECT(m.body == "*");
}
// chunked
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n"
"*\r\n"
"0\r\n\r\n"
};
message<true, indirect_body, fields> m;
flat_streambuf sb{1024};
read(is, sb, m);
BEAST_EXPECT(m.body == "*");
}
}
//--------------------------------------------------------------------------
/*
Read a message header and manually read the body.
*/
void
testManualBody()
{
// Content-Length
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Content-Length: 5\r\n"
"\r\n" // 37 byte header
"*****"
};
header_parser<true, fields> p;
flat_streambuf sb{38};
auto const bytes_used =
read_some(is, sb, p);
sb.consume(bytes_used);
BEAST_EXPECT(p.size() == 5);
BEAST_EXPECT(sb.size() < 5);
sb.commit(boost::asio::read(
is, sb.prepare(5 - sb.size())));
BEAST_EXPECT(sb.size() == 5);
}
// end of file
{
test::string_istream is{ios_,
"HTTP/1.1 200 OK\r\n"
"\r\n" // 19 byte header
"*****"
};
header_parser<false, fields> p;
flat_streambuf sb{20};
auto const bytes_used =
read_some(is, sb, p);
sb.consume(bytes_used);
BEAST_EXPECT(p.state() ==
parse_state::body_to_eof);
BEAST_EXPECT(sb.size() < 5);
sb.commit(boost::asio::read(
is, sb.prepare(5 - sb.size())));
BEAST_EXPECT(sb.size() == 5);
}
}
//--------------------------------------------------------------------------
/*
Read a header, check for Expect: 100-continue,
then conditionally read the body.
*/
void
testExpect100Continue()
{
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Expect: 100-continue\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
};
header_parser<true, fields> p;
flat_streambuf sb{128};
auto const bytes_used =
read_some(is, sb, p);
sb.consume(bytes_used);
BEAST_EXPECT(p.got_header());
BEAST_EXPECT(
p.get().fields["Expect"] ==
"100-continue");
message_parser<
true, string_body, fields> p1{
std::move(p)};
read(is, sb, p1);
BEAST_EXPECT(
p1.get().body == "*****");
}
}
//--------------------------------------------------------------------------
/*
Efficiently relay a message from one stream to another
*/
template<
bool isRequest,
class SyncWriteStream,
class DynamicBuffer,
class SyncReadStream>
void
relay(
SyncWriteStream& out,
DynamicBuffer& sb,
SyncReadStream& in)
{
flat_streambuf buffer{4096}; // 4K limit
header_parser<isRequest, fields> parser;
error_code ec;
do
{
auto const state0 = parser.state();
auto const bytes_used =
read_some(in, buffer, parser, ec);
BEAST_EXPECTS(! ec, ec.message());
switch(state0)
{
case parse_state::header:
{
BEAST_EXPECT(parser.got_header());
write(out, parser.get());
break;
}
case parse_state::chunk_header:
{
// inspect parser.chunk_extension() here
if(parser.is_complete())
boost::asio::write(out,
chunk_encode_final());
break;
}
case parse_state::body:
case parse_state::body_to_eof:
case parse_state::chunk_body:
{
if(! parser.is_complete())
{
auto const body = parser.body();
boost::asio::write(out, chunk_encode(
false, boost::asio::buffer(
body.data(), body.size())));
}
break;
}
case parse_state::complete:
break;
}
buffer.consume(bytes_used);
}
while(! parser.is_complete());
}
void
testRelay()
{
// Content-Length
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Content-Length: 5\r\n"
"\r\n" // 37 byte header
"*****",
3 // max_read
};
test::string_ostream os{ios_};
flat_streambuf sb{16};
relay<true>(os, sb, is);
}
// end of file
{
test::string_istream is{ios_,
"HTTP/1.1 200 OK\r\n"
"\r\n" // 19 byte header
"*****",
3 // max_read
};
test::string_ostream os{ios_};
flat_streambuf sb{16};
relay<false>(os, sb, is);
}
// chunked
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5;x;y=1;z=\"-\"\r\n*****\r\n"
"3\r\n---\r\n"
"1\r\n+\r\n"
"0\r\n\r\n",
2 // max_read
};
test::string_ostream os{ios_};
flat_streambuf sb{16};
relay<true>(os, sb, is);
}
}
//--------------------------------------------------------------------------
/*
Read the request header, then read the request body content using
a fixed-size buffer, i.e. read the body in chunks of 4k for instance.
The end of the body should be indicated somehow and chunk-encoding
should be decoded by beast.
*/
template<bool isRequest,
class SyncReadStream, class BodyCallback>
void
doFixedRead(SyncReadStream& stream, BodyCallback const& cb)
{
flat_streambuf buffer{4096}; // 4K limit
header_parser<isRequest, fields> parser;
std::size_t bytes_used;
bytes_used = read_some(stream, buffer, parser);
BEAST_EXPECT(parser.got_header());
buffer.consume(bytes_used);
do
{
bytes_used =
read_some(stream, buffer, parser);
if(! parser.body().empty())
cb(parser.body());
buffer.consume(bytes_used);
}
while(! parser.is_complete());
}
struct bodyHandler
{
void
operator()(boost::string_ref const& body) const
{
// called for each piece of the body,
}
};
void
testFixedRead()
{
using boost::asio::buffer;
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
// Content-Length
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
};
doFixedRead<true>(is, bodyHandler{});
}
// end of file
{
test::string_istream is{ios_,
"HTTP/1.1 200 OK\r\n"
"\r\n" // 19 byte header
"*****"
};
doFixedRead<false>(is, bodyHandler{});
}
// chunked
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5;x;y=1;z=\"-\"\r\n*****\r\n"
"3\r\n---\r\n"
"1\r\n+\r\n"
"0\r\n\r\n",
2 // max_read
};
doFixedRead<true>(is, bodyHandler{});
}
}
//--------------------------------------------------------------------------
void
run()
{
testDirectBody();
testIndirectBody();
testManualBody();
testExpect100Continue();
testRelay();
testFixedRead();
}
};
BEAST_DEFINE_TESTSUITE(design,http,beast);
} // http
} // beast
-9
View File
@@ -1,9 +0,0 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/empty_body.hpp>
+59
View File
@@ -0,0 +1,59 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/error.hpp>
#include <beast/unit_test/suite.hpp>
#include <memory>
namespace beast {
namespace http {
class error_test : public unit_test::suite
{
public:
void
check(char const* name, error ev)
{
auto const ec = make_error_code(ev);
BEAST_EXPECT(std::string(ec.category().name()) == name);
BEAST_EXPECT(! ec.message().empty());
BEAST_EXPECT(std::addressof(ec.category()) ==
std::addressof(detail::get_http_error_category()));
BEAST_EXPECT(detail::get_http_error_category().equivalent(
static_cast<std::underlying_type<error>::type>(ev),
ec.category().default_error_condition(
static_cast<std::underlying_type<error>::type>(ev))));
BEAST_EXPECT(detail::get_http_error_category().equivalent(
ec, static_cast<std::underlying_type<error>::type>(ev)));
}
void
run() override
{
check("http", error::end_of_stream);
check("http", error::partial_message);
check("http", error::buffer_overflow);
check("http", error::bad_line_ending);
check("http", error::bad_method);
check("http", error::bad_path);
check("http", error::bad_version);
check("http", error::bad_status);
check("http", error::bad_reason);
check("http", error::bad_field);
check("http", error::bad_value);
check("http", error::bad_content_length);
check("http", error::bad_transfer_encoding);
check("http", error::bad_chunk);
}
};
BEAST_DEFINE_TESTSUITE(error,http,beast);
} // http
} // beast
-120
View File
@@ -1,120 +0,0 @@
//
// Copyright (c) 2013-2017 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;
body_what body_rv_ = body_what::normal;
public:
std::string body;
template<class... Args>
explicit
fail_parser(test::fail_counter& fc, Args&&... args)
: fc_(fc)
{
}
void
on_body_rv(body_what 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);
}
void
on_header(std::uint64_t content_length, error_code& ec)
{
if(fc_.fail(ec))
return;
}
body_what
on_body_what(std::uint64_t content_length, error_code& ec)
{
if(fc_.fail(ec))
return body_what::normal;
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
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/header_parser.hpp>
#include <beast/core/flat_streambuf.hpp>
#include <beast/http/read.hpp>
#include <beast/unit_test/suite.hpp>
#include <beast/test/string_istream.hpp>
#include <beast/test/yield_to.hpp>
namespace beast {
namespace http {
class header_parser_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
void
testParse()
{
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"\r\n"
};
flat_streambuf db{1024};
header_parser<true, fields> p;
read_some(is, db, p);
BEAST_EXPECT(p.is_complete());
}
{
test::string_istream is{ios_,
"POST / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
};
flat_streambuf db{1024};
header_parser<true, fields> p;
read_some(is, db, p);
BEAST_EXPECT(! p.is_complete());
BEAST_EXPECT(p.state() == parse_state::body);
}
}
void
run() override
{
testParse();
}
};
BEAST_DEFINE_TESTSUITE(header_parser,http,beast);
} // http
} // beast
-90
View File
@@ -1,90 +0,0 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/header_parser_v1.hpp>
#include <beast/http/fields.hpp>
#include <beast/unit_test/suite.hpp>
#include <boost/asio/buffer.hpp>
namespace beast {
namespace http {
class header_parser_v1_test : public beast::unit_test::suite
{
public:
void testParser()
{
{
error_code ec;
header_parser_v1<true, fields> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"\r\n"
), ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
BEAST_EXPECT(n == 36);
}
{
error_code ec;
header_parser_v1<true, fields> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
), ec);
BEAST_EXPECT(n == 55);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
}
{
error_code ec;
header_parser_v1<false, fields> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n"
), ec);
BEAST_EXPECT(n == 33);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
}
{
error_code ec;
header_parser_v1<false, fields> p;
BEAST_EXPECT(! p.complete());
auto const n = p.write(boost::asio::buffer(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"
), ec);
BEAST_EXPECT(n == 52);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.complete());
}
}
void run() override
{
testParser();
}
};
BEAST_DEFINE_TESTSUITE(header_parser_v1,http,beast);
} // http
} // beast
+3 -3
View File
@@ -8,7 +8,7 @@
// Test that header file is self-contained.
#include <beast/http/message.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/string_body.hpp>
#include <beast/http/fields.hpp>
#include <beast/http/string_body.hpp>
#include <beast/unit_test/suite.hpp>
@@ -195,7 +195,7 @@ public:
void testFreeFunctions()
{
{
request<empty_body> m;
request<string_body> m;
m.method = "GET";
m.url = "/";
m.version = 11;
@@ -213,7 +213,7 @@ public:
void testPrepare()
{
request<empty_body> m;
request<string_body> m;
m.version = 10;
BEAST_EXPECT(! is_upgrade(m));
m.fields.insert("Transfer-Encoding", "chunked");
-1
View File
@@ -9,7 +9,6 @@
#define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP
#include <beast/core/write_dynabuf.hpp>
#include <beast/http/detail/basic_parser_v1.hpp>
#include <beast/http/detail/rfc7230.hpp>
#include <cstdint>
#include <random>
+244
View File
@@ -0,0 +1,244 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/message_parser.hpp>
#include "test_parser.hpp"
#include <beast/unit_test/suite.hpp>
#include <beast/test/string_istream.hpp>
#include <beast/test/string_ostream.hpp>
#include <beast/test/yield_to.hpp>
#include <beast/core/flat_streambuf.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/http/header_parser.hpp>
#include <beast/http/read.hpp>
#include <beast/http/read.hpp>
#include <beast/http/string_body.hpp>
#include <boost/system/system_error.hpp>
namespace beast {
namespace http {
class message_parser_test
: public beast::unit_test::suite
, public beast::test::enable_yield_to
{
public:
template<bool isRequest, class Pred>
void
testMatrix(std::string const& s, Pred const& pred)
{
beast::test::string_istream ss{get_io_service(), s};
error_code ec;
#if 0
streambuf dynabuf;
#else
flat_streambuf dynabuf{1024};
#endif
message<isRequest, string_body, fields> m;
read(ss, dynabuf, m, ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
return;
pred(m);
}
void
testRead()
{
testMatrix<false>(
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"\r\n"
"*******",
[&](message<false, string_body, fields> const& m)
{
BEAST_EXPECTS(m.body == "*******",
"body='" + m.body + "'");
}
);
testMatrix<false>(
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5\r\n"
"*****\r\n"
"2;a;b=1;c=\"2\"\r\n"
"--\r\n"
"0;d;e=3;f=\"4\"\r\n"
"Expires: never\r\n"
"MD5-Fingerprint: -\r\n"
"\r\n",
[&](message<false, string_body, fields> const& m)
{
BEAST_EXPECT(m.body == "*****--");
}
);
testMatrix<false>(
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****",
[&](message<false, string_body, fields> const& m)
{
BEAST_EXPECT(m.body == "*****");
}
);
testMatrix<true>(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"\r\n",
[&](message<true, string_body, fields> const& m)
{
}
);
testMatrix<true>(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"X: \t x \t \r\n"
"\r\n",
[&](message<true, string_body, fields> const& m)
{
BEAST_EXPECT(m.fields["X"] == "x");
}
);
}
void
testParse()
{
using boost::asio::buffer;
{
error_code ec;
beast::test::string_istream is{
get_io_service(),
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"};
flat_streambuf sb{1024};
message_parser<true, string_body, fields> p;
read(is, sb, p, ec);
auto const& m = p.get();
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
BEAST_EXPECT(m.method == "GET");
BEAST_EXPECT(m.url == "/");
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(m.fields["User-Agent"] == "test");
BEAST_EXPECT(m.body == "*");
}
#if 0
{
// test partial parsing of final chunk
// parse through the chunk body
beast::test::string_istream is{
get_io_service(), ""};
streambuf sb;
sb <<
"PUT / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n"
"*";
error_code ec;
message_parser<true, string_body, fields> p;
read(is, sb, p, ec);
BEAST_EXPECT(sb.size() == 0);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(!p.is_complete());
BEAST_EXPECT(p.get().body == "*");
sb << "\r\n0;d;e=3;f=\"4\"\r\n"
"Expires: never\r\n"
"MD5-Fingerprint: -\r\n";
// incomplete parse, missing the final crlf
BEAST_EXPECT(p.write(sb.data(), ec) == 0);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(!p.is_complete());
sb << "\r\n"; // final crlf to end message
BEAST_EXPECT(p.write(sb.data(), ec) == sb.size());
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
{
error_code ec;
message_parser<false, string_body, fields> p;
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);
auto const& m = p.get();
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
BEAST_EXPECT(m.status == 200);
BEAST_EXPECT(m.reason == "OK");
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(m.fields["Server"] == "test");
BEAST_EXPECT(m.body == "*");
}
// skip body
{
error_code ec;
message_parser<false, string_body, fields> p;
std::string const s =
"HTTP/1.1 200 Connection Established\r\n"
"Proxy-Agent: Zscaler/5.1\r\n"
"\r\n";
p.skip_body();
p.write(buffer(s), ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_complete());
}
#endif
}
void
testExpect100Continue()
{
test::string_istream ss{ios_,
"POST / HTTP/1.1\r\n"
"Expect: 100-continue\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****"};
streambuf sb;
error_code ec;
header_parser<true, fields> p0;
auto const bytes_used =
read_some(ss, sb, p0, ec);
sb.consume(bytes_used);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p0.state() != parse_state::header);
BEAST_EXPECT(! p0.is_complete());
message_parser<true,
string_body, fields> p1{std::move(p0)};
read(ss, sb, p1, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p1.get().body == "*****");
}
void
run() override
{
testRead();
testParse();
testExpect100Continue();
}
};
BEAST_DEFINE_TESTSUITE(message_parser,http,beast);
} // http
} // beast
+2 -23
View File
@@ -740,11 +740,6 @@ template<bool isRequest, class Body, class Fields>
class nodejs_parser
: public nodejs_basic_parser<nodejs_parser<isRequest, Body, Fields>>
{
using message_type =
message<isRequest, Body, Fields>;
message_type m_;
typename message_type::body_type::reader r_;
bool started_ = false;
public:
@@ -752,7 +747,6 @@ public:
nodejs_parser()
: http::nodejs_basic_parser<nodejs_parser>(isRequest)
, r_(m_)
{
}
@@ -763,12 +757,6 @@ public:
return started_;
}
message_type
release()
{
return std::move(m_);
}
private:
friend class http::nodejs_basic_parser<nodejs_parser>;
@@ -781,7 +769,6 @@ private:
void
on_field(std::string const& field, std::string const& value)
{
m_.fields.insert(field, value);
}
void
@@ -798,9 +785,6 @@ private:
int major, int minor, bool /*keep_alive*/, bool /*upgrade*/,
std::true_type)
{
m_.method = detail::method_to_string(method);
m_.url = url;
m_.version = major * 10 + minor;
return true;
}
@@ -819,7 +803,7 @@ private:
return on_request(method, url,
major, minor, keep_alive, upgrade,
std::integral_constant<
bool, message_type::is_request>{});
bool, isRequest>{});
}
bool
@@ -828,10 +812,6 @@ private:
std::true_type)
{
beast::detail::ignore_unused(keep_alive, upgrade);
m_.status = status;
m_.reason = reason;
m_.version = major * 10 + minor;
// VFALCO TODO return expect_body_
return true;
}
@@ -848,14 +828,13 @@ private:
{
return on_response(
status, reason, major, minor, keep_alive, upgrade,
std::integral_constant<bool, ! message_type::is_request>{});
std::integral_constant<bool, ! isRequest>{});
}
void
on_body(void const* data,
std::size_t size, error_code& ec)
{
r_.write(data, size, ec);
}
void
-9
View File
@@ -1,9 +0,0 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/parse.hpp>
-60
View File
@@ -1,60 +0,0 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/parse_error.hpp>
#include <beast/unit_test/suite.hpp>
#include <memory>
namespace beast {
namespace http {
class parse_error_test : public unit_test::suite
{
public:
void check(char const* name, parse_error ev)
{
auto const ec = make_error_code(ev);
BEAST_EXPECT(std::string{ec.category().name()} == name);
BEAST_EXPECT(! ec.message().empty());
BEAST_EXPECT(std::addressof(ec.category()) ==
std::addressof(detail::get_parse_error_category()));
BEAST_EXPECT(detail::get_parse_error_category().equivalent(
static_cast<std::underlying_type<parse_error>::type>(ev),
ec.category().default_error_condition(
static_cast<std::underlying_type<parse_error>::type>(ev))));
BEAST_EXPECT(detail::get_parse_error_category().equivalent(
ec, static_cast<std::underlying_type<parse_error>::type>(ev)));
}
void run() override
{
check("http", parse_error::connection_closed);
check("http", parse_error::bad_method);
check("http", parse_error::bad_uri);
check("http", parse_error::bad_version);
check("http", parse_error::bad_crlf);
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);
check("http", parse_error::illegal_content_length);
check("http", parse_error::invalid_chunk_size);
check("http", parse_error::invalid_ext_name);
check("http", parse_error::invalid_ext_val);
check("http", parse_error::header_too_big);
check("http", parse_error::body_too_big);
check("http", parse_error::short_read);
}
};
BEAST_DEFINE_TESTSUITE(parse_error,http,beast);
} // http
} // beast
+128 -11
View File
@@ -9,6 +9,7 @@
#include "message_fuzz.hpp"
#include <beast/http.hpp>
#include <beast/core/consuming_buffers.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/core/to_string.hpp>
#include <beast/unit_test/suite.hpp>
@@ -64,9 +65,39 @@ public:
return v;
}
template<class ConstBufferSequence,
bool isRequest, bool isDirect, class Derived>
static
std::size_t
feed(ConstBufferSequence const& buffers,
basic_parser<isRequest, isDirect, Derived>& parser,
error_code& ec)
{
using boost::asio::buffer_size;
beast::consuming_buffers<
ConstBufferSequence> cb{buffers};
std::size_t used = 0;
for(;;)
{
auto const n =
parser.write(cb, ec);
if(ec)
return 0;
if(n == 0)
break;
cb.consume(n);
used += n;
if(parser.is_complete())
break;
if(buffer_size(cb) == 0)
break;
}
return used;
}
template<class Parser>
void
testParser(std::size_t repeat, corpus const& v)
testParser1(std::size_t repeat, corpus const& v)
{
while(repeat--)
for(auto const& sb : v)
@@ -79,6 +110,21 @@ public:
}
}
template<class Parser>
void
testParser2(std::size_t repeat, corpus const& v)
{
while(repeat--)
for(auto const& sb : v)
{
Parser p;
error_code ec;
feed(sb.data(), p, ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
log << to_string(sb.data()) << std::endl;
}
}
template<class Function>
void
timedTest(std::size_t repeat, std::string const& name, Function&& f)
@@ -98,21 +144,92 @@ public:
}
template<bool isRequest>
struct null_parser : basic_parser_v1<isRequest, null_parser<isRequest>>
struct null_parser :
basic_parser<isRequest, true,
null_parser<isRequest>>
{
};
template<bool isRequest, class Body, class Fields>
struct bench_parser : basic_parser<
isRequest, false, bench_parser<isRequest, Body, Fields>>
{
using mutable_buffers_type =
boost::asio::mutable_buffers_1;
void
on_request(boost::string_ref const&,
boost::string_ref const&,
int, error_code&)
{
}
void
on_response(int,
boost::string_ref const&,
int, error_code&)
{
}
void
on_field(boost::string_ref const&,
boost::string_ref const&,
error_code&)
{
}
void
on_header(error_code& ec)
{
}
void
on_body(error_code& ec)
{
}
void
on_body(std::uint64_t content_length,
error_code& ec)
{
}
void
on_data(boost::string_ref const&,
error_code& ec)
{
}
void
on_chunk(std::uint64_t,
boost::string_ref const&,
error_code&)
{
}
void
on_body(boost::string_ref const&,
error_code&)
{
}
void
on_complete(error_code&)
{
}
};
void
testSpeed()
{
static std::size_t constexpr Trials = 3;
static std::size_t constexpr Repeat = 50;
static std::size_t constexpr Repeat = 500;
log << "sizeof(request parser) == " <<
sizeof(basic_parser_v1<true, null_parser<true>>) << '\n';
sizeof(null_parser<true>) << '\n';
log << "sizeof(response parser) == " <<
sizeof(basic_parser_v1<false, null_parser<true>>)<< '\n';
sizeof(null_parser<false>)<< '\n';
testcase << "Parser speed test, " <<
((Repeat * size_ + 512) / 1024) << "KB in " <<
@@ -121,20 +238,20 @@ public:
timedTest(Trials, "nodejs_parser",
[&]
{
testParser<nodejs_parser<
testParser1<nodejs_parser<
true, streambuf_body, fields>>(
Repeat, creq_);
testParser<nodejs_parser<
testParser1<nodejs_parser<
false, streambuf_body, fields>>(
Repeat, cres_);
});
timedTest(Trials, "http::basic_parser_v1",
timedTest(Trials, "http::basic_parser",
[&]
{
testParser<parser_v1<
true, streambuf_body, fields>>(
testParser2<bench_parser<
true, streambuf_body, fields> >(
Repeat, creq_);
testParser<parser_v1<
testParser2<bench_parser<
false, streambuf_body, fields>>(
Repeat, cres_);
});
-160
View File
@@ -1,160 +0,0 @@
//
// Copyright (c) 2013-2017 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)
//
// Test that header file is self-contained.
#include <beast/http/parser_v1.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/http/fields.hpp>
#include <beast/http/header_parser_v1.hpp>
#include <beast/http/parse.hpp>
#include <beast/http/string_body.hpp>
#include <beast/test/string_istream.hpp>
#include <beast/test/yield_to.hpp>
#include <beast/unit_test/suite.hpp>
namespace beast {
namespace http {
class parser_v1_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
void
testParse()
{
using boost::asio::buffer;
{
error_code ec;
parser_v1<true, string_body,
basic_fields<std::allocator<char>>> p;
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);
BEAST_EXPECT(! ec);
BEAST_EXPECT(p.complete());
auto m = p.release();
BEAST_EXPECT(m.method == "GET");
BEAST_EXPECT(m.url == "/");
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(m.fields["User-Agent"] == "test");
BEAST_EXPECT(m.body == "*");
}
{
error_code ec;
parser_v1<false, string_body,
basic_fields<std::allocator<char>>> p;
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);
BEAST_EXPECT(! ec);
BEAST_EXPECT(p.complete());
auto m = p.release();
BEAST_EXPECT(m.status == 200);
BEAST_EXPECT(m.reason == "OK");
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(m.fields["Server"] == "test");
BEAST_EXPECT(m.body == "*");
}
// skip body
{
error_code ec;
parser_v1<false, string_body, fields> p;
std::string const s =
"HTTP/1.1 200 Connection Established\r\n"
"Proxy-Agent: Zscaler/5.1\r\n"
"\r\n";
p.set_option(skip_body{true});
p.write(buffer(s), ec);
BEAST_EXPECT(! ec);
BEAST_EXPECT(p.complete());
}
}
void
testWithBody()
{
std::string const raw =
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*";
test::string_istream ss{
ios_, raw, raw.size() - 1};
streambuf rb;
header_parser_v1<true, fields> p0;
parse(ss, rb, p0);
request_header const& reqh = p0.get();
BEAST_EXPECT(reqh.method == "GET");
BEAST_EXPECT(reqh.url == "/");
BEAST_EXPECT(reqh.version == 11);
BEAST_EXPECT(reqh.fields["User-Agent"] == "test");
BEAST_EXPECT(reqh.fields["Content-Length"] == "1");
parser_v1<true, string_body, fields> p =
with_body<string_body>(p0);
BEAST_EXPECT(p.get().method == "GET");
BEAST_EXPECT(p.get().url == "/");
BEAST_EXPECT(p.get().version == 11);
BEAST_EXPECT(p.get().fields["User-Agent"] == "test");
BEAST_EXPECT(p.get().fields["Content-Length"] == "1");
parse(ss, rb, p);
request<string_body, fields> req = p.release();
BEAST_EXPECT(req.body == "*");
}
void
testRegressions()
{
using boost::asio::buffer;
// consecutive empty header values
{
error_code ec;
parser_v1<true, string_body, fields> p;
std::string const s =
"GET / HTTP/1.1\r\n"
"X1:\r\n"
"X2:\r\n"
"X3:x\r\n"
"\r\n";
p.write(buffer(s), ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
return;
BEAST_EXPECT(p.complete());
auto const msg = p.release();
BEAST_EXPECT(msg.fields.exists("X1"));
BEAST_EXPECT(msg.fields["X1"] == "");
BEAST_EXPECT(msg.fields.exists("X2"));
BEAST_EXPECT(msg.fields["X2"] == "");
BEAST_EXPECT(msg.fields.exists("X3"));
BEAST_EXPECT(msg.fields["X3"] == "x");
}
}
void run() override
{
testParse();
testWithBody();
testRegressions();
}
};
BEAST_DEFINE_TESTSUITE(parser_v1,http,beast);
} // http
} // beast
+23 -133
View File
@@ -8,10 +8,12 @@
// Test that header file is self-contained.
#include <beast/http/read.hpp>
#include "fail_parser.hpp"
#include "test_parser.hpp"
#include <beast/http/fields.hpp>
#include <beast/http/header_parser.hpp>
#include <beast/http/streambuf_body.hpp>
#include <beast/http/string_body.hpp>
#include <beast/test/fail_stream.hpp>
#include <beast/test/string_istream.hpp>
#include <beast/test/yield_to.hpp>
@@ -27,62 +29,9 @@ class read_test
, public test::enable_yield_to
{
public:
struct fail_body
{
class reader;
class value_type
{
friend class reader;
std::string s_;
test::fail_counter& fc_;
public:
explicit
value_type(test::fail_counter& fc)
: fc_(fc)
{
}
value_type&
operator=(std::string s)
{
s_ = std::move(s);
return *this;
}
};
class reader
{
value_type& body_;
public:
template<bool isRequest, class Allocator>
explicit
reader(message<isRequest, fail_body, Allocator>& msg) noexcept
: body_(msg.body)
{
}
void
init(error_code& ec) noexcept
{
body_.fc_.fail(ec);
}
void
write(void const* data,
std::size_t size, error_code& ec) noexcept
{
if(body_.fc_.fail(ec))
return;
}
};
};
template<bool isRequest>
void failMatrix(char const* s, yield_context do_yield)
void
failMatrix(char const* s, yield_context do_yield)
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
@@ -97,9 +46,9 @@ public:
test::fail_counter fc(n);
test::fail_stream<
test::string_istream> fs{fc, ios_, ""};
fail_parser<isRequest> p(fc);
test_parser<isRequest> p(fc);
error_code ec;
parse(fs, sb, p, ec);
read(fs, sb, p, ec);
if(! ec)
break;
}
@@ -113,9 +62,9 @@ public:
test::fail_counter fc(n);
test::fail_stream<test::string_istream> fs{
fc, ios_, std::string{s + pre, len - pre}};
fail_parser<isRequest> p(fc);
test_parser<isRequest> p(fc);
error_code ec;
parse(fs, sb, p, ec);
read(fs, sb, p, ec);
if(! ec)
break;
}
@@ -128,9 +77,9 @@ public:
test::fail_counter fc(n);
test::fail_stream<
test::string_istream> fs{fc, ios_, ""};
fail_parser<isRequest> p(fc);
test_parser<isRequest> p(fc);
error_code ec;
async_parse(fs, sb, p, do_yield[ec]);
async_read(fs, sb, p, do_yield[ec]);
if(! ec)
break;
}
@@ -144,23 +93,9 @@ public:
test::fail_counter fc(n);
test::fail_stream<test::string_istream> fs{
fc, ios_, std::string{s + pre, len - pre}};
fail_parser<isRequest> p(fc);
test_parser<isRequest> p(fc);
error_code ec;
async_parse(fs, sb, p, do_yield[ec]);
if(! ec)
break;
}
BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
streambuf sb;
sb.commit(buffer_copy(
sb.prepare(len), buffer(s, len)));
test::fail_counter fc{n};
test::string_istream ss{ios_, s};
parser_v1<isRequest, fail_body, fields> p{fc};
error_code ec;
parse(ss, sb, p, ec);
async_read(fs, sb, p, do_yield[ec]);
if(! ec)
break;
}
@@ -173,8 +108,8 @@ public:
{
streambuf sb;
test::string_istream ss(ios_, "GET / X");
parser_v1<true, streambuf_body, fields> p;
parse(ss, sb, p);
message_parser<true, streambuf_body, fields> p;
read(ss, sb, p);
fail();
}
catch(std::exception const&)
@@ -245,52 +180,6 @@ public:
failMatrix<false>(res[i], do_yield);
}
void testReadHeaders(yield_context do_yield)
{
static std::size_t constexpr limit = 100;
std::size_t n;
for(n = 0; n < limit; ++n)
{
test::fail_stream<test::string_istream> fs{n, ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
};
request_header m;
try
{
streambuf sb;
read(fs, sb, m);
break;
}
catch(std::exception const&)
{
}
}
BEAST_EXPECT(n < limit);
for(n = 0; n < limit; ++n)
{
test::fail_stream<test::string_istream> fs(n, ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 0\r\n"
"\r\n"
);
request_header m;
error_code ec;
streambuf sb;
async_read(fs, sb, m, do_yield[ec]);
if(! ec)
break;
}
BEAST_EXPECT(n < limit);
}
void testRead(yield_context do_yield)
{
static std::size_t constexpr limit = 100;
@@ -355,22 +244,23 @@ public:
BEAST_EXPECT(n < limit);
}
void testEof(yield_context do_yield)
void
testEof(yield_context do_yield)
{
{
streambuf sb;
test::string_istream ss(ios_, "");
parser_v1<true, streambuf_body, fields> p;
message_parser<true, streambuf_body, fields> p;
error_code ec;
parse(ss, sb, p, ec);
read(ss, sb, p, ec);
BEAST_EXPECT(ec == boost::asio::error::eof);
}
{
streambuf sb;
test::string_istream ss(ios_, "");
parser_v1<true, streambuf_body, fields> p;
message_parser<true, streambuf_body, fields> p;
error_code ec;
async_parse(ss, sb, p, do_yield[ec]);
async_read(ss, sb, p, do_yield[ec]);
BEAST_EXPECT(ec == boost::asio::error::eof);
}
}
@@ -424,12 +314,12 @@ public:
}
}
void run() override
void
run() override
{
testThrow();
yield_to(&read_test::testFailures, this);
yield_to(&read_test::testReadHeaders, this);
yield_to(&read_test::testRead, this);
yield_to(&read_test::testEof, this);
+109 -5
View File
@@ -13,9 +13,10 @@
#include <string>
#include <vector>
#include <beast/core/detail/empty_base_optimization.hpp>
namespace beast {
namespace http {
namespace test {
class rfc7230_test : public beast::unit_test::suite
{
@@ -135,9 +136,9 @@ public:
BEAST_EXPECTS(got == good, fmt(got));
};
/*
ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] )
ext = token param-list
param-list = *( OWS ";" OWS param )
ext-basic_parsed_list = *( "," OWS ) ext *( OWS "," [ OWS ext ] )
ext = token param-basic_parsed_list
param-basic_parsed_list = *( OWS ";" OWS param )
param = token OWS "=" OWS ( token / quoted-string )
*/
cs(",", "");
@@ -237,17 +238,120 @@ public:
cs("x y", "x");
}
template<class Policy>
static
std::vector<std::string>
to_vector(boost::string_ref const& in)
{
std::vector<std::string> v;
detail::basic_parsed_list<Policy> list{in};
for(auto const& s :
detail::basic_parsed_list<Policy>{in})
v.emplace_back(s.data(), s.size());
return v;
}
template<class Policy>
void
validate(boost::string_ref const& in,
std::vector<std::string> const& v)
{
BEAST_EXPECT(to_vector<Policy>(in) == v);
}
template<class Policy>
void
good(boost::string_ref const& in)
{
BEAST_EXPECT(validate_list(
detail::basic_parsed_list<Policy>{in}));
}
template<class Policy>
void
good(boost::string_ref const& in,
std::vector<std::string> const& v)
{
BEAST_EXPECT(validate_list(
detail::basic_parsed_list<Policy>{in}));
validate<Policy>(in, v);
}
template<class Policy>
void
bad(boost::string_ref const& in)
{
BEAST_EXPECT(! validate_list(
detail::basic_parsed_list<Policy>{in}));
}
void
testOptTokenList()
{
/*
#token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ]
*/
using type = detail::opt_token_list_policy;
good<type>("", {});
good<type>(" ", {});
good<type>("\t", {});
good<type>(" \t", {});
good<type>(",", {});
good<type>(",,", {});
good<type>(", ,", {});
good<type>(",\t,", {});
good<type>(", \t,", {});
good<type>(", \t, ", {});
good<type>(", \t,\t", {});
good<type>(", \t, \t", {});
good<type>("x", {"x"});
good<type>(" x", {"x"});
good<type>("x,,", {"x"});
good<type>("x, ,", {"x"});
good<type>("x,, ", {"x"});
good<type>("x,,,", {"x"});
good<type>("x,y", {"x","y"});
good<type>("x ,y", {"x","y"});
good<type>("x\t,y", {"x","y"});
good<type>("x \t,y", {"x","y"});
good<type>(" x,y", {"x","y"});
good<type>(" x,y ", {"x","y"});
good<type>(",x,y", {"x","y"});
good<type>("x,y,", {"x","y"});
good<type>(",,x,y", {"x","y"});
good<type>(",x,,y", {"x","y"});
good<type>(",x,y,", {"x","y"});
good<type>("x ,, y", {"x","y"});
good<type>("x , ,y", {"x","y"});
good<type>("x,y,z", {"x","y","z"});
bad<type>("(");
bad<type>("x(");
bad<type>("(x");
bad<type>(",(");
bad<type>("(,");
bad<type>("x,(");
bad<type>("(,x");
bad<type>("x y");
}
void
run()
{
testOptTokenList();
#if 0
testParamList();
testExtList();
testTokenList();
#endif
}
};
BEAST_DEFINE_TESTSUITE(rfc7230,http,beast);
} // test
} // http
} // beast
+6 -5
View File
@@ -10,7 +10,7 @@
#include <beast/core/to_string.hpp>
#include <beast/http/fields.hpp>
#include <beast/http/parser_v1.hpp>
#include <beast/http/message_parser.hpp>
#include <beast/http/read.hpp>
#include <beast/http/write.hpp>
#include <beast/test/string_istream.hpp>
@@ -34,11 +34,12 @@ public:
"\r\n"
"xyz";
test::string_istream ss(ios_, s);
parser_v1<false, streambuf_body, fields> p;
message_parser<false, streambuf_body, fields> p;
streambuf sb;
parse(ss, sb, p);
BEAST_EXPECT(to_string(p.get().body.data()) == "xyz");
BEAST_EXPECT(boost::lexical_cast<std::string>(p.get()) == s);
read(ss, sb, p);
auto const& m = p.get();
BEAST_EXPECT(to_string(m.body.data()) == "xyz");
BEAST_EXPECT(boost::lexical_cast<std::string>(m) == s);
}
};
+147
View File
@@ -0,0 +1,147 @@
//
// Copyright (c) 2013-2017 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_PARSER_HPP
#define BEAST_HTTP_TEST_PARSER_HPP
#include <beast/http/basic_parser.hpp>
#include <beast/test/fail_counter.hpp>
namespace beast {
namespace http {
template<bool isRequest>
class test_parser
: public basic_parser<isRequest, false,
test_parser<isRequest>>
{
test::fail_counter* fc_ = nullptr;
public:
using mutable_buffers_type =
boost::asio::mutable_buffers_1;
int status = 0;
int version = 0;
std::string method;
std::string path;
std::string reason;
std::string body;
bool got_on_begin = false;
bool got_on_field = false;
bool got_on_header = false;
bool got_on_body = false;
bool got_content_length = false;
bool got_on_prepare = false;
bool got_on_commit = false;
bool got_on_chunk = false;
bool got_on_complete = false;
test_parser() = default;
explicit
test_parser(test::fail_counter& fc)
: fc_(&fc)
{
}
void
on_request(
boost::string_ref const& method_,
boost::string_ref const& path_,
int version_, error_code& ec)
{
method = std::string(
method_.data(), method_.size());
path = std::string(
path_.data(), path_.size());
version = version_;
got_on_begin = true;
if(fc_)
fc_->fail(ec);
}
void
on_response(int status_,
boost::string_ref const& reason_,
int version_, error_code& ec)
{
status = status_;
reason = std::string(
reason_.data(), reason_.size());
version = version_;
got_on_begin = true;
if(fc_)
fc_->fail(ec);
}
void
on_field(boost::string_ref const&,
boost::string_ref const&,
error_code& ec)
{
got_on_field = true;
if(fc_)
fc_->fail(ec);
}
void
on_header(error_code& ec)
{
got_on_header = true;
if(fc_)
fc_->fail(ec);
}
void
on_body(error_code& ec)
{
got_on_body = true;
if(fc_)
fc_->fail(ec);
}
void
on_body(std::uint64_t content_length,
error_code& ec)
{
got_on_body = true;
got_content_length = true;
if(fc_)
fc_->fail(ec);
}
void
on_data(boost::string_ref const& s,
error_code& ec)
{
body.append(s.data(), s.size());
}
void
on_chunk(std::uint64_t,
boost::string_ref const&,
error_code& ec)
{
got_on_chunk = true;
if(fc_)
fc_->fail(ec);
}
void
on_complete(error_code& ec)
{
got_on_complete = true;
if(fc_)
fc_->fail(ec);
}
};
} // http
} // beast
#endif
+1 -2
View File
@@ -10,7 +10,6 @@
#include <beast/http/fields.hpp>
#include <beast/http/message.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/string_body.hpp>
#include <beast/http/write.hpp>
#include <beast/core/error.hpp>
@@ -510,7 +509,7 @@ public:
}
// upgrade HTTP/1.1
{
message<true, empty_body, fields> m;
message<true, string_body, fields> m;
m.method = "GET";
m.url = "/";
m.version = 11;
+1 -1
View File
@@ -174,7 +174,7 @@ public:
for(n = 0; n < limit; ++n)
{
// valid
http::request<http::empty_body> req;
http::request_header req;
req.method = "GET";
req.url = "/";
req.version = 11;