Files
boost_beast/include/beast/http/impl/basic_parser.ipp
Vinnie Falco a52914175b Refactor http::header contents (API Change):
fix #124

The http::header data members "method", "url", and "reason"
are changed from data members, to pairs of get and set
functions which forward the call to the Fields type used
to instantiate the template.

Previously, these data members were implemented using
std::string. With this change, the implementation of the
Fields type used to instantiate the template is now in
control of the representation of those values. This permits
custom memory allocation strategies including uniform use of
the Allocator type already provided to beast::http::basic_fields.
2017-07-20 08:12:16 -07:00

1063 lines
24 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)
//
#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP
#define BEAST_HTTP_IMPL_BASIC_PARSER_IPP
#include <beast/core/buffer_concepts.hpp>
#include <beast/core/detail/ci_char_traits.hpp>
#include <beast/core/detail/clamp.hpp>
#include <beast/core/detail/type_traits.hpp>
#include <beast/http/error.hpp>
#include <beast/http/rfc7230.hpp>
#include <boost/asio/buffer.hpp>
#include <algorithm>
#include <utility>
namespace beast {
namespace http {
template<bool isRequest, bool isDirect, class Derived>
template<bool OtherIsDirect, class OtherDerived>
basic_parser<isRequest, isDirect, Derived>::
basic_parser(basic_parser<isRequest,
OtherIsDirect, OtherDerived>&& other)
: len_(other.len_)
, buf_(std::move(other.buf_))
, buf_len_(other.buf_len_)
, skip_(other.skip_)
, x_(other.x_)
, f_(other.f_)
, state_(other.state_)
{
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
skip_body()
{
BOOST_ASSERT(! got_some());
f_ |= flagSkipBody;
}
template<bool isRequest, bool isDirect, class Derived>
bool
basic_parser<isRequest, isDirect, Derived>::
is_keep_alive() const
{
BOOST_ASSERT(got_header());
if(f_ & flagHTTP11)
{
if(f_ & flagConnectionClose)
return false;
}
else
{
if(! (f_ & flagConnectionKeepAlive))
return false;
}
return (f_ & flagNeedEOF) == 0;
}
template<bool isRequest, bool isDirect, class Derived>
template<class ConstBufferSequence>
std::size_t
basic_parser<isRequest, isDirect, Derived>::
write(ConstBufferSequence const& buffers,
error_code& ec)
{
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
auto const buffer = maybe_flatten(buffers);
return write(boost::asio::const_buffers_1{
buffer.data(), buffer.size()}, ec);
}
template<bool isRequest, bool isDirect, class Derived>
std::size_t
basic_parser<isRequest, isDirect, Derived>::
write(boost::asio::const_buffers_1 const& buffer,
error_code& ec)
{
return do_write(buffer, ec,
std::integral_constant<bool, isDirect>{});
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
write_eof(error_code& ec)
{
BOOST_ASSERT(got_some());
if(state_ == parse_state::header)
{
ec = error::partial_message;
return;
}
if(f_ & (flagContentLength | flagChunked))
{
if(state_ != parse_state::complete)
{
ec = error::partial_message;
return;
}
return;
}
do_complete(ec);
if(ec)
return;
}
template<bool isRequest, bool isDirect, class Derived>
template<class DynamicBuffer>
std::size_t
basic_parser<isRequest, isDirect, Derived>::
copy_body(DynamicBuffer& dynabuf)
{
// This function not available when isDirect==false
static_assert(isDirect, "");
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
BOOST_ASSERT(dynabuf.size() > 0);
BOOST_ASSERT(
state_ == parse_state::body ||
state_ == parse_state::body_to_eof ||
state_ == parse_state::chunk_body);
maybe_do_body_direct();
switch(state_)
{
case parse_state::body_to_eof:
{
auto const buffers =
impl().on_prepare(dynabuf.size());
BOOST_ASSERT(
buffer_size(buffers) >= 1 &&
buffer_size(buffers) <=
dynabuf.size());
auto const n = buffer_copy(
buffers, dynabuf.data());
dynabuf.consume(n);
impl().on_commit(n);
return n;
}
default:
{
BOOST_ASSERT(len_ > 0);
auto const buffers =
impl().on_prepare(
beast::detail::clamp(len_));
BOOST_ASSERT(
buffer_size(buffers) >= 1 &&
buffer_size(buffers) <=
beast::detail::clamp(len_));
auto const n = buffer_copy(
buffers, dynabuf.data());
commit_body(n);
return n;
}
}
}
template<bool isRequest, bool isDirect, class Derived>
template<class MutableBufferSequence>
void
basic_parser<isRequest, isDirect, Derived>::
prepare_body(boost::optional<
MutableBufferSequence>& buffers, std::size_t limit)
{
// This function not available when isDirect==false
static_assert(isDirect, "");
BOOST_ASSERT(limit > 0);
BOOST_ASSERT(
state_ == parse_state::body ||
state_ == parse_state::body_to_eof ||
state_ == parse_state::chunk_body);
maybe_do_body_direct();
std::size_t n;
switch(state_)
{
case parse_state::body_to_eof:
n = limit;
break;
default:
BOOST_ASSERT(len_ > 0);
n = beast::detail::clamp(len_, limit);
break;
}
buffers.emplace(impl().on_prepare(n));
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
commit_body(std::size_t n)
{
// This function not available when isDirect==false
static_assert(isDirect, "");
BOOST_ASSERT(f_ & flagOnBody);
impl().on_commit(n);
switch(state_)
{
case parse_state::body:
len_ -= n;
if(len_ == 0)
{
// VFALCO This is no good, throwing out ec?
error_code ec;
do_complete(ec);
}
break;
case parse_state::chunk_body:
len_ -= n;
if(len_ == 0)
state_ = parse_state::chunk_header;
break;
default:
break;
}
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
consume_body(error_code& ec)
{
BOOST_ASSERT(
state_ == parse_state::body ||
state_ == parse_state::body_to_eof ||
state_ == parse_state::chunk_body);
switch(state_)
{
case parse_state::body:
case parse_state::body_to_eof:
do_complete(ec);
if(ec)
return;
break;
case parse_state::chunk_body:
len_ = 0;
state_ = parse_state::chunk_header;
break;
default:
break;
}
}
template<bool isRequest, bool isDirect, class Derived>
template<class ConstBufferSequence>
inline
boost::string_ref
basic_parser<isRequest, isDirect, Derived>::
maybe_flatten(
ConstBufferSequence const& buffers)
{
using boost::asio::buffer;
using boost::asio::buffer_cast;
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
auto const it = buffers.begin();
auto const last = buffers.end();
if(it == last)
return {nullptr, 0};
if(std::next(it) == last)
{
// single buffer
auto const b = *it;
return {buffer_cast<char const*>(b),
buffer_size(b)};
}
auto const len = buffer_size(buffers);
if(len > buf_len_)
{
// reallocate
buf_.reset(new char[len]);
buf_len_ = len;
}
// flatten
buffer_copy(
buffer(buf_.get(), buf_len_), buffers);
return {buf_.get(), buf_len_};
}
template<bool isRequest, bool isDirect, class Derived>
inline
std::size_t
basic_parser<isRequest, isDirect, Derived>::
do_write(boost::asio::const_buffers_1 const& buffer,
error_code& ec, std::true_type)
{
BOOST_ASSERT(
state_ == parse_state::header ||
state_ == parse_state::chunk_header);
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto const p = buffer_cast<
char const*>(*buffer.begin());
auto const n =
buffer_size(*buffer.begin());
if(state_ == parse_state::header)
{
if(n > 0)
f_ |= flagGotSome;
return parse_header(p, n, ec);
}
else
{
maybe_do_body_direct();
return parse_chunk_header(p, n, ec);
}
}
template<bool isRequest, bool isDirect, class Derived>
inline
std::size_t
basic_parser<isRequest, isDirect, Derived>::
do_write(boost::asio::const_buffers_1 const& buffer,
error_code& ec, std::false_type)
{
BOOST_ASSERT(state_ != parse_state::complete);
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto const p = buffer_cast<
char const*>(*buffer.begin());
auto const n =
buffer_size(*buffer.begin());
switch(state_)
{
case parse_state::header:
if(n > 0)
f_ |= flagGotSome;
return parse_header(p, n, ec);
case parse_state::body:
maybe_do_body_indirect(ec);
if(ec)
return 0;
return parse_body(p, n, ec);
case parse_state::body_to_eof:
maybe_do_body_indirect(ec);
if(ec)
return 0;
return parse_body_to_eof(p, n, ec);
case parse_state::chunk_header:
maybe_do_body_indirect(ec);
if(ec)
return 0;
return parse_chunk_header(p, n, ec);
case parse_state::chunk_body:
return parse_chunk_body(p, n, ec);
case parse_state::complete:
break;
}
return 0;
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
parse_startline(char const*& it,
int& version, int& status,
error_code& ec, std::true_type)
{
/*
request-line = method SP request-target SP HTTP-version CRLF
method = token
*/
auto const method = parse_method(it);
if(method.empty())
{
ec = error::bad_method;
return;
}
if(*it++ != ' ')
{
ec = error::bad_method;
return;
}
auto const target = parse_target(it);
if(target.empty())
{
ec = error::bad_path;
return;
}
if(*it++ != ' ')
{
ec = error::bad_path;
return;
}
version = parse_version(it);
if(version < 0 || ! parse_crlf(it))
{
ec = error::bad_version;
return;
}
impl().on_request(
method, target, version, ec);
if(ec)
return;
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
parse_startline(char const*& it,
int& version, int& status,
error_code& ec, std::false_type)
{
/*
status-line = HTTP-version SP status-code SP reason-phrase CRLF
status-code = 3*DIGIT
reason-phrase = *( HTAB / SP / VCHAR / obs-text )
*/
version = parse_version(it);
if(version < 0 || *it != ' ')
{
ec = error::bad_version;
return;
}
++it;
status = parse_status(it);
if(status < 0 || *it != ' ')
{
ec = error::bad_status;
return;
}
++it;
auto const reason = parse_reason(it);
if(! parse_crlf(it))
{
ec = error::bad_reason;
return;
}
impl().on_response(
status, reason, version, ec);
if(ec)
return;
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
parse_fields(char const*& it,
char const* last, error_code& ec)
{
/* header-field = field-name ":" OWS field-value OWS
field-name = token
field-value = *( field-content / obs-fold )
field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
field-vchar = VCHAR / obs-text
obs-fold = CRLF 1*( SP / HTAB )
; obsolete line folding
; see Section 3.2.4
*/
for(;;)
{
auto term = find_eol(it, last, ec);
if(ec)
return;
BOOST_ASSERT(term);
if(it == term - 2)
{
it = term;
break;
}
auto const name = parse_name(it);
if(name.empty())
{
ec = error::bad_field;
return;
}
if(*it++ != ':')
{
ec = error::bad_field;
return;
}
if(*term != ' ' &&
*term != '\t')
{
auto it2 = term - 2;
detail::skip_ows(it, it2);
detail::skip_ows_rev(it2, it);
auto const value =
make_string(it, it2);
do_field(name, value, ec);
if(ec)
return;
impl().on_field(name, value, ec);
if(ec)
return;
it = term;
}
else
{
// obs-fold
for(;;)
{
auto const it2 = term - 2;
detail::skip_ows(it, it2);
if(it != it2)
break;
it = term;
if(*it != ' ' && *it != '\t')
break;
term = find_eol(it, last, ec);
if(ec)
return;
}
std::string s;
if(it != term)
{
s.append(it, term - 2);
it = term;
for(;;)
{
if(*it != ' ' && *it != '\t')
break;
s.push_back(' ');
detail::skip_ows(it, term - 2);
term = find_eol(it, last, ec);
if(ec)
return;
if(it != term - 2)
s.append(it, term - 2);
it = term;
}
}
boost::string_ref value{
s.data(), s.size()};
do_field(name, value, ec);
if(ec)
return;
impl().on_field(name, value, ec);
if(ec)
return;
}
}
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
do_field(
boost::string_ref const& name,
boost::string_ref const& value,
error_code& ec)
{
// Connection
if(strieq("connection", name) ||
strieq("proxy-connection", name))
{
auto const list = opt_token_list{value};
if(! validate_list(list))
{
// VFALCO Should this be a field specific error?
ec = error::bad_value;
return;
}
for(auto const& s : list)
{
if(strieq("close", s))
{
f_ |= flagConnectionClose;
continue;
}
if(strieq("keep-alive", s))
{
f_ |= flagConnectionKeepAlive;
continue;
}
if(strieq("upgrade", s))
{
f_ |= flagConnectionUpgrade;
continue;
}
}
return;
}
for(auto it = value.begin();
it != value.end(); ++it)
{
if(! is_text(*it))
{
ec = error::bad_value;
return;
}
}
// Content-Length
if(strieq("content-length", name))
{
if(f_ & flagContentLength)
{
// duplicate
ec = error::bad_content_length;
return;
}
if(f_ & flagChunked)
{
// conflicting field
ec = error::bad_content_length;
return;
}
std::uint64_t v;
if(! parse_dec(
value.begin(), value.end(), v))
{
ec = error::bad_content_length;
return;
}
len_ = v;
f_ |= flagContentLength;
return;
}
// Transfer-Encoding
if(strieq("transfer-encoding", name))
{
if(f_ & flagChunked)
{
// duplicate
ec = error::bad_transfer_encoding;
return;
}
if(f_ & flagContentLength)
{
// conflicting field
ec = error::bad_transfer_encoding;
return;
}
auto const v = token_list{value};
auto const it = std::find_if(v.begin(), v.end(),
[&](typename token_list::value_type const& s)
{
return strieq("chunked", s);
});
if(it == v.end())
return;
if(std::next(it) != v.end())
return;
len_ = 0;
f_ |= flagChunked;
return;
}
// Upgrade
if(strieq("upgrade", name))
{
f_ |= flagUpgrade;
ec = {};
return;
}
}
template<bool isRequest, bool isDirect, class Derived>
inline
std::size_t
basic_parser<isRequest, isDirect, Derived>::
parse_header(char const* p,
std::size_t n, error_code& ec)
{
if(n < 4)
return 0;
auto const term = find_eom(
p + skip_, p + n, ec);
if(ec)
return 0;
if(! term)
{
skip_ = n - 3;
return 0;
}
int version;
int status; // ignored for requests
skip_ = 0;
n = term - p;
parse_startline(p, version, status, ec,
std::integral_constant<
bool, isRequest>{});
if(ec)
return 0;
if(version >= 11)
f_ |= flagHTTP11;
parse_fields(p, term, ec);
if(ec)
return 0;
BOOST_ASSERT(p == term);
do_header(status,
std::integral_constant<
bool, isRequest>{});
impl().on_header(ec);
if(ec)
return 0;
if(state_ == parse_state::complete)
{
impl().on_complete(ec);
if(ec)
return 0;
}
return n;
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
do_header(int, std::true_type)
{
// RFC 7230 section 3.3
// https://tools.ietf.org/html/rfc7230#section-3.3
if(f_ & flagSkipBody)
{
state_ = parse_state::complete;
}
else if(f_ & flagContentLength)
{
if(len_ > 0)
{
f_ |= flagHasBody;
state_ = parse_state::body;
}
else
{
state_ = parse_state::complete;
}
}
else if(f_ & flagChunked)
{
f_ |= flagHasBody;
state_ = parse_state::chunk_header;
}
else
{
len_ = 0;
state_ = parse_state::complete;
}
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
do_header(int status, std::false_type)
{
// RFC 7230 section 3.3
// https://tools.ietf.org/html/rfc7230#section-3.3
if( (f_ & flagSkipBody) || // e.g. response to a HEAD request
status / 100 == 1 || // 1xx e.g. Continue
status == 204 || // No Content
status == 304) // Not Modified
{
state_ = parse_state::complete;
return;
}
if(f_ & flagContentLength)
{
if(len_ > 0)
{
f_ |= flagHasBody;
state_ = parse_state::body;
}
else
{
state_ = parse_state::complete;
}
}
else if(f_ & flagChunked)
{
f_ |= flagHasBody;
state_ = parse_state::chunk_header;
}
else
{
f_ |= flagHasBody;
f_ |= flagNeedEOF;
state_ = parse_state::body_to_eof;
}
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
maybe_do_body_direct()
{
if(f_ & flagOnBody)
return;
f_ |= flagOnBody;
if(got_content_length())
impl().on_body(len_);
else
impl().on_body();
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
maybe_do_body_indirect(error_code& ec)
{
if(f_ & flagOnBody)
return;
f_ |= flagOnBody;
if(got_content_length())
{
impl().on_body(len_, ec);
if(ec)
return;
}
else
{
impl().on_body(ec);
if(ec)
return;
}
}
template<bool isRequest, bool isDirect, class Derived>
std::size_t
basic_parser<isRequest, isDirect, Derived>::
parse_chunk_header(char const* p,
std::size_t n, error_code& ec)
{
/*
chunked-body = *chunk last-chunk trailer-part CRLF
chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
last-chunk = 1*("0") [ chunk-ext ] CRLF
trailer-part = *( header-field CRLF )
chunk-size = 1*HEXDIG
chunk-data = 1*OCTET ; a sequence of chunk-size octets
chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val = token / quoted-string
*/
auto const first = p;
auto const last = p + n;
// Treat the last CRLF in a chunk as
// part of the next chunk, so it can
// be parsed in one call instead of two.
if(f_ & flagExpectCRLF)
{
if(n < 2)
return 0;
if(! parse_crlf(p))
{
ec = error::bad_chunk;
return 0;
}
n -= 2;
}
char const* term;
if(! (f_ & flagFinalChunk))
{
if(n < 2)
return 0;
term = find_eol(p + skip_, last, ec);
if(ec)
return 0;
if(! term)
{
skip_ = n - 1;
return 0;
}
std::uint64_t v;
if(! parse_hex(p, v))
{
ec = error::bad_chunk;
return 0;
}
if(v != 0)
{
if(*p == ';')
{
// VFALCO We need to parse the chunk
// extension to validate it here.
ext_ = make_string(p, term - 2);
impl().on_chunk(v, ext_, ec);
if(ec)
return 0;
}
else if(p != term - 2)
{
ec = error::bad_chunk;
return 0;
}
p = term;
len_ = v;
skip_ = 0;
f_ |= flagExpectCRLF;
state_ = parse_state::chunk_body;
return p - first;
}
// This is the offset from the buffer
// to the beginning of the first '\r\n'
x_ = term - 2 - first;
skip_ = x_;
f_ |= flagFinalChunk;
}
else
{
// We are parsing the value again
// to advance p to the right place.
std::uint64_t v;
auto const result = parse_hex(p, v);
BOOST_ASSERT(result && v == 0);
beast::detail::ignore_unused(result);
beast::detail::ignore_unused(v);
}
term = find_eom(
first + skip_, last, ec);
if(ec)
return 0;
if(! term)
{
if(n > 3)
skip_ = (last - first) - 3;
return 0;
}
if(*p == ';')
{
ext_ = make_string(p, first + x_);
impl().on_chunk(0, ext_, ec);
if(ec)
return 0;
p = first + x_;
}
if(! parse_crlf(p))
{
ec = error::bad_chunk;
return 0;
}
parse_fields(p, term, ec);
if(ec)
return 0;
BOOST_ASSERT(p == term);
do_complete(ec);
if(ec)
return 0;
return p - first;
}
template<bool isRequest, bool isDirect, class Derived>
inline
std::size_t
basic_parser<isRequest, isDirect, Derived>::
parse_body(char const* p,
std::size_t n, error_code& ec)
{
n = beast::detail::clamp(len_, n);
body_ = boost::string_ref{p, n};
impl().on_data(body_, ec);
if(ec)
return 0;
len_ -= n;
if(len_ == 0)
{
do_complete(ec);
if(ec)
return 0;
}
return n;
}
template<bool isRequest, bool isDirect, class Derived>
inline
std::size_t
basic_parser<isRequest, isDirect, Derived>::
parse_body_to_eof(char const* p,
std::size_t n, error_code& ec)
{
body_ = boost::string_ref{p, n};
impl().on_data(body_, ec);
if(ec)
return 0;
return n;
}
template<bool isRequest, bool isDirect, class Derived>
inline
std::size_t
basic_parser<isRequest, isDirect, Derived>::
parse_chunk_body(char const* p,
std::size_t n, error_code& ec)
{
n = beast::detail::clamp(len_, n);
body_ = boost::string_ref{p, n};
impl().on_data(body_, ec);
if(ec)
return 0;
len_ -= n;
if(len_ == 0)
{
body_ = {};
state_ = parse_state::chunk_header;
}
return n;
}
template<bool isRequest, bool isDirect, class Derived>
void
basic_parser<isRequest, isDirect, Derived>::
do_complete(error_code& ec)
{
impl().on_complete(ec);
if(ec)
return;
state_ = parse_state::complete;
}
} // http
} // beast
#endif