// // 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 #include #include #include #include #include #include #include #include #include #include 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(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 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(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 ""; } } // detail template 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 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 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(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 template std::size_t nodejs_basic_parser::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(buffer.data()), buffer.size(), ec); if(ec) return 0; bytes_used += n; if(complete()) break; } return bytes_used; } template http_parser_settings const* nodejs_basic_parser::hooks() { static hooks_t const h; return &h; } template nodejs_basic_parser:: 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 auto nodejs_basic_parser:: 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 nodejs_basic_parser:: 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 auto nodejs_basic_parser:: 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 nodejs_basic_parser:: 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 std::size_t nodejs_basic_parser:: write(void const* data, std::size_t size, error_code& ec) { ec_ = &ec; auto const n = http_parser_execute( &state_, hooks(), static_cast(data), size); if(! ec) ec = detail::make_nodejs_error( static_cast(state_.http_errno)); if(ec) return 0; return n; } template void nodejs_basic_parser:: write_eof(error_code& ec) { ec_ = &ec; http_parser_execute(&state_, hooks(), nullptr, 0); if(! ec) ec = detail::make_nodejs_error( static_cast(state_.http_errno)); } template void nodejs_basic_parser:: check_header() { if(! value_.empty()) { impl().on_field(field_, value_); field_.clear(); value_.clear(); } } template int nodejs_basic_parser:: cb_message_start(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.complete_ = false; t.url_.clear(); t.status_.clear(); t.field_.clear(); t.value_.clear(); t.impl().on_start(); return 0; } template int nodejs_basic_parser:: cb_url(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); t.url_.append(in, bytes); return 0; } template int nodejs_basic_parser:: cb_status(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); t.status_.append(in, bytes); return 0; } template int nodejs_basic_parser:: cb_header_field(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); t.check_header(); t.field_.append(in, bytes); return 0; } template int nodejs_basic_parser:: cb_header_value(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); t.value_.append(in, bytes); return 0; } template int nodejs_basic_parser:: cb_headers_complete(http_parser* p) { auto& t = *reinterpret_cast(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 int nodejs_basic_parser:: cb_body(http_parser* p, char const* in, std::size_t bytes) { auto& t = *reinterpret_cast(p->data); t.impl().on_body(in, bytes, *t.ec_); return *t.ec_ ? 1 : 0; } template int nodejs_basic_parser:: cb_message_complete(http_parser* p) { auto& t = *reinterpret_cast(p->data); t.complete_ = true; t.impl().on_complete(); return 0; } template int nodejs_basic_parser:: cb_chunk_header(http_parser*) { return 0; } template int nodejs_basic_parser:: cb_chunk_complete(http_parser*) { return 0; } //------------------------------------------------------------------------------ /** A HTTP parser. The parser may only be used once. */ template class nodejs_parser : public nodejs_basic_parser> { bool started_ = false; public: nodejs_parser(nodejs_parser&&) = default; nodejs_parser() : http::nodejs_basic_parser(isRequest) { } /// Returns `true` if at least one byte has been processed bool started() { return started_; } private: friend class http::nodejs_basic_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{}); } void on_body(void const*, std::size_t, error_code& ec) { ec = {}; } void on_complete() { } }; } // http } // beast } // boost #endif