e681f9d212
close #1664
641 lines
15 KiB
C++
641 lines
15 KiB
C++
//
|
|
// Copyright (c) 2016-2019 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)
|
|
//
|
|
// Official repository: https://github.com/boostorg/beast
|
|
//
|
|
|
|
#ifndef BOOST_BEAST_HTTP_NODEJS_PARSER_HPP
|
|
#define BOOST_BEAST_HTTP_NODEJS_PARSER_HPP
|
|
|
|
#include "nodejs-parser/http_parser.h"
|
|
|
|
#include <boost/beast/http/message.hpp>
|
|
#include <boost/beast/http/rfc7230.hpp>
|
|
#include <boost/beast/core/buffers_range.hpp>
|
|
#include <boost/beast/core/error.hpp>
|
|
#include <boost/asio/buffer.hpp>
|
|
#include <boost/system/error_code.hpp>
|
|
#include <boost/throw_exception.hpp>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace boost {
|
|
namespace beast {
|
|
namespace http {
|
|
|
|
namespace detail {
|
|
|
|
class nodejs_message_category final
|
|
: public boost::system::error_category
|
|
{
|
|
public:
|
|
const char*
|
|
name() const noexcept override
|
|
{
|
|
return "nodejs-http-error";
|
|
}
|
|
|
|
std::string
|
|
message(int ev) const override
|
|
{
|
|
return http_errno_description(
|
|
static_cast<http_errno>(ev));
|
|
}
|
|
|
|
boost::system::error_condition
|
|
default_error_condition(int ev) const noexcept override
|
|
{
|
|
return boost::system::error_condition{ev, *this};
|
|
}
|
|
|
|
bool
|
|
equivalent(int ev,
|
|
boost::system::error_condition const& condition
|
|
) const noexcept override
|
|
{
|
|
return condition.value() == ev &&
|
|
&condition.category() == this;
|
|
}
|
|
|
|
bool
|
|
equivalent(boost::system::error_code const& error,
|
|
int ev) const noexcept override
|
|
{
|
|
return error.value() == ev &&
|
|
&error.category() == this;
|
|
}
|
|
};
|
|
|
|
template<class = void>
|
|
boost::system::error_code
|
|
make_nodejs_error(int http_errno)
|
|
{
|
|
static nodejs_message_category const mc{};
|
|
return boost::system::error_code{http_errno, mc};
|
|
}
|
|
|
|
inline
|
|
char const*
|
|
method_to_string(unsigned method)
|
|
{
|
|
using namespace beast;
|
|
switch(static_cast<http_method>(method))
|
|
{
|
|
case HTTP_DELETE: return "DELETE";
|
|
case HTTP_GET: return "GET";
|
|
case HTTP_HEAD: return "HEAD";
|
|
case HTTP_POST: return "POST";
|
|
case HTTP_PUT: return "PUT";
|
|
|
|
// pathological
|
|
case HTTP_CONNECT: return "CONNECT";
|
|
case HTTP_OPTIONS: return "OPTIONS";
|
|
case HTTP_TRACE: return "TRACE";
|
|
|
|
// webdav
|
|
case HTTP_COPY: return "COPY";
|
|
case HTTP_LOCK: return "LOCK";
|
|
case HTTP_MKCOL: return "MKCOL";
|
|
case HTTP_MOVE: return "MOVE";
|
|
case HTTP_PROPFIND: return "PROPFIND";
|
|
case HTTP_PROPPATCH: return "PROPPATCH";
|
|
case HTTP_SEARCH: return "SEARCH";
|
|
case HTTP_UNLOCK: return "UNLOCK";
|
|
case HTTP_BIND: return "BIND";
|
|
case HTTP_REBIND: return "REBIND";
|
|
case HTTP_UNBIND: return "UNBIND";
|
|
case HTTP_ACL: return "ACL";
|
|
|
|
// subversion
|
|
case HTTP_REPORT: return "REPORT";
|
|
case HTTP_MKACTIVITY: return "MKACTIVITY";
|
|
case HTTP_CHECKOUT: return "CHECKOUT";
|
|
case HTTP_MERGE: return "MERGE";
|
|
|
|
// upnp
|
|
case HTTP_MSEARCH: return "MSEARCH";
|
|
case HTTP_NOTIFY: return "NOTIFY";
|
|
case HTTP_SUBSCRIBE: return "SUBSCRIBE";
|
|
case HTTP_UNSUBSCRIBE: return "UNSUBSCRIBE";
|
|
|
|
// RFC-5789
|
|
case HTTP_PATCH: return "PATCH";
|
|
case HTTP_PURGE: return "PURGE";
|
|
|
|
// CalDav
|
|
case HTTP_MKCALENDAR: return "MKCALENDAR";
|
|
|
|
// RFC-2068, section 19.6.1.2
|
|
case HTTP_LINK: return "LINK";
|
|
case HTTP_UNLINK: return "UNLINK";
|
|
};
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
} // detail
|
|
|
|
template<class Derived>
|
|
class nodejs_basic_parser
|
|
{
|
|
http_parser state_;
|
|
boost::system::error_code* ec_;
|
|
bool complete_ = false;
|
|
std::string url_;
|
|
std::string status_;
|
|
std::string field_;
|
|
std::string value_;
|
|
|
|
public:
|
|
using error_code = boost::system::error_code;
|
|
|
|
nodejs_basic_parser(nodejs_basic_parser&& other);
|
|
|
|
nodejs_basic_parser&
|
|
operator=(nodejs_basic_parser&& other);
|
|
|
|
nodejs_basic_parser(nodejs_basic_parser const& other);
|
|
|
|
nodejs_basic_parser& operator=(nodejs_basic_parser const& other);
|
|
|
|
explicit
|
|
nodejs_basic_parser(bool request) noexcept;
|
|
|
|
bool
|
|
complete() const noexcept
|
|
{
|
|
return complete_;
|
|
}
|
|
|
|
std::size_t
|
|
write(void const* data, std::size_t size)
|
|
{
|
|
error_code ec;
|
|
auto const used = write(data, size, ec);
|
|
if(ec)
|
|
BOOST_THROW_EXCEPTION(system_error{ec});
|
|
return used;
|
|
}
|
|
|
|
std::size_t
|
|
write(void const* data, std::size_t size,
|
|
error_code& ec);
|
|
|
|
template<class ConstBufferSequence>
|
|
std::size_t
|
|
write(ConstBufferSequence const& buffers)
|
|
{
|
|
error_code ec;
|
|
auto const used = write(buffers, ec);
|
|
if(ec)
|
|
BOOST_THROW_EXCEPTION(system_error{ec});
|
|
return used;
|
|
}
|
|
|
|
template<class ConstBufferSequence>
|
|
std::size_t
|
|
write(ConstBufferSequence const& buffers,
|
|
error_code& ec);
|
|
|
|
void
|
|
write_eof()
|
|
{
|
|
error_code ec;
|
|
write_eof(ec);
|
|
if(ec)
|
|
BOOST_THROW_EXCEPTION(system_error{ec});
|
|
}
|
|
|
|
void
|
|
write_eof(error_code& ec);
|
|
|
|
void
|
|
check_header();
|
|
|
|
private:
|
|
Derived&
|
|
impl()
|
|
{
|
|
return *static_cast<Derived*>(this);
|
|
}
|
|
|
|
static int cb_message_start(http_parser*);
|
|
static int cb_url(http_parser*, char const*, std::size_t);
|
|
static int cb_status(http_parser*, char const*, std::size_t);
|
|
static int cb_header_field(http_parser*, char const*, std::size_t);
|
|
static int cb_header_value(http_parser*, char const*, std::size_t);
|
|
static int cb_headers_complete(http_parser*);
|
|
static int cb_body(http_parser*, char const*, std::size_t);
|
|
static int cb_message_complete(http_parser*);
|
|
static int cb_chunk_header(http_parser*);
|
|
static int cb_chunk_complete(http_parser*);
|
|
|
|
struct hooks_t : http_parser_settings
|
|
{
|
|
hooks_t()
|
|
{
|
|
http_parser_settings_init(this);
|
|
on_message_begin = &nodejs_basic_parser::cb_message_start;
|
|
on_url = &nodejs_basic_parser::cb_url;
|
|
on_status = &nodejs_basic_parser::cb_status;
|
|
on_header_field = &nodejs_basic_parser::cb_header_field;
|
|
on_header_value = &nodejs_basic_parser::cb_header_value;
|
|
on_headers_complete = &nodejs_basic_parser::cb_headers_complete;
|
|
on_body = &nodejs_basic_parser::cb_body;
|
|
on_message_complete = &nodejs_basic_parser::cb_message_complete;
|
|
on_chunk_header = &nodejs_basic_parser::cb_chunk_header;
|
|
on_chunk_complete = &nodejs_basic_parser::cb_chunk_complete;
|
|
}
|
|
};
|
|
|
|
static
|
|
http_parser_settings const*
|
|
hooks();
|
|
};
|
|
|
|
template<class Derived>
|
|
template<class ConstBufferSequence>
|
|
std::size_t
|
|
nodejs_basic_parser<Derived>::write(
|
|
ConstBufferSequence const& buffers, error_code& ec)
|
|
{
|
|
static_assert(net::is_const_buffer_sequence<
|
|
ConstBufferSequence>::value,
|
|
"ConstBufferSequence type requirements not met");
|
|
std::size_t bytes_used = 0;
|
|
for(auto buffer : beast::buffers_range_ref(buffers))
|
|
{
|
|
auto const n = write(
|
|
static_cast<void const*>(buffer.data()),
|
|
buffer.size(), ec);
|
|
if(ec)
|
|
return 0;
|
|
bytes_used += n;
|
|
if(complete())
|
|
break;
|
|
}
|
|
return bytes_used;
|
|
}
|
|
|
|
template<class Derived>
|
|
http_parser_settings const*
|
|
nodejs_basic_parser<Derived>::hooks()
|
|
{
|
|
static hooks_t const h;
|
|
return &h;
|
|
}
|
|
|
|
template<class Derived>
|
|
nodejs_basic_parser<Derived>::
|
|
nodejs_basic_parser(nodejs_basic_parser&& other)
|
|
{
|
|
state_ = other.state_;
|
|
state_.data = this;
|
|
complete_ = other.complete_;
|
|
url_ = std::move(other.url_);
|
|
status_ = std::move(other.status_);
|
|
field_ = std::move(other.field_);
|
|
value_ = std::move(other.value_);
|
|
}
|
|
|
|
template<class Derived>
|
|
auto
|
|
nodejs_basic_parser<Derived>::
|
|
operator=(nodejs_basic_parser&& other) ->
|
|
nodejs_basic_parser&
|
|
{
|
|
state_ = other.state_;
|
|
state_.data = this;
|
|
complete_ = other.complete_;
|
|
url_ = std::move(other.url_);
|
|
status_ = std::move(other.status_);
|
|
field_ = std::move(other.field_);
|
|
value_ = std::move(other.value_);
|
|
return *this;
|
|
}
|
|
|
|
template<class Derived>
|
|
nodejs_basic_parser<Derived>::
|
|
nodejs_basic_parser(nodejs_basic_parser const& other)
|
|
{
|
|
state_ = other.state_;
|
|
state_.data = this;
|
|
complete_ = other.complete_;
|
|
url_ = other.url_;
|
|
status_ = other.status_;
|
|
field_ = other.field_;
|
|
value_ = other.value_;
|
|
}
|
|
|
|
template<class Derived>
|
|
auto
|
|
nodejs_basic_parser<Derived>::
|
|
operator=(nodejs_basic_parser const& other) ->
|
|
nodejs_basic_parser&
|
|
{
|
|
state_ = other.state_;
|
|
state_.data = this;
|
|
complete_ = other.complete_;
|
|
url_ = other.url_;
|
|
status_ = other.status_;
|
|
field_ = other.field_;
|
|
value_ = other.value_;
|
|
return *this;
|
|
}
|
|
|
|
template<class Derived>
|
|
nodejs_basic_parser<Derived>::
|
|
nodejs_basic_parser(bool request) noexcept
|
|
{
|
|
state_.data = this;
|
|
http_parser_init(&state_, request
|
|
? http_parser_type::HTTP_REQUEST
|
|
: http_parser_type::HTTP_RESPONSE);
|
|
}
|
|
|
|
template<class Derived>
|
|
std::size_t
|
|
nodejs_basic_parser<Derived>::
|
|
write(void const* data,
|
|
std::size_t size, error_code& ec)
|
|
{
|
|
ec_ = &ec;
|
|
auto const n = http_parser_execute(
|
|
&state_, hooks(),
|
|
static_cast<const char*>(data), size);
|
|
if(! ec)
|
|
ec = detail::make_nodejs_error(
|
|
static_cast<int>(state_.http_errno));
|
|
if(ec)
|
|
return 0;
|
|
return n;
|
|
}
|
|
|
|
template<class Derived>
|
|
void
|
|
nodejs_basic_parser<Derived>::
|
|
write_eof(error_code& ec)
|
|
{
|
|
ec_ = &ec;
|
|
http_parser_execute(&state_, hooks(), nullptr, 0);
|
|
if(! ec)
|
|
ec = detail::make_nodejs_error(
|
|
static_cast<int>(state_.http_errno));
|
|
}
|
|
|
|
template<class Derived>
|
|
void
|
|
nodejs_basic_parser<Derived>::
|
|
check_header()
|
|
{
|
|
if(! value_.empty())
|
|
{
|
|
impl().on_field(field_, value_);
|
|
field_.clear();
|
|
value_.clear();
|
|
}
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_message_start(http_parser* p)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.complete_ = false;
|
|
t.url_.clear();
|
|
t.status_.clear();
|
|
t.field_.clear();
|
|
t.value_.clear();
|
|
t.impl().on_start();
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_url(http_parser* p,
|
|
char const* in, std::size_t bytes)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.url_.append(in, bytes);
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_status(http_parser* p,
|
|
char const* in, std::size_t bytes)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.status_.append(in, bytes);
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_header_field(http_parser* p,
|
|
char const* in, std::size_t bytes)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.check_header();
|
|
t.field_.append(in, bytes);
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_header_value(http_parser* p,
|
|
char const* in, std::size_t bytes)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.value_.append(in, bytes);
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_headers_complete(http_parser* p)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.check_header();
|
|
t.impl().on_headers_complete(*t.ec_);
|
|
if(*t.ec_)
|
|
return 1;
|
|
bool const keep_alive =
|
|
http_should_keep_alive(p) != 0;
|
|
if(p->type == http_parser_type::HTTP_REQUEST)
|
|
{
|
|
t.impl().on_request(p->method, t.url_,
|
|
p->http_major, p->http_minor, keep_alive,
|
|
p->upgrade);
|
|
return 0;
|
|
}
|
|
return t.impl().on_response(p->status_code, t.status_,
|
|
p->http_major, p->http_minor, keep_alive,
|
|
p->upgrade) ? 0 : 1;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_body(http_parser* p,
|
|
char const* in, std::size_t bytes)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.impl().on_body(in, bytes, *t.ec_);
|
|
return *t.ec_ ? 1 : 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_message_complete(http_parser* p)
|
|
{
|
|
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
|
|
t.complete_ = true;
|
|
t.impl().on_complete();
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_chunk_header(http_parser*)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
template<class Derived>
|
|
int
|
|
nodejs_basic_parser<Derived>::
|
|
cb_chunk_complete(http_parser*)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/** A HTTP parser.
|
|
|
|
The parser may only be used once.
|
|
*/
|
|
template<bool isRequest, class Body, class Fields>
|
|
class nodejs_parser
|
|
: public nodejs_basic_parser<nodejs_parser<isRequest, Body, Fields>>
|
|
{
|
|
bool started_ = false;
|
|
|
|
public:
|
|
nodejs_parser(nodejs_parser&&) = default;
|
|
|
|
nodejs_parser()
|
|
: http::nodejs_basic_parser<nodejs_parser>(isRequest)
|
|
{
|
|
}
|
|
|
|
/// Returns `true` if at least one byte has been processed
|
|
bool
|
|
started()
|
|
{
|
|
return started_;
|
|
}
|
|
|
|
private:
|
|
friend class http::nodejs_basic_parser<nodejs_parser>;
|
|
|
|
void
|
|
on_start()
|
|
{
|
|
started_ = true;
|
|
}
|
|
|
|
void
|
|
on_field(std::string const&, std::string const&)
|
|
{
|
|
}
|
|
|
|
void
|
|
on_headers_complete(error_code& ec)
|
|
{
|
|
// vFALCO TODO Decode the Content-Length and
|
|
// Transfer-Encoding, see if we can reserve the buffer.
|
|
//
|
|
// r_.reserve(content_length)
|
|
ec = {};
|
|
}
|
|
|
|
bool
|
|
on_request(unsigned, std::string const&,
|
|
int, int, bool, bool, std::true_type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
on_request(unsigned, std::string const&,
|
|
int, int, bool, bool, std::false_type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
on_request(unsigned method, std::string const& url,
|
|
int major, int minor, bool keep_alive, bool upgrade)
|
|
{
|
|
return on_request(method, url,
|
|
major, minor, keep_alive, upgrade,
|
|
std::integral_constant<
|
|
bool, isRequest>{});
|
|
}
|
|
|
|
bool
|
|
on_response(int, std::string const&,
|
|
int, int, bool, bool, std::true_type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
on_response(int, std::string const&, int, int, bool, bool,
|
|
std::false_type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
on_response(int status, std::string const& reason,
|
|
int major, int minor, bool keep_alive, bool upgrade)
|
|
{
|
|
return on_response(
|
|
status, reason, major, minor, keep_alive, upgrade,
|
|
std::integral_constant<bool, ! isRequest>{});
|
|
}
|
|
|
|
void
|
|
on_body(void const*, std::size_t, error_code& ec)
|
|
{
|
|
ec = {};
|
|
}
|
|
|
|
void
|
|
on_complete()
|
|
{
|
|
}
|
|
};
|
|
|
|
} // http
|
|
} // beast
|
|
} // boost
|
|
|
|
#endif
|