Files
beast/test/http/parser.cpp
Vinnie Falco 048ee7523c Refactor method and verb (API Change):
The verb interfaces now use verb::unknown instead of
boost::optional<verb> == boost::none to indicate that
the request-method is an unrecognized string.

The http::header interface is modified to focus more on the
verb enum rather than the string. For recognized verbs, the
implementation stores an integer instead of the string.
Unknown verbs are still stored as strings.

* header::method now returns a verb
* header::method_string returns the method text
2017-07-20 08:12:18 -07:00

421 lines
12 KiB
C++

//
// 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.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/consuming_buffers.hpp>
#include <beast/core/flat_buffer.hpp>
#include <beast/core/multi_buffer.hpp>
#include <beast/core/ostream.hpp>
#include <beast/http/read.hpp>
#include <beast/http/string_body.hpp>
#include <boost/system/system_error.hpp>
namespace beast {
namespace http {
class header_parser_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
static
boost::asio::const_buffers_1
buf(string_view s)
{
return {s.data(), s.size()};
}
void
testParse()
{
{
test::string_istream is{ios_,
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"\r\n"
};
flat_buffer db{1024};
header_parser<true, fields> p;
read_some(is, db, p);
BEAST_EXPECT(p.is_header_done());
}
{
test::string_istream is{ios_,
"POST / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
};
flat_buffer db{1024};
header_parser<true, fields> p;
read_some(is, db, p);
BEAST_EXPECT(p.is_header_done());
BEAST_EXPECT(! p.is_done());
}
}
void
run() override
{
testParse();
}
};
BEAST_DEFINE_TESTSUITE(header_parser,http,beast);
class parser_test
: public beast::unit_test::suite
, public beast::test::enable_yield_to
{
public:
template<bool isRequest>
using parser_type =
parser<isRequest, string_body, fields>;
static
boost::asio::const_buffers_1
buf(string_view s)
{
return {s.data(), s.size()};
}
template<class ConstBufferSequence,
bool isRequest, class Derived>
static
void
put(ConstBufferSequence const& buffers,
basic_parser<isRequest, Derived>& p,
error_code& ec)
{
using boost::asio::buffer_size;
consuming_buffers<ConstBufferSequence> cb{buffers};
for(;;)
{
auto const used = p.put(cb, ec);
cb.consume(used);
if(ec)
return;
if(p.need_eof() &&
buffer_size(cb) == 0)
{
p.put_eof(ec);
if(ec)
return;
}
if(p.is_done())
break;
}
}
template<bool isRequest, class F>
void
doMatrix(string_view s0, F const& f)
{
using boost::asio::buffer;
// parse a single buffer
{
auto s = s0;
error_code ec;
parser_type<isRequest> p;
put(buffer(s.data(), s.size()), p, ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
return;
f(p);
}
// parse two buffers
for(auto n = s0.size() - 1; n >= 1; --n)
{
auto s = s0;
error_code ec;
parser_type<isRequest> p;
p.eager(true);
auto used =
p.put(buffer(s.data(), n), ec);
s.remove_prefix(used);
if(ec == error::need_more)
ec = {};
if(! BEAST_EXPECTS(! ec, ec.message()))
continue;
BEAST_EXPECT(! p.is_done());
used = p.put(
buffer(s.data(), s.size()), ec);
s.remove_prefix(used);
if(! BEAST_EXPECTS(! ec, ec.message()))
continue;
BEAST_EXPECT(s.empty());
if(p.need_eof())
{
p.put_eof(ec);
if(! BEAST_EXPECTS(! ec, ec.message()))
continue;
}
if(BEAST_EXPECT(p.is_done()))
f(p);
}
}
void
testParse()
{
doMatrix<false>(
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"\r\n"
"Hello, world!",
[&](parser_type<false> const& p)
{
auto const& m = p.get();
BEAST_EXPECT(! p.is_chunked());
BEAST_EXPECT(p.need_eof());
BEAST_EXPECT(p.content_length() == boost::none);
BEAST_EXPECT(m.version == 10);
BEAST_EXPECT(m.status == 200);
BEAST_EXPECT(m.reason() == "OK");
BEAST_EXPECT(m.fields["Server"] == "test");
BEAST_EXPECT(m.body == "Hello, world!");
}
);
doMatrix<false>(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Expect: Expires, MD5-Fingerprint\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",
[&](parser_type<false> const& p)
{
auto const& m = p.get();
BEAST_EXPECT(! p.need_eof());
BEAST_EXPECT(p.is_chunked());
BEAST_EXPECT(p.content_length() == boost::none);
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(m.status == 200);
BEAST_EXPECT(m.reason() == "OK");
BEAST_EXPECT(m.fields["Server"] == "test");
BEAST_EXPECT(m.fields["Transfer-Encoding"] == "chunked");
BEAST_EXPECT(m.fields["Expires"] == "never");
BEAST_EXPECT(m.fields["MD5-Fingerprint"] == "-");
BEAST_EXPECT(m.body == "*****--");
}
);
doMatrix<false>(
"HTTP/1.0 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****",
[&](parser_type<false> const& p)
{
auto const& m = p.get();
BEAST_EXPECT(m.body == "*****");
}
);
doMatrix<true>(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"\r\n",
[&](parser_type<true> const& p)
{
auto const& m = p.get();
BEAST_EXPECT(m.method() == verb::get);
BEAST_EXPECT(m.target() == "/");
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(! p.need_eof());
BEAST_EXPECT(! p.is_chunked());
BEAST_EXPECT(p.content_length() == boost::none);
}
);
doMatrix<true>(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"X: \t x \t \r\n"
"\r\n",
[&](parser_type<true> const& p)
{
auto const& m = p.get();
BEAST_EXPECT(m.fields["X"] == "x");
}
);
// test eager(true)
{
error_code ec;
parser_type<true> p;
p.eager(true);
p.put(buf(
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*")
, ec);
auto const& m = p.get();
BEAST_EXPECT(! ec);
BEAST_EXPECT(p.is_done());
BEAST_EXPECT(p.is_header_done());
BEAST_EXPECT(! p.need_eof());
BEAST_EXPECT(m.method() == verb::get);
BEAST_EXPECT(m.target() == "/");
BEAST_EXPECT(m.version == 11);
BEAST_EXPECT(m.fields["User-Agent"] == "test");
BEAST_EXPECT(m.body == "*");
}
{
// test partial parsing of final chunk
// parse through the chunk body
error_code ec;
flat_buffer b;
parser_type<true> p;
p.eager(true);
ostream(b) <<
"PUT / HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"1\r\n"
"*";
auto used = p.put(b.data(), ec);
b.consume(used);
BEAST_EXPECT(! ec);
BEAST_EXPECT(! p.is_done());
BEAST_EXPECT(p.get().body == "*");
ostream(b) <<
"\r\n"
"0;d;e=3;f=\"4\"\r\n"
"Expires: never\r\n"
"MD5-Fingerprint: -\r\n";
// incomplete parse, missing the final crlf
used = p.put(b.data(), ec);
b.consume(used);
BEAST_EXPECT(ec == error::need_more);
ec = {};
BEAST_EXPECT(! p.is_done());
ostream(b) <<
"\r\n"; // final crlf to end message
used = p.put(b.data(), ec);
b.consume(used);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_done());
}
// skip body
{
error_code ec;
response_parser<string_body> p;
p.skip(true);
p.put(buf(
"HTTP/1.1 200 OK\r\n"
"Content-Length: 5\r\n"
"\r\n"
"*****")
, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p.is_done());
BEAST_EXPECT(p.is_header_done());
BEAST_EXPECT(p.content_length() &&
*p.content_length() == 5);
}
}
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"
"*****"};
multi_buffer b;
error_code ec;
header_parser<true, fields> p0;
auto const bytes_used =
read_some(ss, b, p0, ec);
b.consume(bytes_used);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p0.is_header_done());
BEAST_EXPECT(! p0.is_done());
request_parser<string_body> p1{std::move(p0)};
read(ss, b, p1, ec);
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(p1.get().body == "*****");
}
//--------------------------------------------------------------------------
template<class DynamicBuffer>
void
testNeedMore()
{
error_code ec;
std::size_t used;
{
DynamicBuffer b;
parser_type<true> p;
ostream(b) <<
"GET / HTTP/1.1\r\n";
used = p.put(b.data(), ec);
BEAST_EXPECTS(ec == error::need_more, ec.message());
b.consume(used);
ec = {};
ostream(b) <<
"User-Agent: test\r\n"
"\r\n";
used = p.put(b.data(), ec);
BEAST_EXPECTS(! ec, ec.message());
b.consume(used);
BEAST_EXPECT(p.is_done());
BEAST_EXPECT(p.is_header_done());
}
}
void
testGotSome()
{
error_code ec;
parser_type<true> p;
auto used = p.put(buf(""), ec);
BEAST_EXPECT(ec == error::need_more);
BEAST_EXPECT(! p.got_some());
BEAST_EXPECT(used == 0);
ec = {};
used = p.put(buf("G"), ec);
BEAST_EXPECT(ec == error::need_more);
BEAST_EXPECT(p.got_some());
BEAST_EXPECT(used == 0);
}
void
run() override
{
testParse();
testNeedMore<flat_buffer>();
testNeedMore<multi_buffer>();
testGotSome();
}
};
BEAST_DEFINE_TESTSUITE(parser,http,beast);
} // http
} // beast