2016-11-20 07:32:41 -05:00
|
|
|
//
|
|
|
|
// 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>
|
|
|
|
template<class ConstBufferSequence>
|
|
|
|
inline
|
2017-05-05 14:45:15 -07:00
|
|
|
string_view
|
2016-11-20 07:32:41 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-05-02 15:49:22 -07:00
|
|
|
auto const target = parse_target(it);
|
|
|
|
if(target.empty())
|
2016-11-20 07:32:41 -05:00
|
|
|
{
|
|
|
|
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(
|
2017-05-02 15:49:22 -07:00
|
|
|
method, target, version, ec);
|
2016-11-20 07:32:41 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2017-05-05 14:45:15 -07:00
|
|
|
string_view value{
|
2016-11-20 07:32:41 -05:00
|
|
|
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(
|
2017-05-05 14:45:15 -07:00
|
|
|
string_view const& name,
|
|
|
|
string_view const& value,
|
2016-11-20 07:32:41 -05:00
|
|
|
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);
|
2017-05-05 14:45:15 -07:00
|
|
|
body_ = string_view{p, n};
|
2016-11-20 07:32:41 -05:00
|
|
|
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)
|
|
|
|
{
|
2017-05-05 14:45:15 -07:00
|
|
|
body_ = string_view{p, n};
|
2016-11-20 07:32:41 -05:00
|
|
|
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);
|
2017-05-05 14:45:15 -07:00
|
|
|
body_ = string_view{p, n};
|
2016-11-20 07:32:41 -05:00
|
|
|
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
|