mirror of
https://github.com/boostorg/beast.git
synced 2026-01-27 17:52:20 +01:00
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
421 lines
12 KiB
C++
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
|
|
|