diff --git a/Jamroot b/Jamroot index 1f863115..24885116 100644 --- a/Jamroot +++ b/Jamroot @@ -60,8 +60,11 @@ project beast multi static shared + on gcc:-std=c++11 clang:-std=c++11 + msvc:_SCL_SECURE_NO_WARNINGS=1 + msvc:_CRT_SECURE_NO_WARNINGS=1 LINUX:_XOPEN_SOURCE=600 LINUX:_GNU_SOURCE=1 SOLARIS:_XOPEN_SOURCE=500 @@ -78,8 +81,6 @@ project beast HPUX:ipv6 QNXNTO:socket HAIKU:network - msvc:_SCL_SECURE_NO_WARNINGS=1 - msvc:_CRT_SECURE_NO_WARNINGS=1 : usage-requirements . : diff --git a/TODO.txt b/TODO.txt index 0d255ab7..1505dd28 100644 --- a/TODO.txt +++ b/TODO.txt @@ -22,3 +22,10 @@ * Figure out why namespace rfc2616 is included in docs (currently disabled via GENERATING_DOCS macro) * Include Example program listings in the docs +* Update for rfc7230 +* HTTP parser size limit with test (configurable?) +* HTTP parser trailers with test +* URL parser, strong URL checking in HTTP parser +* Fix method, use string instead of enum +* More fine grained parser errors +* Fix all the warnings in all projects/build configs diff --git a/doc/beast.dox b/doc/beast.dox index 311c06a3..007fc36e 100644 --- a/doc/beast.dox +++ b/doc/beast.dox @@ -119,6 +119,7 @@ INPUT = \ ../include/beast/streambuf_readstream.hpp \ ../include/beast/type_check.hpp \ ../include/beast/websocket.hpp \ + ../include/beast/write_streambuf.hpp \ ../include/beast/http/basic_headers.hpp \ ../include/beast/http/basic_parser.hpp \ ../include/beast/http/chunk_encode.hpp \ @@ -128,6 +129,7 @@ INPUT = \ ../include/beast/http/headers.hpp \ ../include/beast/http/message.hpp \ ../include/beast/http/method.hpp \ + ../include/beast/http/parse_error.hpp \ ../include/beast/http/parser.hpp \ ../include/beast/http/read.hpp \ ../include/beast/http/resume_context.hpp \ diff --git a/doc/beast.qbk b/doc/beast.qbk index f53b53fe..47a26c6d 100644 --- a/doc/beast.qbk +++ b/doc/beast.qbk @@ -80,9 +80,9 @@ Beast requires: [note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] -Most of the library is header-only; however, the HTTP parser used is written -in C. To link an application that uses Beast, it is necessary to add a single -.cpp file from beast into your project's build script. +The library is [*header-only]. It is not necessary to add any .cpp files, +or to edit your existing build script or project file except to provide +that the include/ directory for beast is searched for include files. [endsect] @@ -95,11 +95,6 @@ flavor of the library. They are complete programs which may be built and run. Source code and build scripts for these programs may be found in the examples directory. -[note - To link these programs, please add the file - `src/beast_http_nodejs_parser.cpp` to your build script or Makefile -] - Use HTTP to request the root page from a website and print the response: ``` #include diff --git a/doc/http.qbk b/doc/http.qbk index 19132ee7..85f91b66 100644 --- a/doc/http.qbk +++ b/doc/http.qbk @@ -33,9 +33,16 @@ interface for the C++ standard library will likely closely resemble the current interface of Boost.Asio, it is logical for Beast.HTTP to use Boost.Asio as its network transport. +[heading Scope] + +The scope of this library is meant to include only the functionality of +modeling the HTTP message, serializing and deserializing the message, and +sending and receiving messages on sockets or streams. It is designed to +be a building block for creating higher level abstractions. + [note The documentation which follows assumes familiarity with both Boost.Asio and the HTTP protocol specification described in -[@https://tools.ietf.org/html/rfc2616 rfc2616] ] +[@https://tools.ietf.org/html/rfc7230 rfc7230] ] [endsect] diff --git a/examples/Jamfile b/examples/Jamfile index 131055d1..2f438baa 100644 --- a/examples/Jamfile +++ b/examples/Jamfile @@ -8,28 +8,23 @@ import os ; exe http_crawl : - ../src/beast_http_nodejs_parser.cpp http_crawl.cpp urls_large_data.cpp ; exe http_server : - ../src/beast_http_nodejs_parser.cpp http_server.cpp ; -exe wsproto_echo : - ../src/beast_http_nodejs_parser.cpp - wsproto_echo.cpp +exe websocket_echo : + websocket_echo.cpp ; exe http_example : - ../src/beast_http_nodejs_parser.cpp http_example.cpp ; exe websocket_example : - ../src/beast_http_nodejs_parser.cpp websocket_example.cpp ; diff --git a/examples/wsproto_async_echo_peer.h b/examples/websocket_async_echo_peer.h similarity index 98% rename from examples/wsproto_async_echo_peer.h rename to examples/websocket_async_echo_peer.h index 469e841f..e494cb85 100644 --- a/examples/wsproto_async_echo_peer.h +++ b/examples/websocket_async_echo_peer.h @@ -17,8 +17,8 @@ */ //============================================================================== -#ifndef BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED -#define BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED +#ifndef BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED #include #include diff --git a/examples/wsproto_echo.cpp b/examples/websocket_echo.cpp similarity index 95% rename from examples/wsproto_echo.cpp rename to examples/websocket_echo.cpp index ba856377..deb1ef8b 100644 --- a/examples/wsproto_echo.cpp +++ b/examples/websocket_echo.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include "wsproto_async_echo_peer.h" -#include "wsproto_sync_echo_peer.h" +#include "websocket_async_echo_peer.h" +#include "websocket_sync_echo_peer.h" #include "sig_wait.h" int main() diff --git a/examples/wsproto_sync_echo_peer.h b/examples/websocket_sync_echo_peer.h similarity index 100% rename from examples/wsproto_sync_echo_peer.h rename to examples/websocket_sync_echo_peer.h diff --git a/include/beast/http.hpp b/include/beast/http.hpp index 47bc45b3..104f409d 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp index 494083ce..20465e22 100644 --- a/include/beast/http/basic_parser.hpp +++ b/include/beast/http/basic_parser.hpp @@ -8,150 +8,355 @@ #ifndef BEAST_HTTP_BASIC_PARSER_HPP #define BEAST_HTTP_BASIC_PARSER_HPP -#include -#include +#include +#include +#include +#include #include -#include -#include +#include +#include +#include +#include #include -#include #include namespace beast { namespace http { +namespace parse_flag { +enum values +{ + chunked = 1 << 0, + connection_keep_alive = 1 << 1, + connection_close = 1 << 2, + connection_upgrade = 1 << 3, + trailing = 1 << 4, + upgrade = 1 << 5, + skipbody = 1 << 6, + contentlength = 1 << 7 +}; +} // parse_flag + /** Parser for producing HTTP requests and responses. - Callbacks: + During parsing, callbacks will be made to the derived class + if those members are present (detected through SFINAE). The + signatures which can be present in the derived class are:
- If a is an object of type Derived, and the call expression is - valid then the stated effects will take place: + @li `void on_method(boost::string_ref const&, error_code& ec)` - a.on_start() + Called for each piece of the Request-Method - Called once when a new message begins. + @li `void on_uri(boost::string_ref const&, error_code& ec)` - a.on_field(std::string field, std::string value) + Called for each piece of the Request-URI - Called for each field + @li `void on_reason(boost::string_ref const&, error_code& ec)` - a.on_headers_complete(error_code&) + Called for each piece of the reason-phrase - Called when all the header fields have been received, but - before any part of the body if any is received. + @li `void on_request(error_code& ec)` - a.on_request(method_t method, std::string url, - int major, int minor, bool keep_alive, bool upgrade) + Called after the entire Request-Line has been parsed successfully. - Called for requests when all the headers have been received. - This will precede any content body. + @li `void on_response(error_code& ec)` - When keep_alive is false: - * Server roles respond with a "Connection: close" header. - * Client roles close the connection. + Called after the entire Response-Line has been parsed successfully. - a.on_response(int status, std::string text, - int major, int minor, bool keep_alive, - bool upgrade) + @li `void on_field(boost::string_ref const&, error_code& ec)` - Called for responses when all the headers have been received. - This will precede any content body. + Called for each piece of the current header field. - When keep_alive is `false`: - * Client roles close the connection. - * Server roles respond with a "Connection: close" header. + @li `void on_value(boost::string_ref const&, error_code& ec)` - This function should return `true` if upgrade is false and - a content body is expected. When upgrade is true, no - content-body is expected, and the return value is ignored. + Called for each piece of the current header value. - a.on_body(void const* data, std::size_t bytes, error_code&) + @li `int on_headers(error_code& ec)` - Called zero or more times for the content body. Any transfer - encoding is already decoded in the memory pointed to by data. + Called when all the headers have been parsed successfully. - a.on_complete() + @li `void on_body(boost::string_ref const&, error_code& ec)` - Called when parsing completes successfully. + Called for each piece of the body. If the headers indicated + chunked encoding, the chunk encoding is removed from the + buffer before being passed to the callback. + + @li `void on_complete(error_code& ec)` + + Called when the entire message has been parsed successfully. + At this point, basic_parser::complete() returns `true`, and + the parser is ready to parse another message if keep_alive() + would return `true`. + + The return value of `on_headers` is special, it controls whether + or not the parser should expect a body. These are the return values: + + @li *0* The parser should expect a body + + @li *1* The parser should skip the body. For example, this is + used when sending a response to a HEAD request. + + @li *2* The parser should skip ths body, this is an + upgrade to a different protocol. The parser uses traits to determine if the callback is possible. - If the Derived type omits the callbacks, they are simply skipped - with no compilation error. + If the Derived type omits one or more callbacks, they are simply + skipped with no compilation error. The default behavior of on_body + when the derived class does not provide the member, is to specify that + the body should not be skipped. + + If a callback sets an error, parsing stops at the current octet + and the error is returned to the caller. */ -/* - VFALCO TODO is_call_possible, enable_if_t on Derived calls - use boost::string_ref instead of std::string -*/ -template +template class 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_; +private: + using self = basic_parser; + typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); + + static std::uint64_t constexpr no_content_length = + std::numeric_limits::max(); + + enum state : std::uint8_t + { + s_closed = 1, + + s_req_start, + s_req_method_start, + s_req_method, + s_req_space_before_url, + s_req_url_start, + s_req_url, + s_req_http_start, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_major_start, + s_req_major, + s_req_minor_start, + s_req_minor, + s_req_line_end, + + s_res_start, + s_res_H, + s_res_HT, + s_res_HTT, + s_res_HTTP, + s_res_major_start, + s_res_major, + s_res_minor_start, + s_res_minor, + s_res_status_code_start, + s_res_status_code, + s_res_status_start, + s_res_status, + s_res_line_almost_done, + s_res_line_done, + + s_header_field_start, + s_header_field, + s_header_value_start, + s_header_value_discard_lWs0, + s_header_value_discard_ws0, + s_header_value_almost_done0, + s_header_value_text_start, + s_header_value_discard_lWs, + s_header_value_discard_ws, + s_header_value_text, + s_header_value_almost_done, + + s_headers_almost_done, + s_headers_done, + + s_chunk_size_start, + s_chunk_size, + s_chunk_parameters, + s_chunk_size_almost_done, + + // states below do not count towards + // the limit on the size of the message + + s_body_identity0, + s_body_identity, + s_body_identity_eof0, + s_body_identity_eof, + + s_chunk_data_start, + s_chunk_data, + s_chunk_data_almost_done, + s_chunk_data_done, + + s_complete, + s_restart + }; + + enum field_state : std::uint8_t + { + h_general = 0, + h_C, + h_CO, + h_CON, + + h_matching_connection, + h_matching_proxy_connection, + h_matching_content_length, + h_matching_transfer_encoding, + h_matching_upgrade, + + h_connection, + h_content_length, + h_transfer_encoding, + h_upgrade, + + h_matching_transfer_encoding_chunked, + h_matching_connection_token_start, + h_matching_connection_keep_alive, + h_matching_connection_close, + h_matching_connection_upgrade, + h_matching_connection_token, + + h_transfer_encoding_chunked, + h_connection_keep_alive, + h_connection_close, + h_connection_upgrade, + }; + + std::uint64_t content_length_; + std::uint64_t nread_; + pmf_t cb_; + state s_ : 8; + unsigned flags_ : 8; + unsigned fs_ : 8; + unsigned pos_ : 8; // position in field state + unsigned http_major_ : 16; + unsigned http_minor_ : 16; + unsigned status_code_ : 16; + bool upgrade_ : 1; // true if parser exited for upgrade public: - using error_code = boost::system::error_code; + /// Copy constructor. + basic_parser(basic_parser const&) = default; - /** Move constructor. + /// Copy assignment. + basic_parser& operator=(basic_parser const&) = default; - The state of the moved-from object is undefined, - but safe to destroy. - */ - basic_parser(basic_parser&& other); + /// Constructor + basic_parser() + { + init(std::integral_constant{}); + } - /** Move assignment. + /// Returns internal flags associated with the parser. + unsigned + flags() const + { + return flags_; + } - The state of the moved-from object is undefined, - but safe to destroy. - */ - basic_parser& - operator=(basic_parser&& other); + /** Returns `true` if the message end is indicated by eof. - /** Copy constructor. */ - basic_parser(basic_parser const& other); + This function returns true if the semantics of the message require + that the end of the message is signaled by an end of file. For + example, if the message is a HTTP/1.0 message and the Content-Length + is unspecified, the end of the message is indicated by an end of file. - /** Copy assignment. */ - basic_parser& operator=(basic_parser const& other); - - /** Construct the parser. - - @param request If `true`, the parser is setup for a request. - */ - explicit - basic_parser(bool request) noexcept; - - /** Returns `true` if parsing is complete. - - This is only defined when no errors have been returned. + @return `true` if write_eof must be used to indicate the message end. */ bool - complete() const noexcept + needs_eof() const { - return complete_; + return needs_eof( + std::integral_constant{}); + } + + /** Returns the major HTTP version number. + + Examples: + * Returns 1 for HTTP/1.1 + * Returns 1 for HTTP/1.0 + + @return The HTTP major version number. + */ + unsigned + http_major() const + { + return http_major_; + } + + /** Returns the minor HTTP version number. + + Examples: + * Returns 1 for HTTP/1.1 + * Returns 0 for HTTP/1.0 + + @return The HTTP minor version number. + */ + unsigned + http_minor() const + { + return http_minor_; + } + + /** Returns `true` if the message is an upgrade message. + + A value of `true` indicates that the parser has successfully + completed parsing a HTTP upgrade message. + + @return `true` if the message is an upgrade message. + */ + bool + upgrade() const + { + return upgrade_; + } + + /** Returns the numeric HTTP Status-Code of a response. + + @return The Status-Code. + */ + unsigned + status_code() const + { + return status_code_; + } + + /** Returns `true` if the connection should be kept open. + + @note This function is only valid to call when the parser + is complete. + */ + bool + keep_alive() const; + + /** Returns `true` if the parse has completed succesfully. + + When the parse has completed successfully, and the semantics + of the parsed message indicate that the connection is still + active, a subsequent call to `write` will begin parsing a + new message. + + @return `true` If the parsing has completed successfully. + */ + bool + complete() const + { + return s_ == s_restart; } /** Write data to the parser. - @param data A pointer to a buffer representing the input sequence. - @param size The number of bytes in the buffer pointed to by data. + @param buffers An object meeting the requirements of + ConstBufferSequence that represents the input sequence. - @throws boost::system::system_error Thrown on failure. + @param ec Set to the error, if any error occurred. @return The number of bytes consumed in the input sequence. */ + template std::size_t - write(void const* data, std::size_t size) - { - error_code ec; - auto const used = write(data, size, ec); - if(ec) - throw boost::system::system_error{ec}; - return used; - } + write(ConstBufferSequence const& buffers, error_code& ec); /** Write data to the parser. @@ -162,41 +367,7 @@ public: @return The number of bytes consumed in the input sequence. */ std::size_t - write(void const* data, std::size_t size, - error_code& ec); - - /** Write data to the parser. - - @param buffers An object meeting the requirements of - ConstBufferSequence that represents the input sequence. - - @throws boost::system::system_error Thrown on failure. - - @return The number of bytes consumed in the input sequence. - */ - template - std::size_t - write(ConstBufferSequence const& buffers) - { - error_code ec; - auto const used = write(buffers, ec); - if(ec) - throw boost::system::system_error{ec}; - return used; - } - - /** Write data to the parser. - - @param buffers An object meeting the requirements of - ConstBufferSequence that represents the input sequence. - @param ec Set to the error, if any error occurred. - - @return The number of bytes consumed in the input sequence. - */ - template - std::size_t - write(ConstBufferSequence const& buffers, - error_code& ec); + write(void const* data, std::size_t size, error_code& ec); /** Called to indicate the end of file. @@ -210,26 +381,6 @@ public: @throws boost::system::system_error Thrown on failure. */ void - write_eof() - { - error_code ec; - write_eof(ec); - if(ec) - throw boost::system::system_error{ec}; - } - - /** Called to indicate the end of file. - - HTTP needs to know where the end of the stream is. For example, - sometimes servers send responses without Content-Length and - expect the client to consume input (for the body) until EOF. - Callbacks and errors will still be processed as usual. - - @note This is typically called when a socket read returns eof. - - @param ec Set to the error, if any error occurred. - */ - void write_eof(error_code& ec); private: @@ -239,299 +390,182 @@ private: return *static_cast(this); } - template - class has_on_start_t - { - template().on_start(), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_start = - std::integral_constant::value>; - void - call_on_start(std::true_type) + init(std::true_type) { - impl().on_start(); + s_ = s_req_start; } void - call_on_start(std::false_type) + init(std::false_type) { - } - - template - class has_on_field_t - { - template().on_field( - std::declval(), - std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_field = - std::integral_constant::value>; - - void - call_on_field(std::string const& field, - std::string const& value, std::true_type) - { - impl().on_field(field, value); - } - - void - call_on_field(std::string const&, std::string const&, - std::false_type) - { - } - - template - class has_on_headers_complete_t - { - template().on_headers_complete( - std::declval()), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_headers_complete = - std::integral_constant::value>; - - void - call_on_headers_complete(error_code& ec, std::true_type) - { - impl().on_headers_complete(ec); - } - - void - call_on_headers_complete(error_code&, std::false_type) - { - } - - template - class has_on_request_t - { - template().on_request( - std::declval(), std::declval(), - std::declval(), std::declval(), - std::declval(), std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_request = - std::integral_constant::value>; - - void - call_on_request(method_t method, std::string url, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) - { - impl().on_request( - method, url, major, minor, keep_alive, upgrade); - } - - void - call_on_request(method_t, std::string, int, int, bool, bool, - std::false_type) - { - } - - template - class has_on_response_t - { - template().on_response( - std::declval(), std::declval, - std::declval(), std::declval(), - std::declval(), std::declval()), - std::true_type{})> - static R check(int); - template - static std::false_type check(...); -#if 0 - using type = decltype(check(0)); -#else - // VFALCO Trait seems broken for http::parser - using type = std::true_type; -#endif - public: - static bool const value = type::value; - }; - template - using has_on_response = - std::integral_constant::value>; - - bool - call_on_response(int status, std::string text, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) - { - return impl().on_response( - status, text, major, minor, keep_alive, upgrade); + s_ = s_res_start; } bool - call_on_response(int, std::string, int, int, bool, bool, - std::false_type) + needs_eof(std::true_type) const; + + bool + needs_eof(std::false_type) const; + + void call_on_method(error_code& ec, + boost::string_ref const& s, std::true_type) { - // VFALCO Certainly incorrect - return true; + impl().on_method(s, ec); } - template - class has_on_body_t - { - template().on_body( - std::declval(), std::declval(), - std::declval()), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_body = - std::integral_constant::value>; - - void - call_on_body(void const* data, std::size_t bytes, - error_code& ec, std::true_type) - { - impl().on_body(data, bytes, ec); - } - - void - call_on_body(void const*, std::size_t, - error_code&, std::false_type) + void call_on_method(error_code&, + boost::string_ref const&, std::false_type) { } - template - class has_on_complete_t + void call_on_method(error_code& ec, + boost::string_ref const& s) { - template().on_complete(), std::true_type{})> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using has_on_complete = - std::integral_constant::value>; - - void - call_on_complete(std::true_type) - { - impl().on_complete(); + call_on_method(ec, s, std::integral_constant::value>{}); } - void - call_on_complete(std::false_type) + void call_on_uri(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_uri(s, ec); + } + + void call_on_uri(error_code&, + boost::string_ref const&, std::false_type) { } - void - check_header(); - - 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 + void call_on_uri(error_code& ec, boost::string_ref const& s) { - hooks_t() - { - http_parser_settings_init(this); - on_message_begin = &basic_parser::cb_message_start; - on_url = &basic_parser::cb_url; - on_status = &basic_parser::cb_status; - on_header_field = &basic_parser::cb_header_field; - on_header_value = &basic_parser::cb_header_value; - on_headers_complete = &basic_parser::cb_headers_complete; - on_body = &basic_parser::cb_body; - on_message_complete = &basic_parser::cb_message_complete; - on_chunk_header = &basic_parser::cb_chunk_header; - on_chunk_complete = &basic_parser::cb_chunk_complete; - } - }; + call_on_uri(ec, s, std::integral_constant::value>{}); + } - static - http_parser_settings const* - hooks(); + void call_on_reason(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_reason(s, ec); + } + + void call_on_reason(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_reason(error_code& ec, boost::string_ref const& s) + { + call_on_reason(ec, s, std::integral_constant::value>{}); + } + + void call_on_request(error_code& ec, std::true_type) + { + impl().on_request(ec); + } + + void call_on_request(error_code&, std::false_type) + { + } + + void call_on_request(error_code& ec) + { + call_on_request(ec, detail::has_on_request{}); + } + + void call_on_response(error_code& ec, std::true_type) + { + impl().on_response(ec); + } + + void call_on_response(error_code&, std::false_type) + { + } + + void call_on_response(error_code& ec) + { + call_on_response(ec, detail::has_on_response{}); + } + + void call_on_field(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_field(s, ec); + } + + void call_on_field(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_field(error_code& ec, boost::string_ref const& s) + { + call_on_field(ec, s, detail::has_on_field{}); + } + + void call_on_value(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_value(s, ec); + } + + void call_on_value(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_value(error_code& ec, boost::string_ref const& s) + { + call_on_value(ec, s, detail::has_on_value{}); + } + + int call_on_headers(error_code& ec, std::true_type) + { + return impl().on_headers(ec); + } + + int call_on_headers(error_code& ec, std::false_type) + { + return 0; + } + + int call_on_headers(error_code& ec) + { + return call_on_headers(ec, detail::has_on_headers{}); + } + + void call_on_body(error_code& ec, + boost::string_ref const& s, std::true_type) + { + impl().on_body(s, ec); + } + + void call_on_body(error_code&, + boost::string_ref const&, std::false_type) + { + } + + void call_on_body(error_code& ec, boost::string_ref const& s) + { + call_on_body(ec, s, detail::has_on_body{}); + } + + void call_on_complete(error_code& ec, std::true_type) + { + impl().on_complete(ec); + } + + void call_on_complete(error_code&, std::false_type) + { + } + + void call_on_complete(error_code& ec) + { + call_on_complete(ec, detail::has_on_complete{}); + } }; -template -template -std::size_t -basic_parser::write( - ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert(beast::is_ConstBufferSequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::size_t bytes_used = 0; - for (auto const& buffer : buffers) - { - auto const n = write( - buffer_cast(buffer), - buffer_size(buffer), ec); - if(ec) - return 0; - bytes_used += n; - if(complete()) - break; - } - return bytes_used; -} - -template -http_parser_settings const* -basic_parser::hooks() -{ - static hooks_t const h; - return &h; -} - } // http } // beast diff --git a/include/beast/http/detail/basic_parser.hpp b/include/beast/http/detail/basic_parser.hpp new file mode 100644 index 00000000..79de107e --- /dev/null +++ b/include/beast/http/detail/basic_parser.hpp @@ -0,0 +1,388 @@ +// +// Copyright (c) 2013-2016 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_DETAIL_BASIC_PARSER_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +// '0'...'9' +inline +bool +is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +inline +bool +is_token(char c) +{ + /* token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 + }}; + return tab[static_cast(c)] != 0; +} + +inline +bool +is_text(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)] != 0; +} + +// converts to lower case, +// returns 0 if not a valid token char +// +inline +char +to_field_char(char c) +{ + /* token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0 + }}; + return tab[static_cast(c)]; +} + +// converts to lower case, +// returns 0 if not a valid text char +// +inline +char +to_value_char(char c) +{ + // TEXT = + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 32 + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // 48 + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 64 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, // 80 + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 96 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, // 112 + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, // 128 + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 144 + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, // 160 + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, // 176 + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, // 192 + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, // 208 + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 224 + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 + }}; + return static_cast(tab[static_cast(c)]); +} + +inline +std::uint8_t +unhex(char c) +{ + static std::array constexpr tab = {{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 48 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 112 + }}; + return tab[static_cast(c)]; +}; + +template +struct parser_str_t +{ + static char constexpr close[6] = "close"; + static char constexpr chunked[8] = "chunked"; + static char constexpr keep_alive[11] = "keep-alive"; + + static char constexpr upgrade[8] = "upgrade"; + static char constexpr connection[11] = "connection"; + static char constexpr content_length[15] = "content-length"; + static char constexpr proxy_connection[17] = "proxy-connection"; + static char constexpr transfer_encoding[18] = "transfer-encoding"; +}; + +template +char constexpr +parser_str_t<_>::close[6]; + +template +char constexpr +parser_str_t<_>::chunked[8]; + +template +char constexpr +parser_str_t<_>::keep_alive[11]; + +template +char constexpr +parser_str_t<_>::upgrade[8]; + +template +char constexpr +parser_str_t<_>::connection[11]; + +template +char constexpr +parser_str_t<_>::content_length[15]; + +template +char constexpr +parser_str_t<_>::proxy_connection[17]; + +template +char constexpr +parser_str_t<_>::transfer_encoding[18]; + +using parser_str = parser_str_t<>; + +template +class has_on_method_t +{ + template().on_method( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_method = + std::integral_constant::value>; + +template +class has_on_uri_t +{ + template().on_uri( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_uri = + std::integral_constant::value>; + +template +class has_on_reason_t +{ + template().on_reason( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_reason = + std::integral_constant::value>; + +template +class has_on_request_t +{ + template().on_request( + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_request = + std::integral_constant::value>; + +template +class has_on_response_t +{ + template().on_response( + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_response = + std::integral_constant::value>; + +template +class has_on_field_t +{ + template().on_uri( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_field = + std::integral_constant::value>; + +template +class has_on_value_t +{ + template().on_uri( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_value = + std::integral_constant::value>; + +template +class has_on_headers_t +{ + template().on_headers( + std::declval()))>> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_headers = + std::integral_constant::value>; + +template +class has_on_body_t +{ + template().on_body( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_body = + std::integral_constant::value>; + +template +class has_on_complete_t +{ + template().on_complete( + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool const value = type::value; +}; +template +using has_on_complete = + std::integral_constant::value>; + +} // detail +} // http +} // beast + +#endif diff --git a/include/beast/http/detail/error.hpp b/include/beast/http/detail/error.hpp deleted file mode 100644 index 414664ef..00000000 --- a/include/beast/http/detail/error.hpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) 2013-2016 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_DETAIL_ERROR_HPP -#define BEAST_HTTP_DETAIL_ERROR_HPP - -#include -#include - -namespace beast { -namespace http { -namespace detail { - -class message_category - : public boost::system::error_category -{ -public: - const char* - name() const noexcept override - { - return "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_error(int http_errno) -{ - static message_category const mc{}; - return boost::system::error_code{http_errno, mc}; -} - -} // detail -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp index 15b624d8..ffa22786 100644 --- a/include/beast/http/impl/basic_parser.ipp +++ b/include/beast/http/impl/basic_parser.ipp @@ -8,306 +8,1060 @@ #ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP #define BEAST_HTTP_IMPL_BASIC_PARSER_IPP -#include -#include -#include - namespace beast { namespace http { -namespace detail { - -inline -beast::http::method_t -convert_http_method(http_method m) +template +bool +basic_parser:: +keep_alive() const { - using namespace beast; - switch (m) + if(http_major_ > 0 && http_minor_ > 0) { - case HTTP_DELETE: return http::method_t::http_delete; - case HTTP_GET: return http::method_t::http_get; - case HTTP_HEAD: return http::method_t::http_head; - case HTTP_POST: return http::method_t::http_post; - case HTTP_PUT: return http::method_t::http_put; - - // pathological - case HTTP_CONNECT: return http::method_t::http_connect; - case HTTP_OPTIONS: return http::method_t::http_options; - case HTTP_TRACE: return http::method_t::http_trace; - - // webdav - case HTTP_COPY: return http::method_t::http_copy; - case HTTP_LOCK: return http::method_t::http_lock; - case HTTP_MKCOL: return http::method_t::http_mkcol; - case HTTP_MOVE: return http::method_t::http_move; - case HTTP_PROPFIND: return http::method_t::http_propfind; - case HTTP_PROPPATCH: return http::method_t::http_proppatch; - case HTTP_SEARCH: return http::method_t::http_search; - case HTTP_UNLOCK: return http::method_t::http_unlock; - case HTTP_BIND: return http::method_t::http_bind; - case HTTP_REBIND: return http::method_t::http_rebind; - case HTTP_UNBIND: return http::method_t::http_unbind; - case HTTP_ACL: return http::method_t::http_acl; - - // subversion - case HTTP_REPORT: return http::method_t::http_report; - case HTTP_MKACTIVITY: return http::method_t::http_mkactivity; - case HTTP_CHECKOUT: return http::method_t::http_checkout; - case HTTP_MERGE: return http::method_t::http_merge; - - // upnp - case HTTP_MSEARCH: return http::method_t::http_msearch; - case HTTP_NOTIFY: return http::method_t::http_notify; - case HTTP_SUBSCRIBE: return http::method_t::http_subscribe; - case HTTP_UNSUBSCRIBE: return http::method_t::http_unsubscribe; - - // RFC-5789 - case HTTP_PATCH: return http::method_t::http_patch; - case HTTP_PURGE: return http::method_t::http_purge; - - // CalDav - case HTTP_MKCALENDAR: return http::method_t::http_mkcalendar; - - // RFC-2068, section 19.6.1.2 - case HTTP_LINK: return http::method_t::http_link; - case HTTP_UNLINK: return http::method_t::http_unlink; - }; - - return http::method_t::http_get; + if(flags_ & parse_flag::connection_close) + return false; + } + else + { + if(! (flags_ & parse_flag::connection_keep_alive)) + return false; + } + return ! needs_eof(); } -} // detail +// Implementation inspired by nodejs/http-parser -template -basic_parser:: -basic_parser(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 -basic_parser::operator=(basic_parser&& other) -> - 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 -basic_parser:: -basic_parser(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 -basic_parser:: -operator=(basic_parser const& other) -> - 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 -basic_parser::basic_parser(bool request) noexcept -{ - state_.data = this; - http_parser_init(&state_, request - ? http_parser_type::HTTP_REQUEST - : http_parser_type::HTTP_RESPONSE); -} - -template +template std::size_t -basic_parser::write(void const* data, - std::size_t size, error_code& ec) +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_error( - static_cast(state_.http_errno)); - if(ec) - return 0; - return n; -} + using beast::http::detail::is_digit; + using beast::http::detail::is_token; + using beast::http::detail::is_text; + using beast::http::detail::to_field_char; + using beast::http::detail::to_value_char; + using beast::http::detail::unhex; -template -void -basic_parser::write_eof(error_code& ec) -{ - ec_ = &ec; - http_parser_execute( - &state_, hooks(), nullptr, 0); - if(! ec) - ec = detail::make_error( - static_cast(state_.http_errno)); -} - -template -void -basic_parser::check_header() -{ - if (! value_.empty()) + auto begin = + reinterpret_cast(data); + auto const end = begin + size; + auto p = begin; + auto used = [&] { - rfc2616::trim_right_in_place(value_); - call_on_field(field_, value_, - has_on_field{}); - field_.clear(); - value_.clear(); - } -} - -template -int -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.call_on_start(has_on_start{}); - return 0; -} - -template -int -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 -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 -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 -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; -} - -/* Called when all the headers are complete but before - the content body, if present. - Returning 1 from here tells the nodejs parser - that the message has no body (e.g. a HEAD request). -*/ -template -int -basic_parser::cb_headers_complete(http_parser* p) -{ - auto& t = *reinterpret_cast(p->data); - t.check_header(); - t.call_on_headers_complete(*t.ec_, - has_on_headers_complete{}); - if(*t.ec_) - return 1; - bool const keep_alive = - http_should_keep_alive(p) != 0; - if(p->type == http_parser_type::HTTP_REQUEST) + return p - reinterpret_cast(data); + }; + auto err = [&](parse_error ev) { - t.call_on_request(detail::convert_http_method( - http_method(p->method)), t.url_, - p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_request{}); - return 0; + ec = ev; + s_ = s_closed; + return used(); + }; + auto piece = [&] + { + return boost::string_ref{ + begin, static_cast(p - begin)}; + }; + auto cb = [&](pmf_t next) + { + if(cb_ && p != begin) + { + (this->*cb_)(ec, piece()); + if(ec) + return true; // error + } + cb_ = next; + if(cb_) + begin = p; + return false; + }; + for(;p != end; ++p) + { + unsigned char ch = *p; + redo: + switch(s_) + { + case s_closed: + return err(parse_error::connection_closed); + break; + + case s_req_start: + flags_ = 0; + cb_ = nullptr; + content_length_ = no_content_length; + s_ = s_req_method_start; + goto redo; + + case s_req_method_start: + if(! is_token(ch)) + return err(parse_error::bad_method); + cb_ = &self::call_on_method; + s_ = s_req_method; + break; + + case s_req_method: + if(! is_token(ch)) + { + if(cb(nullptr)) + return used(); + s_ = s_req_space_before_url; + goto redo; + } + break; + + case s_req_space_before_url: + if(ch != ' ') + return err(parse_error::bad_request); + s_ = s_req_url_start; + break; + + case s_req_url_start: + if(ch == ' ') + return err(parse_error::bad_uri); + // VFALCO TODO Require valid URL character + if(cb(&self::call_on_uri)) + return used(); + s_ = s_req_url; + break; + + case s_req_url: + if(ch == ' ') + { + if(cb(nullptr)) + return used(); + s_ = s_req_http_start; + break; + } + // VFALCO TODO Require valid URL character + break; + + case s_req_http_start: + if(ch != 'H') + return err(parse_error::bad_version); + s_ = s_req_http_H; + break; + + case s_req_http_H: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_req_http_HT; + break; + + case s_req_http_HT: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_req_http_HTT; + break; + + case s_req_http_HTT: + if(ch != 'P') + return err(parse_error::bad_version); + s_ = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + if(ch != '/') + return err(parse_error::bad_version); + s_ = s_req_major_start; + break; + + case s_req_major_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_req_major; + break; + + case s_req_major: + if(ch == '.') + { + s_ = s_req_minor_start; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = 10 * http_major_ + ch - '0'; + if(http_major_ > 999) + return err(parse_error::bad_version); + break; + + case s_req_minor_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_req_minor; + break; + + case s_req_minor: + if(ch == '\r') + { + s_ = s_req_line_end; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = 10 * http_minor_ + ch - '0'; + if(http_minor_ > 999) + return err(parse_error::bad_version); + break; + + case s_req_line_end: + if(ch != '\n') + return err(parse_error::bad_crlf); + call_on_request(ec); + if(ec) + return used(); + s_ = s_header_field_start; + break; + + //-------------------------------------------- + + case s_res_start: + flags_ = 0; + cb_ = nullptr; + content_length_ = no_content_length; + switch(ch) + { + case 'H': s_ = s_res_H; break; + case '\r': + case '\n': + break; + default: + return err(parse_error::bad_version); + } + break; + + case s_res_H: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_res_HT; + break; + + case s_res_HT: + if(ch != 'T') + return err(parse_error::bad_version); + s_ = s_res_HTT; + break; + + case s_res_HTT: + if(ch != 'P') + return err(parse_error::bad_version); + s_ = s_res_HTTP; + break; + + case s_res_HTTP: + if(ch != '/') + return err(parse_error::bad_version); + s_ = s_res_major_start; + break; + + case s_res_major_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = ch - '0'; + s_ = s_res_major; + break; + + case s_res_major: + if(ch == '.') + { + s_ = s_res_minor_start; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_major_ = 10 * http_major_ + ch - '0'; + if(http_major_ > 999) + return err(parse_error::bad_version); + break; + + case s_res_minor_start: + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = ch - '0'; + s_ = s_res_minor; + break; + + case s_res_minor: + if(ch == ' ') + { + s_ = s_res_status_code_start; + break; + } + if(! is_digit(ch)) + return err(parse_error::bad_version); + http_minor_ = 10 * http_minor_ + ch - '0'; + if(http_minor_ > 999) + return err(parse_error::bad_version); + break; + + case s_res_status_code_start: + if(! is_digit(ch)) + { + if(ch == ' ') + break; + return err(parse_error::bad_status_code); + } + status_code_ = ch - '0'; + s_ = s_res_status_code; + break; + + case s_res_status_code: + if(! is_digit(ch)) + { + switch(ch) + { + case ' ': s_ = s_res_status_start; break; + case '\r': s_ = s_res_line_almost_done; break; + case '\n': s_ = s_header_field_start; break; + default: + return err(parse_error::bad_status_code); + } + break; + } + status_code_ = status_code_ * 10 + ch - '0'; + if(status_code_ > 999) + return err(parse_error::bad_status_code); + break; + + case s_res_status_start: + if(ch == '\r') + { + s_ = s_res_line_almost_done; + break; + } + if(ch == '\n') + { + s_ = s_header_field_start; + break; + } + if(cb(&self::call_on_reason)) + return used(); + pos_ = 0; + break; + + case s_res_status: + if(ch == '\r') + { + if(cb(nullptr)) + return used(); + s_ = s_res_line_almost_done; + break; + } + if(ch == '\n') + { + if(cb(nullptr)) + return used(); + s_ = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_res_line_done; + break; + + case s_res_line_done: + call_on_response(ec); + if(ec) + return used(); + s_ = s_header_field_start; + goto redo; + + //-------------------------------------------- + + // message-header = field-name ":" [ field-value ] + // field-name = token + + case s_header_field_start: + { + if(ch == '\r') + { + s_ = s_headers_almost_done; + break; + } + auto c = to_field_char(ch); + if(! c) + return err(parse_error::bad_field); + switch(c) + { + case 'c': pos_ = 0; fs_ = h_C; break; + case 'p': pos_ = 0; fs_ = h_matching_proxy_connection; break; + case 't': pos_ = 0; fs_ = h_matching_transfer_encoding; break; + case 'u': pos_ = 0; fs_ = h_matching_upgrade; break; + default: + fs_ = h_general; + break; + } + if(cb(&self::call_on_field)) + return used(); + s_ = s_header_field; + break; + } + + case s_header_field: + { + for(; p != end; ch = *++p) + { + auto c = to_field_char(ch); + if(! c) + break; + switch(fs_) + { + case h_general: break; + case h_C: ++pos_; fs_ = c=='o' ? h_CO : h_general; break; + case h_CO: ++pos_; fs_ = c=='n' ? h_CON : h_general; break; + case h_CON: + ++pos_; + switch(c) + { + case 'n': fs_ = h_matching_connection; break; + case 't': fs_ = h_matching_content_length; break; + default: + fs_ = h_general; + } + break; + + case h_matching_connection: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::connection)-1 || + c != detail::parser_str::connection[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::connection)-2) + fs_ = h_connection; + break; + + case h_matching_proxy_connection: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::proxy_connection)-1 || + c != detail::parser_str::proxy_connection[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::proxy_connection)-2) + fs_ = h_connection; + break; + + case h_matching_content_length: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::content_length)-1 || + c != detail::parser_str::content_length[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::content_length)-2) + { + if(flags_ & parse_flag::contentlength) + return err(parse_error::bad_content_length); + fs_ = h_content_length; + flags_ |= parse_flag::contentlength; + } + break; + + case h_matching_transfer_encoding: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::transfer_encoding)-1 || + c != detail::parser_str::transfer_encoding[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::transfer_encoding)-2) + fs_ = h_transfer_encoding; + break; + + case h_matching_upgrade: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || + c != detail::parser_str::upgrade[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::upgrade)-2) + fs_ = h_upgrade; + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + // VFALCO Do we allow a space here? + fs_ = h_general; + break; + default: + break; + } + } + if(p == end) + { + --p; + break; + } + if(ch == ':') + { + if(cb(nullptr)) + return used(); + s_ = s_header_value_start; + break; + } + return err(parse_error::bad_field); + } + + // field-value = *( field-content | LWS ) + // field-content = *TEXT + // LWS = [CRLF] 1*( SP | HT ) + + case s_header_value_start: + if(ch == '\r') + { + s_ = s_header_value_discard_lWs0; + break; + } + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value_discard_ws0; + break; + } + s_ = s_header_value_text_start; + goto redo; + + case s_header_value_discard_ws0: + if(ch == ' ' || ch == '\t') + break; + if(ch == '\r') + { + s_ = s_header_value_discard_lWs0; + break; + } + s_ = s_header_value_text_start; + goto redo; + + case s_header_value_discard_lWs0: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_header_value_almost_done0; + break; + + case s_header_value_almost_done0: + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value_discard_ws0; + break; + } + call_on_value(ec, boost::string_ref{"", 0}); + if(ec) + return used(); + s_ = s_header_field_start; + goto redo; + + case s_header_value_text_start: + { + auto const c = to_value_char(ch); + if(! c) + return err(parse_error::bad_value); + switch(fs_) + { + case h_upgrade: + flags_ |= parse_flag::upgrade; + fs_ = h_general; + break; + + case h_transfer_encoding: + if(c == 'c') + fs_ = h_matching_transfer_encoding_chunked; + else + fs_ = h_general; + break; + + case h_content_length: + if(! is_digit(ch)) + return err(parse_error::bad_content_length); + content_length_ = ch - '0'; + break; + + case h_connection: + switch(c) + { + case 'k': fs_ = h_matching_connection_keep_alive; break; + case 'c': fs_ = h_matching_connection_close; break; + case 'u': fs_ = h_matching_connection_upgrade; break; + default: + fs_ = h_matching_connection_token; + break; + } + break; + + case h_matching_connection_token_start: + break; + + default: + fs_ = h_general; + break; + } + pos_ = 0; + if(cb(&self::call_on_value)) + return used(); + s_ = s_header_value_text; + break; + } + + case s_header_value_text: + { + for(; p != end; ch = *++p) + { + if(ch == '\r') + { + if(cb(nullptr)) + return used(); + s_ = s_header_value_discard_lWs; + break; + } + auto const c = to_value_char(ch); + if(! c) + return err(parse_error::bad_value); + switch(fs_) + { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0); + break; + + case h_content_length: + if(ch == ' ' || ch == '\t') + break; + if(! is_digit(ch)) + return err(parse_error::bad_content_length); + if(content_length_ > (no_content_length - 10) / 10) + return err(parse_error::bad_content_length); + content_length_ = + content_length_ * 10 + ch - '0'; + break; + + case h_matching_transfer_encoding_chunked: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::chunked)-1 || + c != detail::parser_str::chunked[pos_]) + fs_ = h_general; + else if(pos_ == sizeof(detail::parser_str::chunked)-2) + fs_ = h_transfer_encoding_chunked; + break; + + case h_matching_connection_token_start: + switch(c) + { + case 'k': fs_ = h_matching_connection_keep_alive; break; + case 'c': fs_ = h_matching_connection_close; break; + case 'u': fs_ = h_matching_connection_upgrade; break; + default: + if(is_token(c)) + fs_ = h_matching_connection_token; + else if(ch == ' ' || ch == '\t') + { } + else + fs_ = h_general; + break; + } + break; + + case h_matching_connection_keep_alive: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::keep_alive)-1 || + c != detail::parser_str::keep_alive[pos_]) + fs_ = h_matching_connection_token; + else if (pos_ == sizeof(detail::parser_str::keep_alive)- 2) + fs_ = h_connection_keep_alive; + break; + + case h_matching_connection_close: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::close)-1 || + c != detail::parser_str::close[pos_]) + fs_ = h_matching_connection_token; + else if(pos_ == sizeof(detail::parser_str::close)-2) + fs_ = h_connection_close; + break; + + case h_matching_connection_upgrade: + ++pos_; + if(pos_ >= sizeof(detail::parser_str::upgrade)-1 || + c != detail::parser_str::upgrade[pos_]) + fs_ = h_matching_connection_token; + else if (pos_ == sizeof(detail::parser_str::upgrade)-2) + fs_ = h_connection_upgrade; + break; + + case h_matching_connection_token: + if(ch == ',') + { + fs_ = h_matching_connection_token_start; + pos_ = 0; + } + break; + + case h_transfer_encoding_chunked: + if(ch != ' ' && ch != '\t') + fs_ = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if(ch ==',') + { + if(fs_ == h_connection_keep_alive) + flags_ |= parse_flag::connection_keep_alive; + else if(fs_ == h_connection_close) + flags_ |= parse_flag::connection_close; + else if(fs_ == h_connection_upgrade) + flags_ |= parse_flag::connection_upgrade; + fs_ = h_matching_connection_token_start; + pos_ = 0; + } + else if(ch != ' ' && ch != '\t') + { + fs_ = h_matching_connection_token; + } + break; + default: + break; + } + } + if(p == end) + --p; + break; + } + + case s_header_value_discard_ws: + if(ch == ' ' || ch == '\t') + break; + if(ch == '\r') + { + s_ = s_header_value_discard_lWs; + break; + } + if(! is_text(ch)) + return err(parse_error::bad_value); + call_on_value(ec, boost::string_ref(" ", 1)); + if(ec) + return used(); + if(cb(&self::call_on_value)) + return used(); + s_ = s_header_value_text; + break; + + case s_header_value_discard_lWs: + if(ch != '\n') + return err(parse_error::bad_crlf); + s_ = s_header_value_almost_done; + break; + + case s_header_value_almost_done: + if(ch == ' ' || ch == '\t') + { + s_ = s_header_value_discard_ws; + break; + } + switch(fs_) + { + case h_connection_keep_alive: flags_ |= parse_flag::connection_keep_alive; break; + case h_connection_close: flags_ |= parse_flag::connection_close; break; + case h_transfer_encoding_chunked: flags_ |= parse_flag::chunked; break; + case h_connection_upgrade: flags_ |= parse_flag::connection_upgrade; break; + default: + break; + } + s_ = s_header_field_start; + goto redo; + + case s_headers_almost_done: + { + if(ch != '\n') + return err(parse_error::bad_crlf); + if(flags_ & parse_flag::trailing) + { + //if(cb(&self::call_on_chunk_complete)) return used(); + s_ = s_complete; + goto redo; + } + if((flags_ & parse_flag::chunked) && (flags_ & parse_flag::contentlength)) + return err(parse_error::illegal_content_length); + upgrade_ = ((flags_ & (parse_flag::upgrade | parse_flag::connection_upgrade)) == + (parse_flag::upgrade | parse_flag::connection_upgrade)) /*|| method == "connect"*/; + auto const maybe_skip = call_on_headers(ec); + if(ec) + return used(); + switch(maybe_skip) + { + case 0: break; + case 2: upgrade_ = true; // fall through + case 1: flags_ |= parse_flag::skipbody; break; + default: + return err(parse_error::bad_on_headers_rv); + } + s_ = s_headers_done; + goto redo; + } + + case s_headers_done: + { + assert(! cb_); + call_on_headers(ec); + if(ec) + return used(); + bool const hasBody = + (flags_ & parse_flag::chunked) || (content_length_ > 0 && + content_length_ != no_content_length); + if(upgrade_ && (/*method == "connect" ||*/ (flags_ & parse_flag::skipbody) || ! hasBody)) + { + s_ = s_complete; + } + else if((flags_ & parse_flag::skipbody) || content_length_ == 0) + { + s_ = s_complete; + } + else if(flags_ & parse_flag::chunked) + { + s_ = s_chunk_size_start; + break; + } + else if(content_length_ == 0) + { + s_ = s_complete; + } + else if(content_length_ != no_content_length) + { + s_ = s_body_identity0; + break; + } + else if(! needs_eof()) + { + s_ = s_complete; + } + else + { + s_ = s_body_identity_eof0; + break; + } + goto redo; + } + + case s_body_identity0: + if(cb(&self::call_on_body)) + return used(); + s_ = s_body_identity; + goto redo; // VFALCO fall through? + + case s_body_identity: + { + std::size_t n; + if(static_cast((end - p)) < content_length_) + n = end - p; + else + n = static_cast(content_length_); + assert(content_length_ != 0 && content_length_ != no_content_length); + content_length_ -= n; + if(content_length_ == 0) + { + p += n - 1; + s_ = s_complete; + goto redo; // ???? + } + p += n - 1; + break; + } + + case s_body_identity_eof0: + if(cb(&self::call_on_body)) + return used(); + s_ = s_body_identity_eof; + goto redo; // VFALCO fall through? + + case s_body_identity_eof: + p = end - 1; + break; + + case s_chunk_size_start: + { + auto v = unhex(ch); + if(v == -1) + return err(parse_error::invalid_chunk_size); + content_length_ = v; + s_ = s_chunk_size; + break; + } + case s_chunk_size: + { + if(ch == '\r') + { + s_ = s_chunk_size_almost_done; + break; + } + auto v = unhex(ch); + if(v == -1) + { + if(ch == ';' || ch == ' ') + { + s_ = s_chunk_parameters; + break; + } + return err(parse_error::invalid_chunk_size); + } + if(content_length_ > (no_content_length - 16) / 16) + return err(parse_error::bad_content_length); + content_length_ = + content_length_ * 16 + v; + break; + } + + case s_chunk_parameters: + if(ch == '\r') + { + s_ = s_chunk_size_almost_done; + break; + } + break; + + case s_chunk_size_almost_done: + if(ch != '\n') + return err(parse_error::bad_crlf); + nread_ = 0; + if(content_length_ == 0) + { + flags_ |= parse_flag::trailing; + s_ = s_header_field_start; + break; + } + //call_chunk_header(ec); if(ec) return used(); + s_ = s_chunk_data_start; + break; + + case s_chunk_data_start: + if(cb(&self::call_on_body)) + return used(); + s_ = s_chunk_data; + goto redo; // VFALCO fall through? + + case s_chunk_data: + { + std::size_t n; + if(static_cast((end - p)) < content_length_) + n = end - p; + else + n = static_cast(content_length_); + content_length_ -= n; + p += n - 1; + if(content_length_ == 0) + s_ = s_chunk_data_almost_done; + break; + } + + case s_chunk_data_almost_done: + if(ch != '\r') + return err(parse_error::bad_crlf); + if(cb(nullptr)) + return used(); + s_ = s_chunk_data_done; + break; + + case s_chunk_data_done: + if(ch != '\n') + return err(parse_error::bad_crlf); + nread_ = 0; + s_ = s_chunk_size_start; + break; + + case s_complete: + ++p; + if(cb(nullptr)) + return used(); + call_on_complete(ec); + if(ec) + return used(); + s_ = s_restart; + return used(); + + case s_restart: + if(keep_alive()) + init(std::integral_constant{}); + else + s_ = s_closed; + goto redo; + } } - return t.call_on_response(p->status_code, t.status_, - p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_response{}) ? 0 : 1; + if(cb_) + { + (this->*cb_)(ec, piece()); + if(ec) + return used(); + } + return used(); } -// Called repeatedly for the content body, -// after any transfer-encoding is applied. -template -int -basic_parser::cb_body(http_parser* p, - char const* in, std::size_t bytes) +template +template +std::size_t +basic_parser:: +write(ConstBufferSequence const& buffers, error_code& ec) { - auto& t = *reinterpret_cast(p->data); - t.call_on_body(in, bytes, *t.ec_, has_on_body{}); - return *t.ec_ ? 1 : 0; + static_assert(is_ConstBufferSequence::value, + "ConstBufferSequence requirements not met"); + std::size_t used = 0; + for(auto const& buffer : buffers) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + used += write(buffer_cast(buffer), + buffer_size(buffer), ec); + if(ec) + break; + } + return used; } -// Called when the both the headers -// and content body (if any) are complete. -template -int -basic_parser::cb_message_complete(http_parser* p) +template +void +basic_parser:: +write_eof(error_code& ec) { - auto& t = *reinterpret_cast(p->data); - t.complete_ = true; - t.call_on_complete(has_on_complete{}); - return 0; + switch(s_) + { + case s_body_identity_eof: + cb_ = nullptr; + call_on_complete(ec); + if(ec) + return; + return; + default: + break; + } + ec = parse_error::short_read; + s_ = s_closed; } -template -int -basic_parser::cb_chunk_header(http_parser*) +template +bool +basic_parser:: +needs_eof(std::true_type) const { - return 0; + return false; } -template -int -basic_parser::cb_chunk_complete(http_parser*) +template +bool +basic_parser:: +needs_eof(std::false_type) const { - return 0; + // See RFC 2616 section 4.4 + if (status_code_ / 100 == 1 || // 1xx e.g. Continue + status_code_ == 204 || // No Content + status_code_ == 304 || // Not Modified + flags_ & parse_flag::skipbody) // response to a HEAD request + { + return false; + } + + if((flags_ & parse_flag::chunked) || content_length_ != no_content_length) + { + return false; + } + + return true; } } // http diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index a507d28a..19191ac4 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -38,6 +38,7 @@ class read_op message_type& m; parser_type p; Handler h; + bool started = false; bool cont; int state = 0; @@ -129,6 +130,8 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) bind_handler(std::move(*this), ec, 0)); return; } + if(used > 0) + d.started = true; d.sb.consume(used); if(d.p.complete()) { @@ -156,7 +159,7 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again) { if(ec == boost::asio::error::eof) { - if(! d.p.started()) + if(! d.started) { // call handler d.state = 99; @@ -219,6 +222,7 @@ read(SyncReadStream& stream, Streambuf& streambuf, static_assert(is_Streambuf::value, "Streambuf requirements not met"); parser p; + bool started = false; for(;;) { auto used = @@ -226,6 +230,8 @@ read(SyncReadStream& stream, Streambuf& streambuf, if(ec) return; streambuf.consume(used); + if(used > 0) + started = true; if(p.complete()) { m = p.release(); @@ -238,7 +244,7 @@ read(SyncReadStream& stream, Streambuf& streambuf, return; if(ec == boost::asio::error::eof) { - if(! p.started()) + if(! started) return; // Caller will see eof on next read. ec = {}; diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp new file mode 100644 index 00000000..29b051e3 --- /dev/null +++ b/include/beast/http/parse_error.hpp @@ -0,0 +1,157 @@ +// +// Copyright (c) 2013-2016 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_PARSE_ERROR_HPP +#define BEAST_HTTP_PARSE_ERROR_HPP + +#include +#include + +namespace beast { +namespace http { + +enum class parse_error +{ + connection_closed, + + bad_method, + bad_uri, + bad_version, + bad_crlf, + bad_request, + + bad_status_code, + bad_status, + + bad_field, + bad_value, + bad_content_length, + illegal_content_length, + bad_on_headers_rv, + + invalid_chunk_size, + + short_read +}; + +class parse_error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "http"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case parse_error::connection_closed: + return "data after Connection close"; + + case parse_error::bad_method: + return "bad method"; + + case parse_error::bad_uri: + return "bad Request-URI"; + + case parse_error::bad_version: + return "bad HTTP-Version"; + + case parse_error::bad_crlf: + return "missing CRLF"; + + case parse_error::bad_request: + return "bad Request-Line"; + + case parse_error::bad_status_code: + return "bad Status-Code"; + + case parse_error::bad_status: + return "bad Status-Line"; + + case parse_error::bad_field: + return "bad field token"; + + case parse_error::bad_value: + return "bad field-value"; + + case parse_error::bad_content_length: + return "bad Content-Length"; + + case parse_error::illegal_content_length: + return "illegal Content-Length with chunked Transfer-Encoding"; + + case parse_error::bad_on_headers_rv: + return "on_headers returned an unknown value"; + + case parse_error::invalid_chunk_size: + return "invalid chunk size"; + + case parse_error::short_read: + return "unexpected end of data"; + + default: + return "beast::http::parser error"; + } + } + + 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(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_parse_error_category() +{ + static parse_error_category const cat{}; + return cat; +} + +inline +boost::system::error_code +make_error_code(parse_error ev) +{ + return error_code(static_cast(ev), + get_parse_error_category()); +} + +} // http +} // beast + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +#endif diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp index fd4f4eaa..a85ef39b 100644 --- a/include/beast/http/parser.hpp +++ b/include/beast/http/parser.hpp @@ -13,43 +13,51 @@ #include #include #include +#include #include #include namespace beast { namespace http { -/** A HTTP parser. +namespace detail { + +struct parser_request +{ + std::string method_; + std::string uri_; +}; + +struct parser_response +{ + std::string reason_; +}; + +} // detail - The parser may only be used once. -*/ template class parser - : public basic_parser> + : public basic_parser> + , private std::conditional::type { using message_type = message; + std::string field_; + std::string value_; message_type m_; typename message_type::body_type::reader r_; - bool started_ = false; public: parser(parser&&) = default; parser() - : http::basic_parser(isRequest) - , r_(m_) + : r_(m_) { } - /// Returns `true` if at least one byte has been processed - bool - started() - { - return started_; - } - message_type release() { @@ -57,94 +65,164 @@ public: } private: - friend class http::basic_parser; + friend class basic_parser; - void - on_start() + void flush() { - started_ = true; + if(! value_.empty()) + { + rfc2616::trim_right_in_place(value_); + // VFALCO could std::move + m_.headers.insert(field_, value_); + field_.clear(); + value_.clear(); + } } - void - on_field(std::string const& field, std::string const& value) + void on_method(boost::string_ref const& s, error_code&) { - m_.headers.insert(field, value); + this->method_.append(s.data(), s.size()); } - void - on_headers_complete(error_code&) + void on_uri(boost::string_ref const& s, error_code&) { - // vFALCO TODO Decode the Content-Length and - // Transfer-Encoding, see if we can reserve the buffer. - // - // r_.reserve(content_length) + this->uri_.append(s.data(), s.size()); } - bool - on_request(http::method_t method, std::string const& url, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) + void on_reason(boost::string_ref const& s, error_code&) { - m_.method = method; - m_.url = url; - m_.version = major * 10 + minor; - return true; + this->reason_.append(s.data(), s.size()); } - bool - on_request(http::method_t, std::string const&, - int, int, bool, bool, - std::false_type) + void on_field(boost::string_ref const& s, error_code&) { - return true; + flush(); + field_.append(s.data(), s.size()); } - bool - on_request(http::method_t method, std::string const& url, - int major, int minor, bool keep_alive, bool upgrade) + void on_value(boost::string_ref const& s, error_code&) { - return on_request(method, url, - major, minor, keep_alive, upgrade, - typename message_type::is_request{}); + value_.append(s.data(), s.size()); } - bool - on_response(int status, std::string const& reason, - int major, int minor, bool keep_alive, bool upgrade, - std::true_type) + void set(std::true_type) { - m_.status = status; - m_.reason = reason; - m_.version = major * 10 + minor; - // VFALCO TODO return expect_body_ - return true; - } - - bool - on_response(int, std::string const&, int, int, bool, bool, - std::false_type) - { - return true; + // VFALCO This is terrible for setting method + auto m = + [&](char const* s, method_t m) + { + if(this->method_ == s) + { + m_.method = m; + return true; + } + return false; + }; + while(false) + { + if(m("DELETE", method_t::http_delete)) + break; + if(m("GET", method_t::http_get)) + break; + if(m("HEAD", method_t::http_head)) + break; + if(m("POST", method_t::http_post)) + break; + if(m("PUT", method_t::http_put)) + break; + if(m("CONNECT", method_t::http_connect)) + break; + if(m("OPTIONS", method_t::http_options)) + break; + if(m("TRACE", method_t::http_trace)) + break; + if(m("COPY", method_t::http_copy)) + break; + if(m("LOCK", method_t::http_lock)) + break; + if(m("MKCOL", method_t::http_mkcol)) + break; + if(m("MOVE", method_t::http_move)) + break; + if(m("PROPFIND", method_t::http_propfind)) + break; + if(m("PROPPATCH", method_t::http_proppatch)) + break; + if(m("SEARCH", method_t::http_search)) + break; + if(m("UNLOCK", method_t::http_unlock)) + break; + if(m("BIND", method_t::http_bind)) + break; + if(m("REBID", method_t::http_rebind)) + break; + if(m("UNBIND", method_t::http_unbind)) + break; + if(m("ACL", method_t::http_acl)) + break; + if(m("REPORT", method_t::http_report)) + break; + if(m("MKACTIVITY", method_t::http_mkactivity)) + break; + if(m("CHECKOUT", method_t::http_checkout)) + break; + if(m("MERGE", method_t::http_merge)) + break; + if(m("MSEARCH", method_t::http_msearch)) + break; + if(m("NOTIFY", method_t::http_notify)) + break; + if(m("SUBSCRIBE", method_t::http_subscribe)) + break; + if(m("UNSUBSCRIBE",method_t::http_unsubscribe)) + break; + if(m("PATCH", method_t::http_patch)) + break; + if(m("PURGE", method_t::http_purge)) + break; + if(m("MKCALENDAR", method_t::http_mkcalendar)) + break; + if(m("LINK", method_t::http_link)) + break; + if(m("UNLINK", method_t::http_unlink)) + break; + } + + m_.url = std::move(this->uri_); + } - bool - on_response(int status, std::string const& reason, - int major, int minor, bool keep_alive, bool upgrade) + void set(std::false_type) { - return on_response( - status, reason, major, minor, keep_alive, upgrade, - std::integral_constant{}); + m_.status = this->status_code(); + m_.reason = this->reason_; } - void - on_body(void const* data, - std::size_t size, error_code& ec) + int on_headers(error_code&) { - r_.write(data, size, ec); + flush(); + m_.version = 10 * this->http_major() + this->http_minor(); + return 0; } - void - on_complete() + void on_request(error_code& ec) + { + set(std::integral_constant< + bool, isRequest>{}); + } + + void on_response(error_code& ec) + { + set(std::integral_constant< + bool, isRequest>{}); + } + + void on_body(boost::string_ref const& s, error_code& ec) + { + r_.write(s.data(), s.size(), ec); + } + + void on_complete(error_code&) { } }; diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp new file mode 100644 index 00000000..3f69354a --- /dev/null +++ b/include/beast/http/rfc7230.hpp @@ -0,0 +1,23 @@ +// +// Copyright (c) 2013-2016 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_RFC7230_HPP +#define BEAST_HTTP_RFC7230_HPP + +#include +#include + +namespace beast { +namespace rfc7230 { + + + +} // rfc7230 +} // beast + +#endif + diff --git a/include/beast/write_streambuf.hpp b/include/beast/write_streambuf.hpp index 750a024f..f4e92485 100644 --- a/include/beast/write_streambuf.hpp +++ b/include/beast/write_streambuf.hpp @@ -21,11 +21,15 @@ namespace beast { argument into the stream buffer. It is capable of converting the following types of arguments: - * `boost::asio::const_buffer` - * `boost::asio::mutable_buffer` - * A type for which the call to `boost::asio::buffer()` is defined - * A type meeting the requirements of `ConstBufferSequence` - * A type meeting the requirements of `MutableBufferSequence` + @li `boost::asio::const_buffer` + + @li `boost::asio::mutable_buffer` + + @li A type for which the call to `boost::asio::buffer()` is defined + + @li A type meeting the requirements of `ConstBufferSequence` + + @li A type meeting the requirements of `MutableBufferSequence` For all types not listed above, the function will invoke `boost::lexical_cast` on the argument in an attempt to convert to @@ -38,7 +42,7 @@ namespace beast { @param args A list of one or more arguments to write. - @throws Any exceptions thrown by `boost::lexical_cast`. + @throws unspecified Any exceptions thrown by `boost::lexical_cast`. @note This function participates in overload resolution only if the `streambuf` parameter meets the requirements of Streambuf. diff --git a/test/Jamfile b/test/Jamfile index 356cda9a..cb947c2b 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -7,7 +7,7 @@ import os ; -unit-test core_tests : +unit-test core-tests : main.cpp async_completion.cpp basic_streambuf.cpp @@ -27,9 +27,8 @@ unit-test core_tests : detail/empty_base_optimization.cpp ; -unit-test http_tests : +unit-test http-tests : main.cpp - ../src/beast_http_nodejs_parser.cpp http/basic_headers.cpp http/basic_parser.cpp http/chunk_encode.cpp @@ -38,17 +37,19 @@ unit-test http_tests : http/headers.cpp http/message.cpp http/method.cpp + http/parse_error.cpp http/parser.cpp http/read.cpp http/reason.cpp http/resume_context.cpp http/rfc2616.cpp + http/rfc7230.cpp http/streambuf_body.cpp http/string_body.cpp http/write.cpp ; -unit-test websocket_tests : +unit-test websocket-tests : main.cpp websocket/error.cpp websocket/option.cpp @@ -56,3 +57,9 @@ unit-test websocket_tests : websocket/static_string.cpp websocket/teardown.cpp ; + +exe parser-bench : + main.cpp + http/nodejs_parser.cpp + http/parser_bench.cpp + ; diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp index 1bffe9af..48627f86 100644 --- a/test/http/basic_parser.cpp +++ b/test/http/basic_parser.cpp @@ -7,3 +7,507 @@ // Test that header file is self-contained. #include + +#include "message_fuzz.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class basic_parser_test : public beast::detail::unit_test::suite +{ + std::mt19937 rng_; + +public: + static + std::string + escaped_string(boost::string_ref const& s) + { + std::string out; + out.reserve(s.size()); + char const* p = s.data(); + while(p != s.end()) + { + if(*p == '\r') + out.append("\\r"); + else if(*p == '\n') + out.append("\\n"); + else if (*p == '\t') + out.append("\\t"); + else + out.append(p, 1); + ++p; + } + return out; + } + + template + struct null_parser : basic_parser> + { + }; + + template + class test_parser : + public basic_parser> + { + std::string field_; + std::string value_; + + void check() + { + if(! value_.empty()) + { + rfc2616::trim_right_in_place(value_); + fields.emplace(field_, value_); + field_.clear(); + value_.clear(); + } + } + + public: + std::map fields; + std::string body; + + void on_field(boost::string_ref const& s, error_code&) + { + check(); + field_.append(s.data(), s.size()); + } + + void on_value(boost::string_ref const& s, error_code&) + { + value_.append(s.data(), s.size()); + } + + int on_headers(error_code&) + { + check(); + return 0; + } + + void on_body(boost::string_ref const& s, error_code&) + { + body.append(s.data(), s.size()); + } + }; + + // Parse the entire input buffer as a valid message, + // then parse in two pieces of all possible lengths. + // + template + void + parse(boost::string_ref const& m, F&& f) + { + { + error_code ec; + Parser p; + p.write(m.data(), m.size(), ec); + if(expect(p.complete())) + if(expect(! ec, ec.message())) + f(p); + } + for(std::size_t i = 1; i < m.size() - 1; ++i) + { + error_code ec; + Parser p; + p.write(&m[0], i, ec); + if(! expect(! ec, ec.message())) + continue; + if(p.complete()) + { + f(p); + } + else + { + p.write(&m[i], m.size() - i, ec); + if(! expect(! ec, ec.message())) + continue; + expect(p.complete()); + f(p); + } + } + } + + // Parse with an expected error code + // + template + void + parse_ev(boost::string_ref const& m, parse_error ev) + { + { + error_code ec; + null_parser p; + p.write(m.data(), m.size(), ec); + if(expect(! p.complete())) + expect(ec == ev, ec.message()); + } + for(std::size_t i = 1; i < m.size() - 1; ++i) + { + error_code ec; + null_parser p; + p.write(&m[0], i, ec); + if(! expect(! p.complete())) + continue; + if(ec) + { + expect(ec == ev, ec.message()); + continue; + } + p.write(&m[i], m.size() - i, ec); + if(! expect(! p.complete())) + continue; + if(! expect(ec == ev, ec.message())) + continue; + } + } + + //-------------------------------------------------------------------------- + + template + UInt + rand(std::size_t n) + { + return static_cast( + std::uniform_int_distribution< + std::size_t>{0, n-1}(rng_)); + } + + //-------------------------------------------------------------------------- + + // Parse a valid message with expected version + // + template + void + version(boost::string_ref const& m, + unsigned major, unsigned minor) + { + parse>(m, + [&](null_parser const& p) + { + expect(p.http_major() == major); + expect(p.http_minor() == minor); + }); + } + + // Parse a valid message with expected flags mask + // + void + checkf(boost::string_ref const& m, std::uint8_t mask) + { + parse>(m, + [&](null_parser const& p) + { + expect(p.flags() & mask); + }); + } + + void + testVersion() + { + version("GET / HTTP/0.0\r\n\r\n", 0, 0); + version("GET / HTTP/0.1\r\n\r\n", 0, 1); + version("GET / HTTP/0.9\r\n\r\n", 0, 9); + version("GET / HTTP/1.0\r\n\r\n", 1, 0); + version("GET / HTTP/1.1\r\n\r\n", 1, 1); + version("GET / HTTP/9.9\r\n\r\n", 9, 9); + version("GET / HTTP/999.999\r\n\r\n", 999, 999); + parse_ev("GET / HTTP/1000.0\r\n\r\n", parse_error::bad_version); + parse_ev("GET / HTTP/0.1000\r\n\r\n", parse_error::bad_version); + parse_ev("GET / HTTP/99999999999999999999.0\r\n\r\n", parse_error::bad_version); + parse_ev("GET / HTTP/0.99999999999999999999\r\n\r\n", parse_error::bad_version); + } + + void + testConnection(std::string const& token, + std::uint8_t flag) + { + checkf("GET / HTTP/1.1\r\nConnection:" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection:\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: \t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + " \r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + " \t\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: " + token + "\t \r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: \r\n" " " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" " " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: \r\n" "\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection:\t\r\n" "\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X, " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X,\t" + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X,\t " + token + "\r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X," + token + " \r\n\r\n", flag); + checkf("GET / HTTP/1.1\r\nConnection: X," + token + "\t\r\n\r\n", flag); + } + + void + testContentLength() + { + std::size_t const length = 0; + std::string const length_s = + std::to_string(length); + + checkf("GET / HTTP/1.1\r\nContent-Length:"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length:\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: \t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + " \t\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: "+ length_s + "\t \r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" " "+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length: \r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + checkf("GET / HTTP/1.1\r\nContent-Length:\t\r\n" "\t"+ length_s + "\r\n\r\n", parse_flag::contentlength); + } + + void + testTransferEncoding() + { + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked \t\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\t \r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" " chunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding: \r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked); + checkf("GET / HTTP/1.1\r\nTransfer-Encoding:\t\r\n" "\tchunked\r\n\r\n0\r\n\r\n", parse_flag::chunked ); + } + + void + testFlags() + { + testConnection("keep-alive", + parse_flag::connection_keep_alive); + + testConnection("close", + parse_flag::connection_close); + + testConnection("upgrade", + parse_flag::connection_upgrade); + + testContentLength(); + + testTransferEncoding(); + + checkf( + "GET / HTTP/1.1\r\n" + "Upgrade: x\r\n" + "\r\n", + parse_flag::upgrade + ); + + parse_ev( + "GET / HTTP/1.1\r\n" + "Transfer-Encoding:chunked\r\n" + "Content-Length: 0\r\n" + "\r\n", parse_error::illegal_content_length); + } + + void + testUpgrade() + { + null_parser p; + boost::string_ref s = + "GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n"; + error_code ec; + p.write(s.data(), s.size(), ec); + if(! expect(! ec, ec.message())) + return; + expect(p.complete()); + expect(p.upgrade()); + } + + void testBad() + { + parse_ev(" ", parse_error::bad_method); + parse_ev(" G", parse_error::bad_method); + parse_ev("G:", parse_error::bad_request); + parse_ev("GET /", parse_error::bad_uri); + parse_ev("GET / X", parse_error::bad_version); + parse_ev("GET / HX", parse_error::bad_version); + parse_ev("GET / HTTX", parse_error::bad_version); + parse_ev("GET / HTTPX", parse_error::bad_version); + parse_ev("GET / HTTP/.", parse_error::bad_version); + parse_ev("GET / HTTP/1000", parse_error::bad_version); + parse_ev("GET / HTTP/1. ", parse_error::bad_version); + parse_ev("GET / HTTP/1.1000", parse_error::bad_version); + parse_ev("GET / HTTP/1.1\r ", parse_error::bad_crlf); + parse_ev("GET / HTTP/1.1\r\nf :", parse_error::bad_field); + } + + void + testRandomReq(std::size_t N) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + message_fuzz mg; + for(std::size_t i = 0; i < N; ++i) + { + std::string s; + { + streambuf sb; + mg.request(sb); + s.reserve(buffer_size(sb.data())); + for(auto const& b : sb.data()) + s.append(buffer_cast(b), + buffer_size(b)); + } + null_parser p; + for(std::size_t j = 1; j < s.size() - 1; ++j) + { + error_code ec; + p.write(&s[0], j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + if(! p.complete()) + { + p.write(&s[j], s.size() - j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + } + if(! expect(p.complete())) + break; + if(! p.keep_alive()) + { + p.~null_parser(); + new(&p) null_parser{}; + } + } + } + } + + void + testRandomResp(std::size_t N) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + message_fuzz mg; + for(std::size_t i = 0; i < N; ++i) + { + std::string s; + { + streambuf sb; + mg.response(sb); + s.reserve(buffer_size(sb.data())); + for(auto const& b : sb.data()) + s.append(buffer_cast(b), + buffer_size(b)); + } + null_parser p; + for(std::size_t j = 1; j < s.size() - 1; ++j) + { + error_code ec; + p.write(&s[0], j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + if(! p.complete()) + { + p.write(&s[j], s.size() - j, ec); + if(! expect(! ec, ec.message())) + { + log << escaped_string(s); + break; + } + } + if(! expect(p.complete())) + break; + if(! p.keep_alive()) + { + p.~null_parser(); + new(&p) null_parser{}; + } + } + } + } + + void testBody() + { + auto match = + [&](std::string const& body) + { + return + [&](test_parser const& p) + { + expect(p.body == body); + }; + }; + parse>( + "GET / HTTP/1.1\r\nContent-Length: 1\r\n\r\n123", match("1")); + parse>( + "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n123", match("123")); + parse>( + "GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", match("")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "1\r\n" + "a\r\n" + "0\r\n" + "\r\n", match("a")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "2\r\n" + "ab\r\n" + "0\r\n" + "\r\n", match("ab")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "2\r\n" + "ab\r\n" + "1\r\n" + "c\r\n" + "0\r\n" + "\r\n", match("abc")); + parse>( + "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + "10\r\n" + "1234567890123456\r\n" + "0\r\n" + "\r\n", match("1234567890123456")); + } + + void run() override + { + testVersion(); + testFlags(); + testUpgrade(); + testBad(); + testRandomReq(100); + testRandomResp(100); + testBody(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_parser,http,beast); + +} // http +} // beast diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp new file mode 100644 index 00000000..7d0254ee --- /dev/null +++ b/test/http/message_fuzz.hpp @@ -0,0 +1,562 @@ +// +// Copyright (c) 2013-2016 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_TEST_MESSAGE_FUZZ_HPP +#define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +std::string +escaped_string(boost::string_ref const& s) +{ + std::string out; + out.reserve(s.size()); + char const* p = s.data(); + while(p != s.end()) + { + if(*p == '\r') + out.append("\\r"); + else if(*p == '\n') + out.append("\\n"); + else if (*p == '\t') + out.append("\\t"); + else + out.append(p, 1); + ++p; + } + return out; +} + +// Produces random HTTP messages +// +template +class message_fuzz_t +{ + std::mt19937 rng_; + + static + std::string + to_hex(std::size_t v) + { + if(! v) + return "0"; + std::string s; + while(v > 0) + { + s.insert(s.begin(), + "0123456789abcdef"[v&0xf]); + v >>= 4; + } + return s; + } + +public: + template + UInt + rand(std::size_t n) + { + return static_cast( + std::uniform_int_distribution< + std::size_t>{0, n-1}(rng_)); + } + + std::string + method() + { +#if 0 + // All IANA registered methods + static char const* const list[39] = { + "ACL", "BASELINE-CONTROL", "BIND", "CHECKIN", "CHECKOUT", "CONNECT", + "COPY", "DELETE", "GET", "HEAD", "LABEL", "LINK", "LOCK", "MERGE", + "MKACTIVITY", "MKCALENDAR", "MKCOL", "MKREDIRECTREF", "MKWORKSPACE", + "MOVE", "OPTIONS", "ORDERPATCH", "PATCH", "POST", "PRI", "PROPFIND", + "PROPPATCH", "PUT", "REBIND", "REPORT", "SEARCH", "TRACE", "UNBIND", + "UNCHECKOUT", "UNLINK", "UNLOCK", "UPDATE", "UPDATEREDIRECTREF", + "VERSION-CONTROL" + }; + return list[rand(39)]; +#else + // methods parsed by nodejs-http-parser + static char const* const list[33] = { + "ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "HEAD", "GET", + "LINK", "LOCK", "MERGE", "MKCOL", "MKCALENDAR", "MKACTIVITY", "M-SEARCH", + "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH", + "PURGE", "PUT", "REBIND", "REPORT", "SEARCH", "SUBSCRIBE", "TRACE", + "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE" + }; + return list[rand(33)]; +#endif + } + + std::string + scheme() + { + static char const* const list[241] = { + "aaa", "aaas", "about", "acap", "acct", "acr", "adiumxtra", "afp", "afs", + "aim", "appdata", "apt", "attachment", "aw", "barion", "beshare", "bitcoin", + "blob", "bolo", "callto", "cap", "chrome", "chrome-extension", "cid", + "coap", "coaps", "com-eventbrite-attendee", "content", "crid", "cvs", + "data", "dav", "dict", "dis", "dlna-playcontainer", "dlna-playsingle", + "dns", "dntp", "dtn", "dvb", "ed2k", "example", "facetime", "fax", "feed", + "feedready", "file", "filesystem", "finger", "fish", "ftp", "geo", "gg", + "git", "gizmoproject", "go", "gopher", "gtalk", "h323", "ham", "hcp", + "http", "https", "iax", "icap", "icon", "im", "imap", "info", "iotdisco", + "ipn", "ipp", "ipps", "irc", "irc6", "ircs", "iris", "iris.beep", + "iris.lwz", "iris.xpc", "iris.xpcs", "isostore", "itms", "jabber", "jar", + "jms", "keyparc", "lastfm", "ldap", "ldaps", "magnet", "mailserver", + "mailto", "maps", "market", "message", "mid", "mms", + "modem", "ms-access", "ms-drive-to", "ms-enrollment", "ms-excel", + "ms-getoffice", "ms-help", "ms-infopath", "ms-media-stream-id", "ms-project", + "ms-powerpoint", "ms-publisher", "ms-search-repair", + "ms-secondary-screen-controller", "ms-secondary-screen-setup", + "ms-settings", "ms-settings-airplanemode", "ms-settings-bluetooth", + "ms-settings-camera", "ms-settings-cellular", "ms-settings-cloudstorage", + "ms-settings-emailandaccounts", "ms-settings-language", "ms-settings-location", + "ms-settings-lock", "ms-settings-nfctransactions", "ms-settings-notifications", + "ms-settings-power", "ms-settings-privacy", "ms-settings-proximity", + "ms-settings-screenrotation", "ms-settings-wifi", "ms-settings-workplace", + "ms-spd", "ms-transit-to", "ms-visio", "ms-walk-to", "ms-word", "msnim", + "msrp", "msrps", "mtqp", "mumble", "mupdate", "mvn", "news", "nfs", "ni", + "nih", "nntp", "notes", "oid", "opaquelocktoken", "pack", "palm", "paparazzi", + "pkcs11", "platform", "pop", "pres", "prospero", "proxy", "psyc", "query", + "redis", "rediss", "reload", "res", "resource", "rmi", "rsync", "rtmfp", + "rtmp", "rtsp", "rtsps", "rtspu", "secondlife", "service", "session", "sftp", + "sgn", "shttp", "sieve", "sip", "sips", "skype", "smb", "sms", "smtp", + "snews", "snmp", "soap.beep", "soap.beeps", "soldat", "spotify", "ssh", + "steam", "stun", "stuns", "submit", "svn", "tag", "teamspeak", "tel", + "teliaeid", "telnet", "tftp", "things", "thismessage", "tip", "tn3270", + "tool", "turn", "turns", "tv", "udp", "unreal", "urn", "ut2004", "v-event", + "vemmi", "ventrilo", "videotex", "vnc", "view-source", "wais", "webcal", + "wpid", "ws", "wss", "wtai", "wyciwyg", "xcon", "xcon-userid", "xfire", + "xmlrpc.beep", "xmlrpc.beeps", "xmpp", "xri", "ymsgr", "z39.50", "z39.50r", + "z39.50s:" + }; + return list[rand(241)]; + } + + std::string + pchar() + { + if(rand(4)) + return std::string(1, + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + ":@&=+$,"[rand(69)]); + std::string s = "%"; + s += "0123456789abcdef"[rand(16)]; + s += "0123456789abcdef"[rand(16)]; + return s; + } + + char + uric() + { + return 'a'; + } + + char + uric_no_slash() + { + return 'a'; + } + + std::string + param() + { + std::string s; + while(rand(2)) + s += pchar(); + return s; + } + + std::string + query() + { + std::string s; + while(rand(2)) + s += uric(); + return s; + } + + std::string + userinfo() + { + std::string s; + while(rand(2)) + s += "a"; + return s; + } + + /* authority = server | reg_name + + reg_name = 1*( unreserved | escaped | "$" | "," | + ";" | ":" | "@" | "&" | "=" | "+" ) + + server = [ [ userinfo "@" ] hostport ] + userinfo = *( unreserved | escaped | + ";" | ":" | "&" | "=" | "+" | "$" | "," ) + + hostport = host [ ":" port ] + host = hostname | IPv4address + hostname = *( domainlabel "." ) toplabel [ "." ] + domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + toplabel = alpha | alpha *( alphanum | "-" ) alphanum + IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit + port = *digit + */ + std::string + server() + { + std::string s; + if(rand(2)) + s += userinfo() + "@"; + return s; + } + + std::string + reg_name() + { + std::string s; + s = "a"; + while(rand(2)) + s += "a"; + return s; + } + + std::string + authority() + { + if(rand(2)) + return server(); + return reg_name(); + } + + std::string + opaque_part() + { + std::string s; + s += uric_no_slash(); + while(rand(2)) + s += uric(); + return s; + } + + /* abs_path = "/" path_segments + path_segments = segment *( "/" segment ) + segment = *pchar *( ";" param ) + param = *pchar + pchar = unreserved | escaped | + ":" | "@" | "&" | "=" | "+" | "$" | "," + */ + std::string + abs_path() + { + std::string s = "/"; + for(;;) + { + while(rand(2)) + s += pchar(); + while(rand(2)) + s += ";" + param(); + if(rand(2)) + break; + s.append("/"); + } + return s; + } + + /* net_path = "//" authority [ abs_path ] + */ + std::string + net_path() + { + std::string s = "//"; + s += authority(); + if(rand(2)) + s += abs_path(); + return s; + } + + /* absoluteURI = scheme ":" ( hier_part | opaque_part ) + scheme = alpha *( alpha | digit | "+" | "-" | "." ) + hier_part = ( net_path | abs_path ) [ "?" query ] + abs_path = "/" path_segments + query = *uric + opaque_part = uric_no_slash *uric + */ + std::string + abs_uri() + { + std::string s; + s = scheme() + ":"; + if(rand(2)) + { + if(rand(2)) + s += net_path(); + else + s += abs_path(); + if(rand(2)) + s += "?" + query(); + } + else + { + s += opaque_part(); + } + return s; + } + + std::string + uri() + { + //switch(rand(4)) + switch(1) + { + case 0: return abs_uri(); + case 1: return abs_path(); + case 2: return authority(); + default: + case 3: break; + } + return "*"; + } + + std::string + token() + { + static char constexpr valid[78] = + "!#$%&\'*+-." "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" + "UVWXYZ^_`a" "bcdefghijk" "lmnopqrstu" "vwxyz|~"; + std::string s; + s.append(1, valid[rand(77)]); + while(rand(4)) + s.append(1, valid[rand(77)]); + return s; + } + +#if 0 + std::string + uri() + { + static char constexpr alpha[63] = + "0123456789" "ABCDEFGHIJ" "KLMNOPQRST" + "UVWXYZabcd" "efghijklmn" "opqrstuvwx" "yz"; + std::string s; + s = "/"; + while(rand(4)) + s.append(1, alpha[rand(62)]); + return s; + } +#endif + + std::string + field() + { static char const* const list[289] = + { + "A-IM", + "Accept", "Accept-Additions", "Accept-Charset", "Accept-Datetime", "Accept-Encoding", + "Accept-Features", "Accept-Language", "Accept-Patch", "Accept-Ranges", "Age", "Allow", + "ALPN", "Also-Control", "Alt-Svc", "Alt-Used", "Alternate-Recipient", "Alternates", + "Apply-To-Redirect-Ref", "Approved", "Archive", "Archived-At", "Article-Names", + "Article-Updates", "Authentication-Info", "Authentication-Results", "Authorization", + "Auto-Submitted", "Autoforwarded", "Autosubmitted", "Base", "Bcc", "Body", "C-Ext", + "C-Man", "C-Opt", "C-PEP", "C-PEP-Info", "Cache-Control", + "CalDAV-Timezones", "Cc", "Close", "Comments", /*"Connection",*/ "Content-Alternative", + "Content-Base", "Content-Description", "Content-Disposition", "Content-Duration", + "Content-Encoding", "Content-features", "Content-ID", "Content-Identifier", + "Content-Language", /*"Content-Length",*/ "Content-Location", "Content-MD5", + "Content-Range", "Content-Return", "Content-Script-Type", "Content-Style-Type", + "Content-Transfer-Encoding", "Content-Type", "Content-Version", "Control", "Conversion", + "Conversion-With-Loss", "Cookie", "Cookie2", "DASL", "DAV", "DL-Expansion-History", "Date", + "Date-Received", "Default-Style", "Deferred-Delivery", "Delivery-Date", "Delta-Base", + "Depth", "Derived-From", "Destination", "Differential-ID", "Digest", + "Discarded-X400-IPMS-Extensions", "Discarded-X400-MTS-Extensions", "Disclose-Recipients", + "Disposition-Notification-Options", "Disposition-Notification-To", "Distribution", + "DKIM-Signature", "Downgraded-Bcc", "Downgraded-Cc", "Downgraded-Disposition-Notification-To", + "Downgraded-Final-Recipient", "Downgraded-From", "Downgraded-In-Reply-To", + "Downgraded-Mail-From", "Downgraded-Message-Id", "Downgraded-Original-Recipient", + "Downgraded-Rcpt-To", "Downgraded-References", "Downgraded-Reply-To", "Downgraded-Resent-Bcc", + "Downgraded-Resent-Cc", "Downgraded-Resent-From", "Downgraded-Resent-Reply-To", + "Downgraded-Resent-Sender", "Downgraded-Resent-To", "Downgraded-Return-Path", + "Downgraded-Sender", "Downgraded-To", "Encoding", "Encrypted", "ETag", "Expect", + "Expires", "Expiry-Date", "Ext", "Followup-To", "Forwarded", "From", + "Generate-Delivery-Report", "GetProfile", "Hobareg", "Host", "HTTP2-Settings", "IM", "If", + "If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Schedule-Tag-Match", + "If-Unmodified-Since", "Importance", "In-Reply-To", "Incomplete-Copy", "Injection-Date", + "Injection-Info", "Keep-Alive", "Keywords", "Label", "Language", "Last-Modified", + "Latest-Delivery-Time", "Lines", "Link", "List-Archive", "List-Help", "List-ID", + "List-Owner", "List-Post", "List-Subscribe", "List-Unsubscribe", "Location", "Lock-Token", + "Man", "Max-Forwards", "Memento-Datetime", "Message-Context", "Message-ID", "Message-Type", + "Meter", "MIME-Version", "MMHS-Exempted-Address", "MMHS-Extended-Authorisation-Info", + "MMHS-Subject-Indicator-Codes", "MMHS-Handling-Instructions", "MMHS-Message-Instructions", + "MMHS-Codress-Message-Indicator", "MMHS-Originator-Reference", "MMHS-Primary-Precedence", + "MMHS-Copy-Precedence", "MMHS-Message-Type", "MMHS-Other-Recipients-Indicator-To", + "MMHS-Other-Recipients-Indicator-CC", "MMHS-Acp127-Message-Identifier", "MMHS-Originator-PLAD", + "MT-Priority", "Negotiate", "Newsgroups", "NNTP-Posting-Date", "NNTP-Posting-Host", + "Obsoletes", "Opt", "Ordering-Type", "Organization", "Origin", + "Original-Encoded-Information-Types", "Original-From", "Original-Message-ID", + "Original-Recipient", "Original-Sender", "Originator-Return-Address", "Original-Subject", + "Overwrite", "P3P", "Path", "PEP", "PICS-Label", "Pep-Info", "Position", "Posting-Version", + "Pragma", "Prefer", "Preference-Applied", "Prevent-NonDelivery-Report", "Priority", + "ProfileObject", "Protocol", "Protocol-Info", "Protocol-Query", "Protocol-Request", + "Proxy-Authenticate", "Proxy-Authentication-Info", "Proxy-Authorization", "Proxy-Features", + "Proxy-Instruction", "Public", "Public-Key-Pins", "Public-Key-Pins-Report-Only", "Range", + "Received", "Received-SPF", "Redirect-Ref", "References", "Referer", "Relay-Version", + "Reply-By", "Reply-To", "Require-Recipient-Valid-Since", "Resent-Bcc", "Resent-Cc", + "Resent-Date", "Resent-From", "Resent-Message-ID", "Resent-Reply-To", "Resent-Sender", + "Resent-To", "Retry-After", "Return-Path", "Safe", "Schedule-Reply", "Schedule-Tag", + "Sec-WebSocket-Accept", "Sec-WebSocket-Extensions", "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", "Sec-WebSocket-Version", "Security-Scheme", "See-Also", "Sender", + "Sensitivity", "Server", "Set-Cookie", "Set-Cookie2", + "SetProfile", "SLUG", "SoapAction", "Solicitation", "Status-URI", "Strict-Transport-Security", + "Subject", "Summary", "Supersedes", "Surrogate-Capability", "Surrogate-Control", "TCN", + "TE", "Timeout", "To", "Trailer", /*"Transfer-Encoding",*/ "URI", /*"Upgrade",*/ "User-Agent", + "Variant-Vary", "Vary", "VBR-Info", "Via", "WWW-Authenticate", "Want-Digest", "Warning", + "X400-Content-Identifier", "X400-Content-Return", "X400-Content-Type", "X400-MTS-Identifier", + "X400-Originator", "X400-Received", "X400-Recipients", "X400-Trace", "X-Frame-Options", "Xref" + }; + return list[rand(289)]; + } + + std::string + text() + { + std::string s; + while(rand(3)) + { + for(;;) + { + char c = rand(256); + if(detail::is_text(c)) + { + s.append(1, c); + break; + } + } + } + return s; + } + + std::string + value() + { + std::string s; + while(rand(3)) + { + if(rand(5)) + { + s.append(text()); + } + else + { + // LWS + if(! rand(4)) + s.append("\r\n"); + s.append(1, rand(2) ? ' ' : '\t'); + while(rand(2)) + s.append(1, rand(2) ? ' ' : '\t'); + } + } + return s; + } + + template + void + headers(Streambuf& sb) + { + while(rand(6)) + { + write(sb, field()); + write(sb, rand(4) ? ": " : ":"); + write(sb, value()); + write(sb, "\r\n"); + } + } + + template + void + body(Streambuf& sb) + { + if(! rand(4)) + { + write(sb, "Content-Length: 0\r\n\r\n"); + return; + } + if(rand(2)) + { + auto const len = rand(500); + write(sb, "Content-Length: ", len, "\r\n\r\n"); + for(auto const& b : sb.prepare(len)) + { + auto p = boost::asio::buffer_cast(b); + auto n = boost::asio::buffer_size(b); + while(n--) + *p++ = static_cast(32 + rand(26+26+10+6)); + } + sb.commit(len); + } + else + { + auto len = rand(500); + write(sb, "Transfer-Encoding: chunked\r\n\r\n"); + while(len > 0) + { + auto n = std::min(1 + rand(300), len); + len -= n; + write(sb, to_hex(n), "\r\n"); + for(auto const& b : sb.prepare(n)) + { + auto p = boost::asio::buffer_cast(b); + auto m = boost::asio::buffer_size(b); + while(m--) + *p++ = static_cast(32 + rand(26+26+10+6)); + } + sb.commit(n); + write(sb, "\r\n"); + } + write(sb, "0\r\n\r\n"); + } + } + + template + void + request(Streambuf& sb) + { + write(sb, method(), " ", uri(), " HTTP/1.1\r\n"); + headers(sb); + body(sb); + } + + template + void + response(Streambuf& sb) + { + write(sb, "HTTP/1."); + write(sb, rand(2) ? "0" : "1"); + write(sb, " ", 100 + rand(401), " "); + write(sb, token()); + write(sb, "\r\n"); + headers(sb); + body(sb); + write(sb, "\r\n"); + } +}; + +using message_fuzz = message_fuzz_t<>; + +} // http +} // beast + +#endif diff --git a/src/http-parser/AUTHORS b/test/http/nodejs-parser/AUTHORS similarity index 100% rename from src/http-parser/AUTHORS rename to test/http/nodejs-parser/AUTHORS diff --git a/src/http-parser/LICENSE-MIT b/test/http/nodejs-parser/LICENSE-MIT similarity index 100% rename from src/http-parser/LICENSE-MIT rename to test/http/nodejs-parser/LICENSE-MIT diff --git a/src/http-parser/README.md b/test/http/nodejs-parser/README.md similarity index 100% rename from src/http-parser/README.md rename to test/http/nodejs-parser/README.md diff --git a/src/http-parser/http_parser.c b/test/http/nodejs-parser/http_parser.c similarity index 99% rename from src/http-parser/http_parser.c rename to test/http/nodejs-parser/http_parser.c index 278d4b20..71961754 100644 --- a/src/http-parser/http_parser.c +++ b/test/http/nodejs-parser/http_parser.c @@ -21,7 +21,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ -#include "../../include/beast/http/impl/http_parser.h" +#include "http_parser.h" #include #include #include diff --git a/include/beast/http/impl/http_parser.h b/test/http/nodejs-parser/http_parser.h similarity index 100% rename from include/beast/http/impl/http_parser.h rename to test/http/nodejs-parser/http_parser.h diff --git a/src/beast_http_nodejs_parser.cpp b/test/http/nodejs_parser.cpp similarity index 92% rename from src/beast_http_nodejs_parser.cpp rename to test/http/nodejs_parser.cpp index cb1dccf9..a216ec0d 100644 --- a/src/beast_http_nodejs_parser.cpp +++ b/test/http/nodejs_parser.cpp @@ -10,7 +10,7 @@ # pragma warning (disable: 4127) // conditional expression is constant # pragma warning (disable: 4244) // integer conversion, possible loss of data #endif -#include "http-parser/http_parser.c" +#include "nodejs-parser/http_parser.c" #ifdef _MSC_VER # pragma warning (pop) #endif diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp new file mode 100644 index 00000000..44f13f09 --- /dev/null +++ b/test/http/nodejs_parser.hpp @@ -0,0 +1,881 @@ +// +// Copyright (c) 2013-2016 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_NODEJS_PARSER_HPP +#define BEAST_HTTP_NODEJS_PARSER_HPP + +#include "nodejs-parser/http_parser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +class nodejs_message_category + : 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 +beast::http::method_t +convert_http_method(http_method m) +{ + using namespace beast; + switch (m) + { + case HTTP_DELETE: return http::method_t::http_delete; + case HTTP_GET: return http::method_t::http_get; + case HTTP_HEAD: return http::method_t::http_head; + case HTTP_POST: return http::method_t::http_post; + case HTTP_PUT: return http::method_t::http_put; + + // pathological + case HTTP_CONNECT: return http::method_t::http_connect; + case HTTP_OPTIONS: return http::method_t::http_options; + case HTTP_TRACE: return http::method_t::http_trace; + + // webdav + case HTTP_COPY: return http::method_t::http_copy; + case HTTP_LOCK: return http::method_t::http_lock; + case HTTP_MKCOL: return http::method_t::http_mkcol; + case HTTP_MOVE: return http::method_t::http_move; + case HTTP_PROPFIND: return http::method_t::http_propfind; + case HTTP_PROPPATCH: return http::method_t::http_proppatch; + case HTTP_SEARCH: return http::method_t::http_search; + case HTTP_UNLOCK: return http::method_t::http_unlock; + case HTTP_BIND: return http::method_t::http_bind; + case HTTP_REBIND: return http::method_t::http_rebind; + case HTTP_UNBIND: return http::method_t::http_unbind; + case HTTP_ACL: return http::method_t::http_acl; + + // subversion + case HTTP_REPORT: return http::method_t::http_report; + case HTTP_MKACTIVITY: return http::method_t::http_mkactivity; + case HTTP_CHECKOUT: return http::method_t::http_checkout; + case HTTP_MERGE: return http::method_t::http_merge; + + // upnp + case HTTP_MSEARCH: return http::method_t::http_msearch; + case HTTP_NOTIFY: return http::method_t::http_notify; + case HTTP_SUBSCRIBE: return http::method_t::http_subscribe; + case HTTP_UNSUBSCRIBE: return http::method_t::http_unsubscribe; + + // RFC-5789 + case HTTP_PATCH: return http::method_t::http_patch; + case HTTP_PURGE: return http::method_t::http_purge; + + // CalDav + case HTTP_MKCALENDAR: return http::method_t::http_mkcalendar; + + // RFC-2068, section 19.6.1.2 + case HTTP_LINK: return http::method_t::http_link; + case HTTP_UNLINK: return http::method_t::http_unlink; + }; + + return http::method_t::http_get; +} + +} // 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) + throw boost::system::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) + throw boost::system::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) + throw boost::system::system_error{ec}; + } + + void + write_eof(error_code& ec); + +private: + Derived& + impl() + { + return *static_cast(this); + } + + template + class has_on_start_t + { + template().on_start(), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_start = + std::integral_constant::value>; + + void + call_on_start(std::true_type) + { + impl().on_start(); + } + + void + call_on_start(std::false_type) + { + } + + template + class has_on_field_t + { + template().on_field( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_field = + std::integral_constant::value>; + + void + call_on_field(std::string const& field, + std::string const& value, std::true_type) + { + impl().on_field(field, value); + } + + void + call_on_field(std::string const&, std::string const&, + std::false_type) + { + } + + template + class has_on_headers_complete_t + { + template().on_headers_complete( + std::declval()), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_headers_complete = + std::integral_constant::value>; + + void + call_on_headers_complete(error_code& ec, std::true_type) + { + impl().on_headers_complete(ec); + } + + void + call_on_headers_complete(error_code&, std::false_type) + { + } + + template + class has_on_request_t + { + template().on_request( + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval(), std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_request = + std::integral_constant::value>; + + void + call_on_request(method_t method, std::string url, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + impl().on_request( + method, url, major, minor, keep_alive, upgrade); + } + + void + call_on_request(method_t, std::string, int, int, bool, bool, + std::false_type) + { + } + + template + class has_on_response_t + { + template().on_response( + std::declval(), std::declval, + std::declval(), std::declval(), + std::declval(), std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); +#if 0 + using type = decltype(check(0)); +#else + // VFALCO Trait seems broken for http::parser + using type = std::true_type; +#endif + public: + static bool const value = type::value; + }; + template + using has_on_response = + std::integral_constant::value>; + + bool + call_on_response(int status, std::string text, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + return impl().on_response( + status, text, major, minor, keep_alive, upgrade); + } + + bool + call_on_response(int, std::string, int, int, bool, bool, + std::false_type) + { + // VFALCO Certainly incorrect + return true; + } + + template + class has_on_body_t + { + template().on_body( + std::declval(), std::declval(), + std::declval()), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_body = + std::integral_constant::value>; + + void + call_on_body(void const* data, std::size_t bytes, + error_code& ec, std::true_type) + { + impl().on_body(data, bytes, ec); + } + + void + call_on_body(void const*, std::size_t, + error_code&, std::false_type) + { + } + + template + class has_on_complete_t + { + template().on_complete(), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_complete = + std::integral_constant::value>; + + void + call_on_complete(std::true_type) + { + impl().on_complete(); + } + + void + call_on_complete(std::false_type) + { + } + + void + check_header(); + + 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(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::size_t bytes_used = 0; + for (auto const& buffer : buffers) + { + auto const n = write( + buffer_cast(buffer), + buffer_size(buffer), 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()) + { + rfc2616::trim_right_in_place(value_); + call_on_field(field_, value_, + has_on_field{}); + 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.call_on_start(has_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.call_on_headers_complete(*t.ec_, + has_on_headers_complete{}); + if(*t.ec_) + return 1; + bool const keep_alive = + http_should_keep_alive(p) != 0; + if(p->type == http_parser_type::HTTP_REQUEST) + { + t.call_on_request(detail::convert_http_method( + http_method(p->method)), t.url_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_request{}); + return 0; + } + return t.call_on_response(p->status_code, t.status_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_response{}) ? 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.call_on_body(in, bytes, *t.ec_, has_on_body{}); + 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.call_on_complete(has_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; +} + +} // http +} // beast + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A HTTP parser. + + The parser may only be used once. +*/ +template +class nodejs_parser + : public nodejs_basic_parser> +{ + using message_type = + message; + + message_type m_; + typename message_type::body_type::reader r_; + bool started_ = false; + +public: + nodejs_parser(nodejs_parser&&) = default; + + nodejs_parser() + : http::nodejs_basic_parser(isRequest) + , r_(m_) + { + } + + /// Returns `true` if at least one byte has been processed + bool + started() + { + return started_; + } + + message_type + release() + { + return std::move(m_); + } + +private: + friend class http::nodejs_basic_parser; + + void + on_start() + { + started_ = true; + } + + void + on_field(std::string const& field, std::string const& value) + { + m_.headers.insert(field, value); + } + + void + on_headers_complete(error_code&) + { + // vFALCO TODO Decode the Content-Length and + // Transfer-Encoding, see if we can reserve the buffer. + // + // r_.reserve(content_length) + } + + bool + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + m_.method = method; + m_.url = url; + m_.version = major * 10 + minor; + return true; + } + + bool + on_request(http::method_t, std::string const&, + int, int, bool, bool, + std::false_type) + { + return true; + } + + bool + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade) + { + return on_request(method, url, + major, minor, keep_alive, upgrade, + typename message_type::is_request{}); + } + + bool + on_response(int status, std::string const& reason, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + m_.status = status; + m_.reason = reason; + m_.version = major * 10 + minor; + // VFALCO TODO return expect_body_ + 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* data, + std::size_t size, error_code& ec) + { + r_.write(data, size, ec); + } + + void + on_complete() + { + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp new file mode 100644 index 00000000..5cc9b0f9 --- /dev/null +++ b/test/http/parse_error.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 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 diff --git a/test/http/parser_bench.cpp b/test/http/parser_bench.cpp new file mode 100644 index 00000000..32cea58f --- /dev/null +++ b/test/http/parser_bench.cpp @@ -0,0 +1,157 @@ +// +// Copyright (c) 2013-2016 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) +// + +#include "nodejs_parser.hpp" +#include "message_fuzz.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class parser_bench_test : public beast::detail::unit_test::suite +{ +public: + static std::size_t constexpr N = 2000; + + using corpus = std::vector; + + corpus creq_; + corpus cres_; + std::size_t size_ = 0; + + parser_bench_test() + { + creq_ = build_corpus(N/2, std::true_type{}); + cres_ = build_corpus(N/2, std::false_type{}); + } + + corpus + build_corpus(std::size_t N, std::true_type) + { + corpus v; + v.resize(N); + message_fuzz mg; + for(std::size_t i = 0; i < N; ++i) + { + mg.request(v[i]); +//log << debug::buffers_to_string(v[i].data()) << "\r"; + size_ += v[i].size(); + } + return v; + } + + corpus + build_corpus(std::size_t N, std::false_type) + { + corpus v; + v.resize(N); + message_fuzz mg; + for(std::size_t i = 0; i < N; ++i) + { + mg.response(v[i]); +//log << debug::buffers_to_string(v[i].data()) << "\r"; + size_ += v[i].size(); + } + return v; + } + + template + void + testParser(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& sb : v) + { + Parser p; + error_code ec; + p.write(sb.data(), ec); + if(! expect(! ec, ec.message())) + log << debug::buffers_to_string( + sb.data()) << std::endl; + } + } + + template + void + timedTest(std::size_t repeat, std::string const& name, Function&& f) + { + using namespace std::chrono; + using clock_type = std::chrono::high_resolution_clock; + log << name; + for(std::size_t trial = 1; trial <= repeat; ++trial) + { + auto const t0 = clock_type::now(); + f(); + auto const elapsed = clock_type::now() - t0; + log << + "Trial " << trial << ": " << + duration_cast(elapsed).count() << " ms"; + } + } + + template + struct null_parser : basic_parser> + { + }; + + void + testSpeed() + { + static std::size_t constexpr Trials = 3; + static std::size_t constexpr Repeat = 50; + + log << "sizeof(request parser) == " << + sizeof(basic_parser>); + + log << "sizeof(response parser) == " << + sizeof(basic_parser>); + + testcase << "Parser speed test, " << + ((Repeat * size_ + 512) / 1024) << "KB in " << + (Repeat * (creq_.size() + cres_.size())) << " messages"; + + timedTest(Trials, "nodejs_parser", + [&] + { + testParser>( + Repeat, creq_); + testParser>( + Repeat, cres_); + }); + timedTest(Trials, "http::basic_parser", + [&] + { + testParser>( + Repeat, creq_); + testParser>( + Repeat, cres_); + }); + pass(); + } + + void run() override + { + pass(); + testSpeed(); + } +}; + +BEAST_DEFINE_TESTSUITE(parser_bench,http,beast); + +} // http +} // beast + diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp new file mode 100644 index 00000000..93a1a81a --- /dev/null +++ b/test/http/rfc7230.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2016 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