From e8be3fd7d38e14f6990184a0716dc454e674702f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 20 Nov 2016 07:32:41 -0500 Subject: [PATCH] New HTTP interfaces (API Change): fix #123 fix #154 fix #265 This completely replaces the HTTP parser used to read and parse incoming messages. The new version is faster than the old one, and written in fewer lines. A summary of changes: * parse and async_parse renamed to read and async_read * basic_parser is optimized for linear buffers, use flat_streambuf for the best performance with these functions: - http::read - http::read_some - http::async_read - http::async_read_some * The overloads of read() and async_read() which take just a header have been removed, since they would throw away important parse metadata. * The derived class callbacks for basic_parser have been streamlined. All strings passed to callbacks are presented in their entirety, instead of being provided in pieces. These changes allow use-cases that were previously difficult or impossible, such as: - Efficient relaying - Late body type commitment - Expect: 100-continue handling --- CHANGELOG.md | 4 + README.md | 2 +- doc/examples.qbk | 2 +- doc/http.qbk | 16 +- doc/master.qbk | 1 - doc/quickref.xml | 26 +- doc/types/Parser.qbk | 61 - doc/types/Reader.qbk | 179 ++- doc/websocket.qbk | 2 +- examples/http_crawl.cpp | 2 +- examples/http_example.cpp | 2 +- examples/ssl/http_ssl_example.cpp | 2 +- include/beast/core/detail/ci_char_traits.hpp | 11 +- include/beast/http.hpp | 10 +- include/beast/http/basic_dynabuf_body.hpp | 37 +- include/beast/http/basic_parser.hpp | 643 ++++++++ include/beast/http/basic_parser_v1.hpp | 856 ----------- include/beast/http/concepts.hpp | 77 +- .../beast/http/detail/basic_parsed_list.hpp | 194 +++ include/beast/http/detail/basic_parser.hpp | 446 ++++++ include/beast/http/detail/basic_parser_v1.hpp | 146 -- include/beast/http/detail/rfc7230.hpp | 114 +- include/beast/http/error.hpp | 83 ++ include/beast/http/header_parser.hpp | 190 +++ include/beast/http/header_parser_v1.hpp | 233 --- include/beast/http/impl/async_read.ipp | 716 +++++++++ include/beast/http/impl/basic_parser.ipp | 1062 ++++++++++++++ include/beast/http/impl/basic_parser_v1.ipp | 1289 ----------------- include/beast/http/impl/error.ipp | 106 ++ include/beast/http/impl/header_parser.ipp | 25 + include/beast/http/impl/message_parser.ipp | 38 + include/beast/http/impl/parse.ipp | 302 ---- include/beast/http/impl/parse_error.ipp | 105 -- include/beast/http/impl/read.ipp | 525 +++---- include/beast/http/impl/rfc7230.ipp | 20 + include/beast/http/message_parser.hpp | 286 ++++ include/beast/http/parse.hpp | 155 -- include/beast/http/parse_error.hpp | 48 - include/beast/http/parser_v1.hpp | 339 ----- include/beast/http/read.hpp | 366 +++-- include/beast/http/rfc7230.hpp | 41 + include/beast/http/string_body.hpp | 47 +- include/beast/websocket/detail/decorator.hpp | 5 +- .../beast/websocket/detail/stream_base.hpp | 1 - include/beast/websocket/impl/accept.ipp | 88 +- include/beast/websocket/impl/handshake.ipp | 12 +- include/beast/websocket/impl/stream.ipp | 20 +- include/beast/websocket/stream.hpp | 25 +- include/beast/zlib/deflate_stream.hpp | 2 +- include/beast/zlib/inflate_stream.hpp | 2 +- test/Jamfile | 11 +- test/http/CMakeLists.txt | 13 +- test/http/basic_parser.cpp | 960 ++++++++++++ test/http/basic_parser_v1.cpp | 1175 --------------- test/http/design.cpp | 534 +++++++ test/http/empty_body.cpp | 9 - test/http/error.cpp | 59 + test/http/fail_parser.hpp | 120 -- test/http/header_parser.cpp | 66 + test/http/header_parser_v1.cpp | 90 -- test/http/message.cpp | 6 +- test/http/message_fuzz.hpp | 1 - test/http/message_parser.cpp | 244 ++++ test/http/nodejs_parser.hpp | 25 +- test/http/parse.cpp | 9 - test/http/parse_error.cpp | 60 - test/http/parser_bench.cpp | 139 +- test/http/parser_v1.cpp | 160 -- test/http/read.cpp | 156 +- test/http/rfc7230.cpp | 114 +- test/http/streambuf_body.cpp | 11 +- test/http/test_parser.hpp | 147 ++ test/http/write.cpp | 3 +- test/websocket/stream.cpp | 2 +- 74 files changed, 7106 insertions(+), 5972 deletions(-) delete mode 100644 doc/types/Parser.qbk create mode 100644 include/beast/http/basic_parser.hpp delete mode 100644 include/beast/http/basic_parser_v1.hpp create mode 100644 include/beast/http/detail/basic_parsed_list.hpp create mode 100644 include/beast/http/detail/basic_parser.hpp delete mode 100644 include/beast/http/detail/basic_parser_v1.hpp create mode 100644 include/beast/http/error.hpp create mode 100644 include/beast/http/header_parser.hpp delete mode 100644 include/beast/http/header_parser_v1.hpp create mode 100644 include/beast/http/impl/async_read.ipp create mode 100644 include/beast/http/impl/basic_parser.ipp delete mode 100644 include/beast/http/impl/basic_parser_v1.ipp create mode 100644 include/beast/http/impl/error.ipp create mode 100644 include/beast/http/impl/header_parser.ipp create mode 100644 include/beast/http/impl/message_parser.ipp delete mode 100644 include/beast/http/impl/parse.ipp delete mode 100644 include/beast/http/impl/parse_error.ipp create mode 100644 include/beast/http/message_parser.hpp delete mode 100644 include/beast/http/parse.hpp delete mode 100644 include/beast/http/parse_error.hpp delete mode 100644 include/beast/http/parser_v1.hpp create mode 100644 test/http/basic_parser.cpp delete mode 100644 test/http/basic_parser_v1.cpp create mode 100644 test/http/design.cpp delete mode 100644 test/http/empty_body.cpp create mode 100644 test/http/error.cpp delete mode 100644 test/http/fail_parser.hpp create mode 100644 test/http/header_parser.cpp delete mode 100644 test/http/header_parser_v1.cpp create mode 100644 test/http/message_parser.cpp delete mode 100644 test/http/parse.cpp delete mode 100644 test/http/parse_error.cpp delete mode 100644 test/http/parser_v1.cpp create mode 100644 test/http/test_parser.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 8975da9a..c03c407b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * Add flat_streambuf * Rename to BEAST_DOXYGEN +API Changes: + +* New HTTP interfaces + -------------------------------------------------------------------------------- 1.0.0-b34 diff --git a/README.md b/README.md index 5f5766b9..a7afea68 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request req; + beast::http::request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/doc/examples.qbk b/doc/examples.qbk index 224f4d9f..8cbb2f7d 100644 --- a/doc/examples.qbk +++ b/doc/examples.qbk @@ -34,7 +34,7 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request req; + beast::http::request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/doc/http.qbk b/doc/http.qbk index 9249fd64..d08a3046 100644 --- a/doc/http.qbk +++ b/doc/http.qbk @@ -26,7 +26,6 @@ contents: Algorithms Write Read - Parse Examples Send Request Receive Response @@ -136,7 +135,7 @@ object: [[HTTP Request] [HTTP Response]] [[ ``` - request req; + request req; req.version = 11; // HTTP/1.1 req.method = "GET"; req.url = "/index.htm" @@ -227,15 +226,6 @@ The message [*`Body`] template parameter controls both the type of the data member of the resulting message object, and the algorithms used during parsing and serialization. Beast provides three very common [*`Body`] types: -* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body. -Used in GET requests where there is no message body. Example: -``` - request req; - req.version = 11; - req.method = "GET"; - req.url = "/index.html"; -``` - * [link beast.ref.http__string_body [*`string_body`:]] A body with a `value_type` as `std::string`. Useful for quickly putting together a request or response with simple text in the message body (such as an error message). @@ -303,7 +293,7 @@ operations performed). To send messages synchronously, use one of the ``` void send_request(boost::asio::ip::tcp::socket& sock) { - request req; + request req; req.version = 11; req.method = "GET"; req.url = "/index.html"; @@ -322,7 +312,7 @@ An asynchronous interface is available: ``` void handle_write(boost::system::error_code); ... - request req; + request req; ... async_write(sock, req, std::bind(&handle_write, std::placeholders::_1)); ``` diff --git a/doc/master.qbk b/doc/master.qbk index 24494c38..90b697c3 100644 --- a/doc/master.qbk +++ b/doc/master.qbk @@ -107,7 +107,6 @@ provides implementations of the HTTP and WebSocket protocols. [include types/DynamicBuffer.qbk] [include types/Field.qbk] [include types/FieldSequence.qbk] -[include types/Parser.qbk] [include types/Reader.qbk] [include types/Streams.qbk] [include types/Writer.qbk] diff --git a/doc/quickref.xml b/doc/quickref.xml index cabad789..94a1a3c3 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -31,13 +31,12 @@ basic_dynabuf_body basic_fields - basic_parser_v1 - empty_body + basic_parser fields header - header_parser_v1 + header_parser message - parser_v1 + message_parser request request_header response @@ -49,6 +48,7 @@ ext_list + opt_token_list param_list token_list @@ -57,7 +57,7 @@ Functions async_read - async_parse + async_read_some async_write chunk_encode chunk_encode_final @@ -65,17 +65,15 @@ is_keep_alive is_upgrade operator<< - parse prepare read + read_some reason_string - with_body write Type Traits is_Body - is_Parser is_Reader is_Writer has_reader @@ -83,26 +81,16 @@ - Options - - header_max_size - body_max_size - skip_body - Constants - body_what connection - no_content_length - parse_error - parse_flag + error Concepts Body Field FieldSequence - Parser Reader Writer diff --git a/doc/types/Parser.qbk b/doc/types/Parser.qbk deleted file mode 100644 index 12cd7b74..00000000 --- a/doc/types/Parser.qbk +++ /dev/null @@ -1,61 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:Parser Parser requirements] - -A [*Parser] is used to deserialize objects from -[link beast.ref.streams streams]. Objects of this type are used with -[link beast.ref.http__parse http::parse] and -[link beast.ref.http__async_parse http::async_parse]. The definition of -an object, and the predicate defining when the parse is complete, are -determined by the implementation. - -In this table: - -* `X` denotes a type meeting the requirements of [*Parser]. - -* `a` denotes a value of type `X`. - -* `b` is a value meeting the requirements of __ConstBufferSequence__. - -* `ec` is a value of type [link beast.ref.error_code `error_code&`]. - -[table Parser requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`a.complete()`] - [`bool`] - [ - Returns `true` when parsing is complete. - ] -] -[ - [`a.write(b, ec)`] - [`std::size_t`] - [ - Sequentially parses the octets in the specified input buffer sequence - until an error occurs, the end of the buffer is reached, or parsing is - complete. Upon success, this function returns the number of bytes used - from the input. If an error occurs, `ec` is set to the error code and - parsing stops. - ] -] -[ - [`a.write_eof(ec)`] - [`void`] - [ - Indicates to the parser that no more octets will be available. - Typically this function is called when the end of stream is reached. - For example, if a call to `boost::asio::ip::tcp::socket::read_some` - generates a `boost::asio::error::eof` error. Some objects, such as - certain HTTP/1 messages, determine the end of the message body by - an end of file marker or closing of the connection. - ] -] -] - -[endsect] diff --git a/doc/types/Reader.qbk b/doc/types/Reader.qbk index 0fd852f1..b8b22db9 100644 --- a/doc/types/Reader.qbk +++ b/doc/types/Reader.qbk @@ -10,57 +10,198 @@ Parsers provided by the implementation will construct the corresponding `reader` object during parsing. This customization point allows the Body to determine the strategy for storing incoming message body data. +Readers come in two flavors, direct and indirect: -In this table: +Direct readers provide a buffer to callers, in which body data is placed. +This type of reader is used when the bytes corresponding to the body data +are stored without transformation. The parse algorithm performs stream or +socket reads directly into the reader-provided buffer, hence the name +"direct." This model avoids an unnecessary buffer copy. An example of +a [*Body] type with a direct reader is +[link beast.ref.http__string_body `string_body`]. + +Indirect readers are passed body data in a buffer managed by the parser +algorithm. This reader is appropriate when the body data is transformed +or not otherwised stored verbatim. Some examples of when an indirect +reader is appropriate: + +* When bytes corresponding to the body are written to a file + as they are parsed. + +* The content of the message is JSON, which is parsed as it is + being read in, and stored in a structured, hierarchical format. + +In the tables below: * `X` denotes a type meeting the requirements of [*`Reader`]. * `a` denotes a value of type `X`. -* `n` is a value convertible to `std::size_t`. +* `n` is a value convertible to `std::size_t` without loss of precision. -* `p` is a `void const*` to valid memory of at least `n` bytes. +* `v` is a value convertible to `std::uint64_t` without loss of precision. + +* `s` is a value of type `boost::string_ref`. * `ec` is a value of type [link beast.ref.error_code `error_code&`]. * `m` denotes a value of type `message&` where `std::is_same::value == true`. -[table Reader requirements +[table Direct Reader requirements [[operation] [type] [semantics, pre/post-conditions]] [ - [`X a(m);`] + [`X::is_direct`] + [`bool`] + [ + This static constant must be set to `true` to indicate that + the reader is a direct reader. + ] +] +[ + [`X::mutable_buffers_type`] + [] + [ + This member type must be present, and meet the requirements + of [*MutableBufferSequence]. It represents the type of + the writable buffers returned by the reader, in which + bytes representing the body are stored by the implementation. + ] +] +[ + [`X a{m};`] [] [ `a` is constructible from `m`. The lifetime of `m` is guaranteed to end no earlier than after `a` is destroyed. The constructor will be called after all headers have been stored in `m`, and - before any body data is deserialized. This function must be - `noexcept`. + just before parsing bytes corresponding to the body for messages + whose semantics indicate that a body is present with non-zero + length. ] ] [ - [`a.init(ec)`] - [`void`] + [`a.init()`] + [] [ - Called immediately after construction. If the function sets - an error code in `ec`, the parse is aborted and the error is - propagated to the caller. This function must be `noexcept`. + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages whose + body is determined by the end-of-file marker on a stream, + or for messages where the chunked Transfer-Encoding is + specified. ] ] [ - [`a.write(p, n, ec)`] - [`void`] + [`a.init(v)`] + [] [ - Deserializes the input sequence into the body. If `ec` is set, - the deserialization is aborted and the error is propagated to - the caller. If the message headers specify a chunked transfer - encoding, the reader will receive the decoded version of the - body. This function must be `noexcept`. + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages where + the Content-Length is specified. The value of `v` will be + set to the number of bytes indicated by the content length. + ] +] +[ + [`a.prepare(n)`] + [`mutable_buffers_type`] + [ + The implementation calls this function to obtain a mutable + buffer sequence of up to `n` bytes in size in which to place + data corresponding to the body. The buffer returned must + be at least one byte in size, and may be smaller than `n`. + ] +] +[ + [`a.commit(n)`] + [] + [ + The implementation calls this function to indicate to the + reader that `n` bytes of data have been successfully placed + into the buffer obtained through a prior call to `prepare`. + The value of `n` will be less than or equal to the size of + the buffer returned in the previous call to `prepare`. + ] +] +[ + [`a.finish()`] + [] + [ + This function is called after all the bytes corresponding + to the body have been written to the buffers and committed. ] ] ] +[table Indirect Reader requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::is_direct`] + [`bool`] + [ + This static constant must be set to `false` to indicate that + the reader is an indirect reader. + ] +] +[ + [`X a{m};`] + [] + [ + `a` is constructible from `m`. The lifetime of `m` is guaranteed + to end no earlier than after `a` is destroyed. The constructor + will be called after all headers have been stored in `m`, and + just before parsing bytes corresponding to the body for messages + whose semantics indicate that a body is present with non-zero + length. + ] +] +[ + [`a.init(ec)`] + [] + [ + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages whose + body is determined by the end-of-file market on a stream, + or for messages where the chunked Transfer-Encoding is + specified. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + + ] +] +[ + [`a.init(v,ec)`] + [] + [ + This function is called once before any bytes corresponding + to the body are presented to the reader, for messages where + the Content-Length is specified. The value of `v` will be + set to the number of bytes indicated by the content length. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + ] +] +[ + [`a.write(s,ec)`] + [] + [ + The implementation calls this function with `s` containing + bytes corresponding to the body, after removing any present + chunked encoding transformation. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + ] +] +[ + [`a.finish(ec)`] + [] + [ + This function is called after all the bytes corresponding + to the body have been written to the buffers and committed. + If `ec` is set before returning, parsing will stop + and the error will be returned to the caller. + ] +] +] [note Definitions for required `Reader` member functions should be declared inline so the generated code can become part of the implementation. diff --git a/doc/websocket.qbk b/doc/websocket.qbk index 77bcc2bd..494b27a3 100644 --- a/doc/websocket.qbk +++ b/doc/websocket.qbk @@ -50,7 +50,7 @@ The WebSocket protocol is described fully in The interface to Beast's WebSocket implementation is a single template class [link beast.ref.websocket__stream `beast::websocket::stream`] which wraps a "next layer" object. The next layer object must meet the requirements -of [link beast.ref.streams.SyncStream [*`SyncReadStream`]] if synchronous +of [link beast.ref.streams.SyncStream [*`SyncStream`]] if synchronous operations are performed, or [link beast.ref.streams.AsyncStream [*`AsyncStream`]] if asynchronous operations are performed, or both. Arguments supplied during construction are diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp index 1a794169..a34f067d 100644 --- a/examples/http_crawl.cpp +++ b/examples/http_crawl.cpp @@ -36,7 +36,7 @@ int main(int, char const*[]) ip::tcp::socket sock(ios); connect(sock, it); auto ep = sock.remote_endpoint(); - request req; + request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/examples/http_example.cpp b/examples/http_example.cpp index 038ac2e2..6c060817 100644 --- a/examples/http_example.cpp +++ b/examples/http_example.cpp @@ -22,7 +22,7 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request req; + beast::http::request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/examples/ssl/http_ssl_example.cpp b/examples/ssl/http_ssl_example.cpp index 30c81940..3b9992b4 100644 --- a/examples/ssl/http_ssl_example.cpp +++ b/examples/ssl/http_ssl_example.cpp @@ -34,7 +34,7 @@ int main() stream.handshake(ssl::stream_base::client); // Send HTTP request over SSL using Beast - beast::http::request req; + beast::http::request req; req.method = "GET"; req.url = "/"; req.version = 11; diff --git a/include/beast/core/detail/ci_char_traits.hpp b/include/beast/core/detail/ci_char_traits.hpp index 7dfd0c18..07fecc81 100644 --- a/include/beast/core/detail/ci_char_traits.hpp +++ b/include/beast/core/detail/ci_char_traits.hpp @@ -10,17 +10,15 @@ #include #include -#include -#include namespace beast { namespace detail { inline char -tolower(char c) +tolower(signed char c) { - static std::array constexpr tab = {{ + static unsigned char constexpr tab[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, @@ -37,8 +35,9 @@ tolower(char c) 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 - }}; - return static_cast(tab[static_cast(c)]); + }; + return static_cast( + tab[static_cast(c)]); } template diff --git a/include/beast/http.hpp b/include/beast/http.hpp index b4c7734f..8a8c3ff3 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -11,14 +11,14 @@ #include #include -#include +#include #include -#include +#include #include +#include #include -#include -#include -#include +#include +#include #include #include #include diff --git a/include/beast/http/basic_dynabuf_body.hpp b/include/beast/http/basic_dynabuf_body.hpp index 981905e8..8ad5f691 100644 --- a/include/beast/http/basic_dynabuf_body.hpp +++ b/include/beast/http/basic_dynabuf_body.hpp @@ -33,30 +33,47 @@ private: class reader { - value_type& sb_; + value_type& body_; public: + static bool constexpr is_direct = true; + + using mutable_buffers_type = + typename DynamicBuffer::mutable_buffers_type; + template explicit reader(message& m) noexcept - : sb_(m.body) + basic_dynabuf_body, Fields>& msg) + : body_(msg.body) { } void - init(error_code&) noexcept + init() { } void - write(void const* data, - std::size_t size, error_code&) noexcept + init(std::uint64_t content_length) + { + } + + mutable_buffers_type + prepare(std::size_t n) + { + return body_.prepare(n); + } + + void + commit(std::size_t n) + { + body_.commit(n); + } + + void + finish() { - using boost::asio::buffer; - using boost::asio::buffer_copy; - sb_.commit(buffer_copy( - sb_.prepare(size), buffer(data, size))); } }; diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp new file mode 100644 index 00000000..755cccc1 --- /dev/null +++ b/include/beast/http/basic_parser.hpp @@ -0,0 +1,643 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_BASIC_PARSER_HPP +#define BEAST_HTTP_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** Describes the parser's current state. + + The state is expressed as the type of data that + @ref basic_parser is expecting to see in subsequently + provided octets. +*/ +enum class parse_state +{ + /// Expecting one or more header octets + header = 0, + + /// Expecting one or more body octets + body = 1, + + /// Expecting zero or more body octets followed by EOF + body_to_eof = 2, + + /// Expecting additional chunk header octets + chunk_header = 3, + + /// Expecting one or more chunk body octets + chunk_body = 4, + + /** The parsing is complete. + + The parse is considered complete when the full header + is received and either the full body is received, or + the semantics of the message indicate that no body + is expected. This includes the case where the caller + has indicated to the parser that no body is expected, + for example when receiving a response to a HEAD request. + */ + complete = 5 +}; + +/** A parser for decoding HTTP/1 wire format messages. + + This parser is designed to efficiently parse messages in the + HTTP/1 wire format. It allocates no memory when input is + presented as a single contiguous buffer, and uses minimal + state. It will handle chunked encoding and it understands + the semantics of the Connection, Content-Length, and Upgrade + fields. + + The interface uses CRTP (Curiously Recurring Template Pattern). + To use this class, derive from @ref basic_parser. When bytes + are presented, the implementation will make a series of zero + or more calls to derived class members functions (referred to + as "callbacks" from here on) matching a specific signature. + + Every callback must be provided by the derived class, or else + a compilation error will be generated. This exemplar shows + the signature and description of the callbacks required in + the derived class. + + @par Derived Example + + @code + template + struct derived + : basic_parser> + { + // The type used when providing a mutable + // buffer sequence in which to store body data. + // + using mutable_buffers_type = ...; + + // When isRequest == true, called + // after the Request Line is received. + // + void + on_request( + boost::string_ref const& method, + boost::string_ref const& path, + int version, + error_code& ec); + + // When isRequest == false, called + // after the Status Line is received. + // + void + on_response( + int status, + boost::string_ref const& reason, + int version, + error_code& ec); + + // Called after receiving a field/value pair. + // + void + on_field( + boost::string_ref const& name, + boost::string_ref const& value, + error_code& ec); + + // Called after the header is complete. + // + void + on_header( + error_code& ec); + + // Called once before the body, if any, is started. + // This will only be called if the semantics of the + // message indicate that a body exists, including + // an indicated body of zero length. + // + void + on_body(); + + // Called zero or more times to provide body data. + // + // Only used if isDirect == false + // + void + on_data( + boost::string_ref const& s, + error_code& ec); + + // Called zero or more times to retrieve a mutable + // buffer sequence in which to store body data. + // + // Only used if isDirect == true + // + mutable_buffers_type + on_prepare( + std::size_t n); + + // Called after body data has been stored in the + // buffer returned by the previous call to on_prepare. + // + // Only used if isDirect == true + // + void + on_commit( + std::size_t n); + + // If the Transfer-Encoding is specified, and the + // last item in the list of encodings is "chunked", + // called after receiving a chunk header or a final + // chunk. + // + void + on_chunk( + std::uint64_t length, // Length of this chunk + boost::string_ref const& ext, // The chunk extensions, if any + error_code& ec); + + // Called once when the message is complete. + // This will be called even if there is no body. + // + void + on_complete(error_code& ec); + }; + @endcode + + If a callback sets the error code, the error will be propagated + to the caller of the parser. Behavior of parsing after an error + is returned is undefined. + + When the parser state is positioned to read bytes belonging to + the body, calling @ref write or @ref write will implicitly + cause a buffer copy (because bytes are first transferred to the + dynamic buffer). To avoid this copy, the additional functions + @ref copy_body, @ref prepare_body, and @ref commit_body are + provided to allow the caller to read bytes directly into buffers + supplied by the parser. + + The parser is optimized for the case where the input buffer + sequence consists of a single contiguous buffer. The + @ref beast::flat_streambuf class is provided, which guarantees + that the input sequence of the stream buffer will be represented + by exactly one contiguous buffer. To ensure the optimum performance + of the parser, use @ref beast::flat_streambuf with HTTP algorithms + such as @ref beast::http::read, @ref beast::http::read_some, + @ref beast::http::async_read, and @ref beast::http::async_read_some. + Alternatively, the caller may use custom techniques to ensure that + the structured portion of the HTTP message (header or chunk header) + is contained in a linear buffer. + + @tparam isRequest A `bool` indicating whether the parser will be + presented with request or response message. + + @tparam isDirect A `bool` indicating whether the parser interface + supports reading body data directly into parser-provided buffers. + + @tparam Derived The derived class type. This is part of the + Curiously Recurring Template Pattern interface. +*/ +template +class basic_parser + : private detail::basic_parser_base +{ + template + friend class basic_parser; + + // Message will be complete after reading header + static unsigned constexpr flagSkipBody = 1<< 0; + + + + static unsigned constexpr flagOnBody = 1<< 1; + + // The parser has read at least one byte + static unsigned constexpr flagGotSome = 1<< 2; + + // Message semantics indicate a body is expected. + // cleared if flagSkipBody set + // + static unsigned constexpr flagHasBody = 1<< 3; + + static unsigned constexpr flagHTTP11 = 1<< 4; + static unsigned constexpr flagNeedEOF = 1<< 5; + static unsigned constexpr flagExpectCRLF = 1<< 6; + static unsigned constexpr flagFinalChunk = 1<< 7; + static unsigned constexpr flagConnectionClose = 1<< 8; + static unsigned constexpr flagConnectionUpgrade = 1<< 9; + static unsigned constexpr flagConnectionKeepAlive = 1<< 10; + static unsigned constexpr flagContentLength = 1<< 11; + static unsigned constexpr flagChunked = 1<< 12; + static unsigned constexpr flagUpgrade = 1<< 13; + + std::uint64_t len_; // size of chunk or body + std::unique_ptr buf_; + std::size_t buf_len_ = 0; + std::size_t skip_ = 0; // search from here + std::size_t x_; // scratch variable + unsigned f_ = 0; // flags + parse_state state_ = parse_state::header; + boost::string_ref ext_; + boost::string_ref body_; + +public: + /// Copy constructor (disallowed) + basic_parser(basic_parser const&) = delete; + + /// Copy assignment (disallowed) + basic_parser& operator=(basic_parser const&) = delete; + + /// Default constructor + basic_parser() = default; + + /// `true` if this parser parses requests, `false` for responses. + static bool constexpr is_request = isRequest; + + /// Destructor + ~basic_parser() = default; + + /** Move constructor + + After the move, the only valid operation on the + moved-from object is destruction. + */ + template + basic_parser(basic_parser< + isRequest, OtherIsDirect, OtherDerived>&&); + + /** Set the skip body option. + + The option controls whether or not the parser expects to + see an HTTP body, regardless of the presence or absence of + certain fields such as Content-Length. + + Depending on the request, some responses do not carry a body. + For example, a 200 response to a CONNECT request from a + tunneling proxy. In these cases, callers may use this function + inform the parser that no body is expected. The parser will + consider the message complete after the header has been received. + + @note This function must called before any bytes are processed. + */ + void + skip_body(); + + /** Returns the current parser state. + + The parser state indicates what octets the parser + expects to see next in the input stream. + */ + parse_state + state() const + { + return state_; + } + + /// Returns `true` if the parser has received at least one byte of input. + bool + got_some() const + { + return (f_ & flagGotSome) != 0; + } + + /// Returns `true` if the complete header has been parsed. + bool + got_header() const + { + return state_ != parse_state::header; + } + + /** Returns `true` if a Content-Length is specified. + + @note Only valid after parsing a complete header. + */ + bool + got_content_length() const + { + return (f_ & flagContentLength) != 0; + } + + /** Returns `true` if the message is complete. + + The message is complete after a full header is + parsed and one of the following is true: + + @li @ref skip_body was called + + @li The semantics of the message indicate there is no body. + + @li The semantics of the message indicate a body is + expected, and the entire body was received. + */ + bool + is_complete() const + { + return state_ == parse_state::complete; + } + + /** Returns `true` if the message is an upgrade message. + + @note Only valid after parsing a complete header. + */ + bool + is_upgrade() const + { + return (f_ & flagConnectionUpgrade) != 0; + } + + /** Returns `true` if keep-alive is specified + + @note Only valid after parsing a complete header. + */ + bool + is_keep_alive() const; + + /** Returns `true` if the chunked Transfer-Encoding is specified. + + @note Only valid after parsing a complete header. + */ + bool + is_chunked() const + { + return (f_ & flagChunked) != 0; + } + + /** Write part of a buffer sequence to the parser. + + This function attempts to parse the HTTP message + stored in the caller provided buffers. Upon success, + a positive return value indicates that the parser + made forward progress, consuming that number of + bytes. + + A return value of zero indicates that the parser + requires additional input. In this case the caller + should append additional bytes to the input buffer + sequence and call @ref write again. + + @param buffers An object meeting the requirements of + @b ConstBufferSequence that represents the message. + + @param ec Set to the error, if any occurred. + + @return The number of bytes consumed in the buffer + sequence. + */ + template + std::size_t + write(ConstBufferSequence const& buffers, error_code& ec); + +#if ! GENERATING_DOCS + std::size_t + write(boost::asio::const_buffers_1 const& buffer, + error_code& ec); +#endif + + /** Inform the parser that the end of stream was reached. + + In certain cases, 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. + + This is typically called when a read from the + underlying stream object sets the error code to + `boost::asio::error::eof`. + + @note Only valid after parsing a complete header. + + @param ec Set to the error, if any occurred. + */ + void + write_eof(error_code& ec); + + /** Returns the number of bytes remaining in the body or chunk. + + If a Content-Length is specified and the parser state + is equal to @ref beast::http::parse_state::body, this will return + the number of bytes remaining in the body. If the + chunked Transfer-Encoding is indicated and the parser + state is equal to @ref beast::http::parse_state::chunk_body, this + will return the number of bytes remaining in the chunk. + Otherwise, the function behavior is undefined. + */ + std::uint64_t + size() const + { + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::chunk_body); + return len_; + } + + /** Returns the body data parsed in the last call to @ref write. + + This buffer is invalidated after any call to @ref write + or @ref write_eof. + + @note If the last call to @ref write came from the input + area of a @b DynamicBuffer object, a call to the dynamic + buffer's `consume` function may invalidate this return + value. + */ + boost::string_ref const& + body() const + { + // This function not available when isDirect==true + static_assert(! isDirect, ""); + return body_; + } + + /** Returns the chunk extension parsed in the last call to @ref write. + + This buffer is invalidated after any call to @ref write + or @ref write_eof. + + @note If the last call to @ref write came from the input + area of a @b DynamicBuffer object, a call to the dynamic + buffer's `consume` function may invalidate this return + value. + */ + boost::string_ref const& + chunk_extension() const + { + // This function not available when isDirect==true + static_assert(! isDirect, ""); + return ext_; + } + + /** Returns the optional value of Content-Length if known. + + @note The return value is undefined unless a complete + header has been parsed. + */ + boost::optional + content_length() const + { + BOOST_ASSERT(got_header()); + if(! (f_ & flagContentLength)) + return boost::none; + return len_; + } + + /** Copy leftover body data from the dynamic buffer. + + @note This member function is only available when + `isDirect==true`. + + @return The number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling + `consume` on the buffer. + */ + template + std::size_t + copy_body(DynamicBuffer& dynabuf); + + /** Returns a set of buffers for storing body data. + + @note This member function is only available when + `isDirect==true`. + + @param limit The maximum number of bytes in the + size of the returned buffer sequence. The actual size + of the buffer sequence may be lower than this number. + */ + template + void + prepare_body(boost::optional< + MutableBufferSequence>& buffers, std::size_t limit); + + /** Commit body data. + + @note This member function is only available when + `isDirect==true`. + */ + void + commit_body(std::size_t n); + + /** Indicate that body octets have been consumed. + */ + void + consume(std::size_t n) + { + BOOST_ASSERT(n <= len_); + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::chunk_body); + len_ -= n; + if(len_ == 0) + { + if(state_ == parse_state::body) + state_ = parse_state::complete; + else + state_ = parse_state::chunk_header; + } + } + + /** Consume all remaining body data. + + This function instructs the parser to advance the + state past any expected body octets. Callers who + wish to read and process the body themselves will + call this function. + */ + void + consume_body(error_code& ec); + +private: + inline + Derived& + impl() + { + return *static_cast(this); + } + + template + boost::string_ref + maybe_flatten( + ConstBufferSequence const& buffers); + + std::size_t + do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::true_type); + + std::size_t + do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::false_type); + + void + parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::true_type); + + void + parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::false_type); + + void + parse_fields(char const*& it, + char const* last, error_code& ec); + + void + do_field( + boost::string_ref const& name, + boost::string_ref const& value, + error_code& ec); + + std::size_t + parse_header(char const* p, + std::size_t n, error_code& ec); + + void + do_header(int, std::true_type); + + void + do_header(int status, std::false_type); + + void + maybe_do_body_direct(); + + void + maybe_do_body_indirect(error_code& ec); + + std::size_t + parse_chunk_header(char const* p, + std::size_t n, error_code& ec); + + std::size_t + parse_body(char const* p, + std::size_t n, error_code& ec); + + std::size_t + parse_body_to_eof(char const* p, + std::size_t n, error_code& ec); + + std::size_t + parse_chunk_body(char const* p, + std::size_t n, error_code& ec); + + void + do_complete(error_code& ec); +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/basic_parser_v1.hpp b/include/beast/http/basic_parser_v1.hpp deleted file mode 100644 index 3f03dff6..00000000 --- a/include/beast/http/basic_parser_v1.hpp +++ /dev/null @@ -1,856 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_BASIC_PARSER_v1_HPP -#define BEAST_HTTP_BASIC_PARSER_v1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Parse flags - - The set of parser bit flags are returned by @ref basic_parser_v1::flags. -*/ -enum parse_flag -{ - chunked = 1, - connection_keep_alive = 2, - connection_close = 4, - connection_upgrade = 8, - trailing = 16, - upgrade = 32, - skipbody = 64, - contentlength = 128, - paused = 256 -}; - -/** Body maximum size option. - - Sets the maximum number of cumulative bytes allowed including - all body octets. Octets in chunk-encoded bodies are counted - after decoding. A value of zero indicates no limit on - the number of body octets. - - The default body maximum size for requests is 4MB (four - megabytes or 4,194,304 bytes) and unlimited for responses. - - @note Objects of this type are used with @ref basic_parser_v1::set_option. -*/ -struct body_max_size -{ - std::size_t value; - - explicit - body_max_size(std::size_t v) - : value(v) - { - } -}; - -/** Header maximum size option. - - Sets the maximum number of cumulative bytes allowed - including all header octets. A value of zero indicates - no limit on the number of header octets. - - The default header maximum size is 16KB (16,384 bytes). - - @note Objects of this type are used with @ref basic_parser_v1::set_option. -*/ -struct header_max_size -{ - std::size_t value; - - explicit - header_max_size(std::size_t v) - : value(v) - { - } -}; - -/** A value indicating how the parser should treat the body. - - This value is returned from the `on_header` callback in - the derived class. It controls what the parser does next - in terms of the message body. -*/ -enum class body_what -{ - /** The parser should expect a body, keep reading. - */ - normal, - - /** Skip parsing of the body. - - When returned by `on_header` this causes parsing to - complete and control to return to the caller. This - could be used when sending a response to a HEAD - request, for example. - */ - skip, - - /** The message represents an UPGRADE request. - - When returned by `on_body_prepare` this causes parsing - to complete and control to return to the caller. - */ - upgrade, - - /** Suspend parsing before reading the body. - - When returned by `on_body_prepare` this causes parsing - to pause. Control is returned to the caller, and the - parser state is preserved such that a subsequent call - to the parser will begin reading the message body. - - This could be used by callers to inspect the HTTP - header before committing to read the body. For example, - to choose the body type based on the fields. Or to - respond to an Expect: 100-continue request. - */ - pause -}; - -/// The value returned when no content length is known or applicable. -static std::uint64_t constexpr no_content_length = - (std::numeric_limits::max)(); - -/** A parser for decoding HTTP/1 wire format messages. - - This parser is designed to efficiently parse messages in the - HTTP/1 wire format. It allocates no memory and uses minimal - state. It will handle chunked encoding and it understands the - semantics of the Connection and Content-Length header fields. - - The interface uses CRTP (Curiously Recurring Template Pattern). - To use this class, derive from basic_parser. When bytes are - presented, the implementation will make a series of zero or - more calls to derived class members functions (referred to as - "callbacks" from here on) matching a specific signature. - - Every callback must be provided by the derived class, or else - a compilation error will be generated. This exemplar shows - the signature and description of the callbacks required in - the derived class. - - @code - template - struct exemplar : basic_parser_v1 - { - // Called when the first valid octet of a new message is received - // - void on_start(error_code&); - - // Called for each piece of the Request-Method - // - void on_method(boost::string_ref const&, error_code&); - - // Called for each piece of the Request-URI - // - void on_uri(boost::string_ref const&, error_code&); - - // Called for each piece of the reason-phrase - // - void on_reason(boost::string_ref const&, error_code&); - - // Called after the entire Request-Line has been parsed successfully. - // - void on_request(error_code&); - - // Called after the entire Response-Line has been parsed successfully. - // - void on_response(error_code&); - - // Called for each piece of the current header field. - // - void on_field(boost::string_ref const&, error_code&); - - // Called for each piece of the current header value. - // - void on_value(boost::string_ref const&, error_code&) - - // Called when the entire header has been parsed successfully. - // - void - on_header(std::uint64_t content_length, error_code&); - - // Called after on_header, before the body is parsed - // - body_what - on_body_what(std::uint64_t content_length, error_code&); - - // Called for each piece of the body. - // - // If the header indicates chunk encoding, the chunk - // encoding is removed from the buffer before being - // passed to the callback. - // - void on_body(boost::string_ref const&, error_code&); - - // Called when the entire message has been parsed successfully. - // At this point, @ref complete returns `true`, and the parser - // is ready to parse another message if `keep_alive` would - // return `true`. - // - void on_complete(error_code&) {} - }; - @endcode - - The return value of `on_body_what` is special, it controls - whether or not the parser should expect a body. See @ref body_what - for choices of the return value. - - If a callback sets an error, parsing stops at the current octet - and the error is returned to the caller. Callbacks must not throw - exceptions. - - @tparam isRequest A `bool` indicating whether the parser will be - presented with request or response message. - - @tparam Derived The derived class type. This is part of the - Curiously Recurring Template Pattern interface. -*/ -template -class basic_parser_v1 : public detail::parser_base -{ -private: - template - friend class basic_parser_v1; - - using self = basic_parser_v1; - typedef void(self::*pmf_t)(error_code&, boost::string_ref const&); - - 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_length0, - h_content_length, - h_content_length_ows, - h_transfer_encoding, - h_upgrade, - - h_matching_transfer_encoding_chunked, - h_matching_transfer_encoding_general, - h_matching_connection_keep_alive, - h_matching_connection_close, - h_matching_connection_upgrade, - - h_transfer_encoding_chunked, - h_transfer_encoding_chunked_ows, - - h_connection_keep_alive, - h_connection_keep_alive_ows, - h_connection_close, - h_connection_close_ows, - h_connection_upgrade, - h_connection_upgrade_ows, - h_connection_token, - h_connection_token_ows - }; - - std::size_t h_max_; - std::size_t h_left_; - std::size_t b_max_; - std::size_t b_left_; - std::uint64_t content_length_; - pmf_t cb_; - state s_ : 8; - unsigned fs_ : 8; - unsigned pos_ : 8; // position in field state - unsigned http_major_ : 16; - unsigned http_minor_ : 16; - unsigned status_code_ : 16; - unsigned flags_ : 9; - bool upgrade_ : 1; // true if parser exited for upgrade - -public: - /// Default constructor - basic_parser_v1(); - - /// Copy constructor. - template - basic_parser_v1(basic_parser_v1< - isRequest, OtherDerived> const& other); - - /// Copy assignment. - template - basic_parser_v1& operator=(basic_parser_v1< - isRequest, OtherDerived> const& other); - - /** Set options on the parser. - - @param args One or more parser options to set. - */ -#if BEAST_DOXYGEN - template - void - set_option(Args&&... args) -#else - template - void - set_option(A1&& a1, A2&& a2, An&&... an) -#endif - { - set_option(std::forward(a1)); - set_option(std::forward(a2), - std::forward(an)...); - } - - /// Set the header maximum size option - void - set_option(header_max_size const& o) - { - h_max_ = o.value; - h_left_ = h_max_; - } - - /// Set the body maximum size option - void - set_option(body_max_size const& o) - { - b_max_ = o.value; - b_left_ = b_max_; - } - - /// Returns internal flags associated with the parser. - unsigned - flags() const - { - return flags_; - } - - /** Returns `true` if the message end is indicated by eof. - - 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. - - @return `true` if write_eof must be used to indicate the message end. - */ - bool - needs_eof() const - { - 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 || - s_ == s_closed_complete || - (flags_ & parse_flag::paused); - } - - /** Write a sequence of buffers 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 -#if BEAST_DOXYGEN - std::size_t -#else - typename std::enable_if< - ! std::is_convertible::value, - std::size_t>::type -#endif - write(ConstBufferSequence const& buffers, error_code& ec); - - /** Write a single buffer of data to the parser. - - @param buffer The buffer to write. - @param ec Set to the error, if any error occurred. - - @return The number of bytes consumed in the buffer. - */ - std::size_t - write(boost::asio::const_buffer const& buffer, error_code& 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. - */ - void - write_eof(error_code& ec); - -protected: - /** Reset the parsing state. - - The state of the parser is reset to expect the beginning of - a new request or response. The old state is discarded. - */ - void - reset(); - -private: - Derived& - impl() - { - return *static_cast(this); - } - - void - reset(std::true_type) - { - s_ = s_req_start; - } - - void - reset(std::false_type) - { - s_ = s_res_start; - } - - void - init(std::true_type) - { - // Request: 16KB max header, 4MB max body - h_max_ = 16 * 1024; - b_max_ = 4 * 1024 * 1024; - } - - void - init(std::false_type) - { - // Response: 16KB max header, unlimited body - h_max_ = 16 * 1024; - b_max_ = 0; - } - - void - init() - { - init(std::integral_constant{}); - reset(); - } - - bool - needs_eof(std::true_type) const; - - bool - needs_eof(std::false_type) const; - - template> - struct check_on_start : std::false_type {}; - - template - struct check_on_start().on_start( - std::declval()) - )>> : std::true_type { }; - - template> - struct check_on_method : std::false_type {}; - - template - struct check_on_method().on_method( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_uri : std::false_type {}; - - template - struct check_on_uri().on_uri( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_reason : std::false_type {}; - - template - struct check_on_reason().on_reason( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_request : std::false_type {}; - - template - struct check_on_request().on_request( - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_response : std::false_type {}; - - template - struct check_on_response().on_response( - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_field : std::false_type {}; - - template - struct check_on_field().on_field( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_value : std::false_type {}; - - template - struct check_on_value().on_value( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_headers : std::false_type {}; - - template - struct check_on_headers().on_header( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - // VFALCO Can we use std::is_detected? Is C++11 capable? - template - class check_on_body_what_t - { - template().on_body_what( - std::declval(), - std::declval())), - body_what>> - static R check(int); - template - static std::false_type check(...); - using type = decltype(check(0)); - public: - static bool const value = type::value; - }; - template - using check_on_body_what = - std::integral_constant::value>; - - template> - struct check_on_body : std::false_type {}; - - template - struct check_on_body().on_body( - std::declval(), - std::declval()) - )>> : std::true_type {}; - - template> - struct check_on_complete : std::false_type {}; - - template - struct check_on_complete().on_complete( - std::declval()) - )>> : std::true_type {}; - - void call_on_start(error_code& ec) - { - static_assert(check_on_start::value, - "on_start requirements not met"); - impl().on_start(ec); - } - - void call_on_method(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_method::value, - "on_method requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_method(s, ec); - } - - void call_on_method(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_method(error_code& ec, - boost::string_ref const& s) - { - call_on_method(ec, s, - std::integral_constant{}); - } - - void call_on_uri(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_uri::value, - "on_uri requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_uri(s, ec); - } - - void call_on_uri(error_code&, - boost::string_ref const&, std::false_type) - { - } - - void call_on_uri(error_code& ec, - boost::string_ref const& s) - { - call_on_uri(ec, s, - std::integral_constant{}); - } - - void call_on_reason(error_code& ec, - boost::string_ref const& s, std::true_type) - { - static_assert(check_on_reason::value, - "on_reason requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - 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{}); - } - - void call_on_request(error_code& ec, std::true_type) - { - static_assert(check_on_request::value, - "on_request requirements not met"); - impl().on_request(ec); - } - - void call_on_request(error_code&, std::false_type) - { - } - - void call_on_request(error_code& ec) - { - call_on_request(ec, - std::integral_constant{}); - } - - void call_on_response(error_code& ec, std::true_type) - { - static_assert(check_on_response::value, - "on_response requirements not met"); - impl().on_response(ec); - } - - void call_on_response(error_code&, std::false_type) - { - } - - void call_on_response(error_code& ec) - { - call_on_response(ec, - std::integral_constant{}); - } - - void call_on_field(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_field::value, - "on_field requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_field(s, ec); - } - - void call_on_value(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_value::value, - "on_value requirements not met"); - if(h_max_ && s.size() > h_left_) - { - ec = parse_error::header_too_big; - return; - } - h_left_ -= s.size(); - impl().on_value(s, ec); - } - - void - call_on_headers(error_code& ec) - { - static_assert(check_on_headers::value, - "on_header requirements not met"); - impl().on_header(content_length_, ec); - } - - body_what - call_on_body_what(error_code& ec) - { - static_assert(check_on_body_what::value, - "on_body_what requirements not met"); - return impl().on_body_what(content_length_, ec); - } - - void call_on_body(error_code& ec, - boost::string_ref const& s) - { - static_assert(check_on_body::value, - "on_body requirements not met"); - if(b_max_ && s.size() > b_left_) - { - ec = parse_error::body_too_big; - return; - } - b_left_ -= s.size(); - impl().on_body(s, ec); - } - - void call_on_complete(error_code& ec) - { - static_assert(check_on_complete::value, - "on_complete requirements not met"); - impl().on_complete(ec); - } -}; - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/concepts.hpp b/include/beast/http/concepts.hpp index 8dafa2ca..7c58502a 100644 --- a/include/beast/http/concepts.hpp +++ b/include/beast/http/concepts.hpp @@ -5,13 +5,15 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_HTTP_TYPE_CHECK_HPP -#define BEAST_HTTP_TYPE_CHECK_HPP +#ifndef BEAST_HTTP_CONCEPTS_HPP +#define BEAST_HTTP_CONCEPTS_HPP #include #include +#include #include #include +#include #include #include @@ -86,45 +88,6 @@ public: >; }; -template -class is_Parser -{ - template().complete()), - bool>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().write( - std::declval(), - std::declval())), - std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - template().write_eof(std::declval()), - std::true_type{})> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - -public: - using type = std::integral_constant; -}; - } // detail /// Determine if `T` meets the requirements of @b Body. @@ -183,19 +146,27 @@ template struct is_Reader : std::integral_constant {}; #else template> -struct is_Reader : std::false_type {}; +struct is_Reader : std::true_type {}; template struct is_Reader(), std::declval().init( - std::declval()), - std::declval().write( - std::declval(), - std::declval(), - std::declval()) + std::declval>()), + std::declval().prepare( + std::declval()), + std::declval().commit( + std::declval()), + std::declval().finish() )> > : std::integral_constant::value - > + is_MutableBufferSequence< + typename T::mutable_buffers_type>::value && + std::is_convertible().prepare( + std::declval())), + typename T::mutable_buffers_type + >::value> + { static_assert(std::is_same< typename M::body_type::reader, T>::value, @@ -217,14 +188,6 @@ struct is_Writer : std::integral_constant {}; using is_Writer = typename detail::is_Writer::type; #endif -/// Determine if `T` meets the requirements of @b Parser. -template -#if BEAST_DOXYGEN -struct is_Parser : std::integral_constant{}; -#else -using is_Parser = typename detail::is_Parser::type; -#endif - } // http } // beast diff --git a/include/beast/http/detail/basic_parsed_list.hpp b/include/beast/http/detail/basic_parsed_list.hpp new file mode 100644 index 00000000..91224454 --- /dev/null +++ b/include/beast/http/detail/basic_parsed_list.hpp @@ -0,0 +1,194 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSED_LIST_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +/** A list parser which presents the sequence as a container. +*/ +template +class basic_parsed_list +{ + boost::string_ref s_; + +public: + /// The type of policy this list uses for parsing. + using policy_type = Policy; + + /// The type of each element in the list. + using value_type = typename Policy::value_type; + + /// A constant iterator to a list element. +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + class const_iterator + : private beast::detail:: + empty_base_optimization + { + basic_parsed_list const* list_ = nullptr; + char const* it_ = nullptr; + typename Policy::value_type v_; + bool error_ = false; + + public: + using value_type = + typename Policy::value_type; + using reference = value_type const&; + using pointer = value_type const*; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::forward_iterator_tag; + + const_iterator() = default; + + bool + operator==( + const_iterator const& other) const + { + return + other.list_ == list_ && + other.it_ == it_; + } + + bool + operator!=( + const_iterator const& other) const + { + return ! (*this == other); + } + + reference + operator*() const + { + return v_; + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + bool + error() const + { + return error_; + } + + private: + friend class basic_parsed_list; + + const_iterator( + basic_parsed_list const& list, bool at_end) + : list_(&list) + , it_(at_end ? nullptr : + list.s_.begin()) + { + if(! at_end) + increment(); + } + + void + increment() + { + if(! this->member()( + v_, it_, list_->s_)) + { + it_ = nullptr; + error_ = true; + } + } + }; + + /// Construct a list from a string + explicit + basic_parsed_list(boost::string_ref const& s) + : s_(s) + { + } + + /// Return a const iterator to the beginning of the list + const_iterator begin() const; + + /// Return a const iterator to the end of the list + const_iterator end() const; + + /// Return a const iterator to the beginning of the list + const_iterator cbegin() const; + + /// Return a const iterator to the end of the list + const_iterator cend() const; +}; + +template +inline +auto +basic_parsed_list:: +begin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +inline +auto +basic_parsed_list:: +end() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +template +inline +auto +basic_parsed_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{*this, false}; +} + +template +inline +auto +basic_parsed_list:: +cend() const -> + const_iterator +{ + return const_iterator{*this, true}; +} + +} // detail +} // http +} // beast + +#endif + diff --git a/include/beast/http/detail/basic_parser.hpp b/include/beast/http/detail/basic_parser.hpp new file mode 100644 index 00000000..96e2c4f4 --- /dev/null +++ b/include/beast/http/detail/basic_parser.hpp @@ -0,0 +1,446 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_HPP +#define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include + +/* + Portions of this file based on code from picophttpparser, + copyright notice below. + https://github.com/h2o/picohttpparser +*/ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +namespace beast { +namespace http { +namespace detail { + +#if __GNUC__ >= 3 +# define BEAST_LIKELY(x) __builtin_expect(!!(x), 1) +# define BEAST_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define BEAST_LIKELY(x) (x) +#define BEAST_UNLIKELY(x) (x) +#endif + +class basic_parser_base +{ +protected: + static + bool + is_pathchar(char c) + { + // VFALCO This looks the same as the one below... + + // TEXT = + static bool constexpr tab[256] = { + 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, 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)]; + } + + static + bool + is_value_char(char c) + { + // any OCTET except CTLs and LWS + static bool constexpr tab[256] = { + 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, 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)]; + } + + static + inline + bool + is_text(char c) + { + // VCHAR / SP / HT / obs-text + static bool constexpr tab[256] = { + 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)]; + } + + static + inline + bool + unhex(unsigned char& d, char c) + { + static signed char constexpr tab[256] = { + -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 + + -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 + }; + d = static_cast( + tab[static_cast(c)]); + return d != static_cast(-1); + } + + static + bool + is_digit(char c) + { + return static_cast(c-'0') < 10; + } + + static + bool + is_print(char c) + { + return static_cast(c-33) < 94; + } + + static + boost::string_ref + make_string(char const* first, char const* last) + { + return {first, static_cast< + std::size_t>(last - first)}; + } + + template + static + bool + strieq(boost::string_ref const& s1, + boost::string_ref const& s2) + { + if(s1.size() != s2.size()) + return false; + auto p1 = s1.data(); + auto p2 = s2.data(); + for(auto n = s1.size(); n--; ++p1, ++p2) + if(*p1 != tolower(*p2)) + return false; + return true; + } + + template + bool + strieq(const char (&s1)[N], + boost::string_ref const& s2) + { + return strieq({s1, N-1}, s2); + } + + template + static + bool + parse_dec(Iter it, Iter last, Unsigned& v) + { + if(! is_digit(*it)) + return false; + v = *it - '0'; + for(;;) + { + if(! is_digit(*++it)) + break; + auto const d = *it - '0'; + if(v > ((std::numeric_limits< + Unsigned>::max)() - 10) / 10) + return false; + v = 10 * v + d; + } + return it == last; + } + + template + bool + parse_hex(Iter& it, Unsigned& v) + { + unsigned char d; + if(! unhex(d, *it)) + return false; + v = d; + for(;;) + { + if(! unhex(d, *++it)) + break; + auto const v0 = v; + v = 16 * v + d; + if(v <= v0) + return false; + } + return true; + } + + static + bool + parse_crlf(char const*& it) + { + if(*it != '\r') + return false; + if(*++it != '\n') + return false; + ++it; + return true; + } + + static + boost::string_ref + parse_method(char const*& it) + { + auto const first = it; + while(detail::is_tchar(*it)) + ++it; + return {first, static_cast< + boost::string_ref::size_type>( + it - first)}; + } + + static + boost::string_ref + parse_path(char const*& it) + { + auto const first = it; + while(is_pathchar(*it)) + ++it; + if(*it != ' ') + return {}; + return {first, static_cast< + boost::string_ref::size_type>( + it - first)}; + } + + static + boost::string_ref + parse_name(char const*& it) + { + auto const first = it; + while(to_field_char(*it)) + ++it; + return {first, static_cast< + boost::string_ref::size_type>( + it - first)}; + } + + static + int + parse_version(char const*& it) + { + if(*it != 'H') + return -1; + if(*++it != 'T') + return -1; + if(*++it != 'T') + return -1; + if(*++it != 'P') + return -1; + if(*++it != '/') + return -1; + if(! is_digit(*++it)) + return -1; + int v = 10 * (*it - '0'); + if(*++it != '.') + return -1; + if(! is_digit(*++it)) + return -1; + v += *it++ - '0'; + return v; + } + + static + int + parse_status(char const*& it) + { + int v; + if(! is_digit(*it)) + return -1; + v = 100 * (*it - '0'); + if(! is_digit(*++it)) + return -1; + v += 10 * (*it - '0'); + if(! is_digit(*++it)) + return -1; + v += (*it++ - '0'); + return v; + } + + static + boost::string_ref + parse_reason(char const*& it) + { + auto const first = it; + while(*it != '\r') + { + if(! is_text(*it)) + return {}; + ++it; + } + return {first, static_cast< + std::size_t>(it - first)}; + } + + // VFALCO Can SIMD help this? + static + char const* + find_eol( + char const* first, char const* last, + error_code& ec) + { + auto it = first; + for(;;) + { + if(it == last) + return nullptr; + if(*it == '\r') + { + if(++it == last) + return nullptr; + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + return ++it; + } + // VFALCO Should we handle the legacy case + // for lines terminated with a single '\n'? + ++it; + } + } + + // VFALCO Can SIMD help this? + static + char const* + find_eom( + char const* first, char const* last, + error_code& ec) + { + auto it = first; + for(;;) + { + if(it == last) + return nullptr; + if(*it == '\r') + { + if(++it == last) + return nullptr; + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + if(++it == last) + return nullptr; + if(*it != '\r') + { + ++it; + continue; + } + if(++it == last) + return nullptr; + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + return ++it; + } + // VFALCO Should we handle the legacy case + // for lines terminated with a single '\n'? + ++it; + } + } +}; + +} // detail +} // http +} // beast + +#endif diff --git a/include/beast/http/detail/basic_parser_v1.hpp b/include/beast/http/detail/basic_parser_v1.hpp deleted file mode 100644 index 2df947d1..00000000 --- a/include/beast/http/detail/basic_parser_v1.hpp +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP -#define BEAST_HTTP_DETAIL_BASIC_PARSER_V1_HPP - -#include - -namespace beast { -namespace http { -namespace detail { - -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<>; - -class parser_base -{ -protected: - enum state : std::uint8_t - { - s_dead = 1, - - s_req_start, - s_req_method0, - s_req_method, - s_req_url0, - s_req_url, - s_req_http, - s_req_http_H, - s_req_http_HT, - s_req_http_HTT, - s_req_http_HTTP, - s_req_major, - s_req_dot, - s_req_minor, - s_req_cr, - s_req_lf, - - s_res_start, - s_res_H, - s_res_HT, - s_res_HTT, - s_res_HTTP, - s_res_major, - s_res_dot, - s_res_minor, - s_res_space_1, - s_res_status0, - s_res_status1, - s_res_status2, - s_res_space_2, - s_res_reason0, - s_res_reason, - s_res_line_lf, - s_res_line_done, - - s_header_name0, - s_header_name, - s_header_value0_lf, - s_header_value0_almost_done, - s_header_value0, - s_header_value, - s_header_value_lf, - s_header_value_almost_done, - s_header_value_unfold, - - s_headers_almost_done, - s_headers_done, - - s_chunk_size0, - s_chunk_size, - s_chunk_ext_name0, - s_chunk_ext_name, - s_chunk_ext_val, - s_chunk_size_lf, - s_chunk_data0, - s_chunk_data, - s_chunk_data_cr, - s_chunk_data_lf, - - s_body_pause, - s_body_identity0, - s_body_identity, - s_body_identity_eof0, - s_body_identity_eof, - - s_complete, - s_restart, - s_closed_complete - }; -}; - -} // detail -} // http -} // beast - -#endif diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp index a68b4da8..518efb82 100644 --- a/include/beast/http/detail/rfc7230.hpp +++ b/include/beast/http/detail/rfc7230.hpp @@ -46,7 +46,7 @@ is_alpha(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } inline @@ -73,7 +73,7 @@ is_text(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } inline @@ -105,7 +105,7 @@ is_tchar(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } inline @@ -134,7 +134,7 @@ is_qdchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } inline @@ -164,7 +164,7 @@ is_qpchar(char c) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } // converts to lower case, @@ -200,7 +200,7 @@ to_field_char(char c) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } // converts to lower case, @@ -230,9 +230,10 @@ to_value_char(char c) 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // 240 }; static_assert(sizeof(tab) == 256, ""); - return static_cast(tab[static_cast(c)]); + return static_cast(tab[static_cast(c)]); } +// VFALCO TODO Make this return unsigned? inline std::int8_t unhex(char c) @@ -256,22 +257,68 @@ unhex(char c) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240 }; static_assert(sizeof(tab) == 256, ""); - return tab[static_cast(c)]; + return tab[static_cast(c)]; } template +inline void skip_ows(FwdIt& it, FwdIt const& end) { while(it != end) { - auto const c = *it; - if(c != ' ' && c != '\t') + if(*it != ' ' && *it != '\t') break; ++it; } } +template +inline +void +skip_ows_rev( + RanIt& it, RanIt const& first) +{ + while(it != first) + { + auto const c = it[-1]; + if(c != ' ' && c != '\t') + break; + --it; + } +} + +// obs-fold = CRLF 1*( SP / HTAB ) +// return `false` on parse error +// +template +inline +bool +skip_obs_fold( + FwdIt& it, FwdIt const& last) +{ + for(;;) + { + if(*it != '\r') + return true; + if(++it == last) + return false; + if(*it != '\n') + return false; + if(++it == last) + return false; + if(*it != ' ' && *it != '\t') + return false; + for(;;) + { + if(++it == last) + return true; + if(*it != ' ' && *it != '\t') + return true; + } + } +} + template void skip_token(FwdIt& it, FwdIt const& last) @@ -403,6 +450,53 @@ increment() } } +/* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] +*/ +struct opt_token_list_policy +{ + using value_type = boost::string_ref; + + bool + operator()(value_type& v, + char const*& it, boost::string_ref const& s) const + { + v = {}; + auto need_comma = it != s.begin(); + for(;;) + { + detail::skip_ows(it, s.end()); + if(it == s.end()) + { + it = nullptr; + return true; + } + auto const c = *it; + if(detail::is_tchar(c)) + { + if(need_comma) + return false; + auto const p0 = it; + for(;;) + { + ++it; + if(it == s.end()) + break; + if(! detail::is_tchar(*it)) + break; + } + v = boost::string_ref{&*p0, + static_cast(it - p0)}; + return true; + } + if(c != ',') + return false; + need_comma = false; + ++it; + } + } +}; + } // detail } // http } // beast diff --git a/include/beast/http/error.hpp b/include/beast/http/error.hpp new file mode 100644 index 00000000..d273f0e2 --- /dev/null +++ b/include/beast/http/error.hpp @@ -0,0 +1,83 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_ERROR_HPP +#define BEAST_HTTP_ERROR_HPP + +#include +#include + +namespace beast { +namespace http { + +/// Error codes returned from HTTP parsing +enum class error +{ + /** The end of the stream was reached. + + This error is returned by @ref basic_parser::write_eof + when the end of stream is reached and there are no + unparsed bytes in the stream buffer. + */ + end_of_stream = 1, + + /** The incoming message is incomplete. + + This happens when the end of stream is reached + and some bytes have been received, but not the + entire message. + */ + partial_message, + + /** Buffer maximum exceeded. + + This error is returned when reading HTTP content + into a dynamic buffer, and the operation would + exceed the maximum size of the buffer. + */ + buffer_overflow, + + /// The line ending was malformed + bad_line_ending, + + /// The method is invalid. + bad_method, + + /// The request-target is invalid. + bad_path, + + /// The HTTP-version is invalid. + bad_version, + + /// The status-code is invalid. + bad_status, + + /// The reason-phrase is invalid. + bad_reason, + + /// The field name is invalid. + bad_field, + + /// The field value is invalid. + bad_value, + + /// The Content-Length is invalid. + bad_content_length, + + /// The Transfer-Encoding is invalid. + bad_transfer_encoding, + + /// The chunk syntax is invalid. + bad_chunk +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/header_parser.hpp b/include/beast/http/header_parser.hpp new file mode 100644 index 00000000..72cdaa01 --- /dev/null +++ b/include/beast/http/header_parser.hpp @@ -0,0 +1,190 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_HEADER_PARSER_HPP +#define BEAST_HTTP_HEADER_PARSER_HPP + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A parser for producing HTTP/1 headers. + + This class uses the basic HTTP/1 wire format parser to convert + a series of octets into a @ref header. + + @note A new instance of the parser is required for each message. + + @tparam isRequest Indicates whether a request or response + will be parsed. + + @tparam Fields The type of container used to represent the fields. +*/ +template +class header_parser + : public basic_parser> +{ + header h_; + +public: + using mutable_buffers_type = + boost::asio::null_buffers; + + /// The type of @ref header this object produces. + using value_type = header; + + /// Copy constructor. + header_parser(header_parser const&) = default; + + /// Copy assignment. + header_parser& operator=(header_parser const&) = default; + + /** Move constructor. + + After the move, the only valid operation + on the moved-from object is destruction. + */ + header_parser(header_parser&&) = default; + + /** Constructor + + @param args If present, additional arguments to be + forwarded to the @ref beast::http::header constructor. + */ + template + explicit + header_parser(Args&&... args); + + /** Returns the parsed header + + Only valid if @ref got_header would return `true`. + */ + value_type const& + get() const + { + return h_; + } + + /** Returns the parsed header. + + Only valid if @ref got_header would return `true`. + */ + value_type& + get() + { + return h_; + } + + /** Returns ownership of the parsed header. + + Ownership is transferred to the caller. Only + valid if @ref got_header would return `true`. + + Requires: + @ref value_type is @b MoveConstructible + */ + value_type + release() + { + static_assert(std::is_move_constructible::value, + "MoveConstructible requirements not met"); + return std::move(h_); + } + +private: + friend class basic_parser< + isRequest, false, header_parser>; + + void + on_request( + boost::string_ref const& method, + boost::string_ref const& path, + int version, error_code&) + { + h_.url = std::string{ + path.data(), path.size()}; + h_.method = std::string{ + method.data(), method.size()}; + h_.version = version; + } + + void + on_response(int status, + boost::string_ref const& reason, + int version, error_code&) + { + h_.status = status; + h_.reason = std::string{ + reason.data(), reason.size()}; + h_.version = version; + } + + void + on_field(boost::string_ref const& name, + boost::string_ref const& value, + error_code&) + { + h_.fields.insert(name, value); + } + + void + on_header(error_code&) + { + } + + void + on_body(error_code& ec) + { + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + } + + void + on_data(boost::string_ref const& s, + error_code& ec) + { + } + + void + on_commit(std::size_t n) + { + // Can't write body data with header-only parser! + BOOST_ASSERT(false); + throw std::logic_error{ + "invalid member function call"}; + } + + void + on_chunk(std::uint64_t n, + boost::string_ref const& ext, + error_code& ec) + { + } + + void + on_complete(error_code&) + { + } +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/header_parser_v1.hpp b/include/beast/http/header_parser_v1.hpp deleted file mode 100644 index 555659ec..00000000 --- a/include/beast/http/header_parser_v1.hpp +++ /dev/null @@ -1,233 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_HEADERS_PARSER_V1_HPP -#define BEAST_HTTP_HEADERS_PARSER_V1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -struct request_parser_base -{ - std::string method_; - std::string uri_; -}; - -struct response_parser_base -{ - std::string reason_; -}; - -} // detail - -/** A parser for a HTTP/1 request or response header. - - This class uses the HTTP/1 wire format parser to - convert a series of octets into a request or - response @ref header. - - @note A new instance of the parser is required for each message. -*/ -template -class header_parser_v1 - : public basic_parser_v1> - , private std::conditional::type -{ -public: - /// The type of the header this parser produces. - using header_type = header; - -private: - // VFALCO Check Fields requirements? - - std::string field_; - std::string value_; - header_type h_; - bool flush_ = false; - -public: - /// Default constructor - header_parser_v1() = default; - - /// Move constructor - header_parser_v1(header_parser_v1&&) = default; - - /// Copy constructor (disallowed) - header_parser_v1(header_parser_v1 const&) = delete; - - /// Move assignment (disallowed) - header_parser_v1& operator=(header_parser_v1&&) = delete; - - /// Copy assignment (disallowed) - header_parser_v1& operator=(header_parser_v1 const&) = delete; - - /** Construct the parser. - - @param args Forwarded to the header constructor. - */ -#if BEAST_DOXYGEN - template - explicit - header_parser_v1(Args&&... args); -#else - template::type, header_parser_v1>::value>> - explicit - header_parser_v1(Arg1&& arg1, ArgN&&... argn) - : h_(std::forward(arg1), - std::forward(argn)...) - { - } -#endif - - /** Returns the parsed header - - Only valid if @ref complete would return `true`. - */ - header_type const& - get() const - { - return h_; - } - - /** Returns the parsed header. - - Only valid if @ref complete would return `true`. - */ - header_type& - get() - { - return h_; - } - - /** Returns ownership of the parsed header. - - Ownership is transferred to the caller. Only - valid if @ref complete would return `true`. - - Requires: - @ref header_type is @b MoveConstructible - */ - header_type - release() - { - static_assert(std::is_move_constructible::value, - "MoveConstructible requirements not met"); - return std::move(h_); - } - -private: - friend class basic_parser_v1; - - void flush() - { - if(! flush_) - return; - flush_ = false; - BOOST_ASSERT(! field_.empty()); - h_.fields.insert(field_, value_); - field_.clear(); - value_.clear(); - } - - void on_start(error_code&) - { - } - - void on_method(boost::string_ref const& s, error_code&) - { - this->method_.append(s.data(), s.size()); - } - - void on_uri(boost::string_ref const& s, error_code&) - { - this->uri_.append(s.data(), s.size()); - } - - void on_reason(boost::string_ref const& s, error_code&) - { - this->reason_.append(s.data(), s.size()); - } - - void on_request_or_response(std::true_type) - { - h_.method = std::move(this->method_); - h_.url = std::move(this->uri_); - } - - void on_request_or_response(std::false_type) - { - h_.status = this->status_code(); - h_.reason = std::move(this->reason_); - } - - void on_request(error_code& ec) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_response(error_code& ec) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_field(boost::string_ref const& s, error_code&) - { - flush(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - flush_ = true; - } - - void - on_header(std::uint64_t, error_code&) - { - flush(); - h_.version = 10 * this->http_major() + this->http_minor(); - } - - body_what - on_body_what(std::uint64_t, error_code&) - { - return body_what::pause; - } - - void on_body(boost::string_ref const&, error_code&) - { - } - - void on_complete(error_code&) - { - } -}; - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/async_read.ipp b/include/beast/http/impl/async_read.ipp new file mode 100644 index 00000000..af362f6b --- /dev/null +++ b/include/beast/http/impl/async_read.ipp @@ -0,0 +1,716 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP +#define BEAST_HTTP_IMPL_ASYNC_READ_IPP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template +class read_some_buffer_op +{ + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + basic_parser& p; + boost::optional mb; + boost::optional bb; + std::size_t bytes_used; + int state = 0; + + data(Handler& handler, Stream& s_, DynamicBuffer& db_, + basic_parser& p_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(db_) + , p(p_) + { + } + }; + + handler_ptr d_; + +public: + read_some_buffer_op(read_some_buffer_op&&) = default; + read_some_buffer_op(read_some_buffer_op const&) = default; + + template + read_some_buffer_op(DeducedHandler&& h, + Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + (*this)(error_code{}, 0, false); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* + asio_handler_allocate(std::size_t size, + read_some_buffer_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void + asio_handler_deallocate( + void* p, std::size_t size, + read_some_buffer_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool + asio_handler_is_continuation( + read_some_buffer_op* op) + { + return op->d_->cont; + } + + template + friend + void + asio_handler_invoke(Function&& f, + read_some_buffer_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +read_some_buffer_op:: +operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(d.state == 99) + goto upcall; + for(;;) + { + switch(d.state) + { + case 0: + if(d.db.size() == 0) + { + d.state = 2; + break; + } + //[[fallthrough]] + + case 1: + { + BOOST_ASSERT(d.db.size() > 0); + d.bytes_used = + d.p.write(d.db.data(), ec); + if(d.bytes_used > 0 || ec) + { + // call handler + if(d.state == 1) + goto upcall; + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + } + //[[fallthrough]] + } + + case 2: + case 3: + { + auto const size = + read_size_helper(d.db, 65536); + BOOST_ASSERT(size > 0); + try + { + d.mb.emplace(d.db.prepare(size)); + } + catch(std::length_error const&) + { + // call handler + if(d.state == 3) + goto upcall; + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), + error::buffer_overflow, 0)); + return; + } + // read + d.state = 4; + d.s.async_read_some(*d.mb, std::move(*this)); + return; + } + + case 4: + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + d.bytes_used = 0; + if(! d.p.got_some()) + goto upcall; + // caller sees EOF on next read. + ec = {}; + d.p.write_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(d.p.is_complete()); + goto upcall; + } + else if(ec) + { + d.bytes_used = 0; + goto upcall; + } + BOOST_ASSERT(bytes_transferred > 0); + d.db.commit(bytes_transferred); + d.state = 1; + break; + } + } +upcall: + // can't pass any members of `d` otherwise UB + auto const bytes_used = d.bytes_used; + d_.invoke(ec, bytes_used); +} + +//------------------------------------------------------------------------------ + +template +class read_some_body_op +{ + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + basic_parser& p; + boost::optional mb; + std::size_t bytes_used; + int state = 0; + + data(Handler& handler, Stream& s_, DynamicBuffer& db_, + basic_parser& p_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(db_) + , p(p_) + { + } + }; + + handler_ptr d_; + +public: + read_some_body_op(read_some_body_op&&) = default; + read_some_body_op(read_some_body_op const&) = default; + + template + read_some_body_op(DeducedHandler&& h, + Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + (*this)(error_code{}, 0, false); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + void* + asio_handler_allocate(std::size_t size, + read_some_body_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void + asio_handler_deallocate( + void* p, std::size_t size, + read_some_body_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool + asio_handler_is_continuation( + read_some_body_op* op) + { + return op->d_->cont; + } + + template + friend + void + asio_handler_invoke(Function&& f, + read_some_body_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +read_some_body_op:: +operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(d.state == 99) + goto upcall; + for(;;) + { + switch(d.state) + { + case 0: + if(d.db.size() > 0) + { + d.bytes_used = d.p.copy_body(d.db); + // call handler + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), + ec, 0)); + return; + } + d.p.prepare_body(d.mb, 65536); + // read + d.state = 1; + d.s.async_read_some( + *d.mb, std::move(*this)); + return; + + case 1: + d.bytes_used = 0; + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + // caller sees EOF on next read + ec = {}; + d.p.write_eof(ec); + if(ec) + goto upcall; + BOOST_ASSERT(d.p.is_complete()); + } + else if(! ec) + { + d.p.commit_body(bytes_transferred); + } + goto upcall; + } + } +upcall: + // can't pass any members of `d` otherwise UB + auto const bytes_used = d.bytes_used; + d_.invoke(ec, bytes_used); +} + +//------------------------------------------------------------------------------ + +template +class parse_op +{ + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + basic_parser& p; + + data(Handler& handler, Stream& s_, DynamicBuffer& db_, + basic_parser& p_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(db_) + , p(p_) + { + } + }; + + handler_ptr d_; + +public: + parse_op(parse_op&&) = default; + parse_op(parse_op const&) = default; + + template + parse_op(DeducedHandler&& h, + Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + (*this)(error_code{}, 0, false); + } + + void + operator()(error_code const& ec, + std::size_t bytes_used, bool again = true); + + friend + void* + asio_handler_allocate( + std::size_t size, parse_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void + asio_handler_deallocate( + void* p, std::size_t size, + parse_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool + asio_handler_is_continuation( + parse_op* op) + { + return op->d_->cont; + } + + template + friend + void + asio_handler_invoke( + Function&& f, parse_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +parse_op:: +operator()(error_code const& ec, + std::size_t bytes_used, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(! ec) + { + d.db.consume(bytes_used); + if(! d.p.is_complete()) + return async_read_some( + d.s, d.db, d.p, std::move(*this)); + } + d_.invoke(ec); +} + +//------------------------------------------------------------------------------ + +template +class read_message_op +{ + using parser_type = + message_parser; + + using message_type = + message; + + struct data + { + bool cont; + Stream& s; + DynamicBuffer& db; + message_type& m; + parser_type p; + bool started = false; + int state = 0; + + data(Handler& handler, Stream& s_, + DynamicBuffer& sb_, message_type& m_) + : cont(beast_asio_helpers:: + is_continuation(handler)) + , s(s_) + , db(sb_) + , m(m_) + { + } + }; + + handler_ptr d_; + +public: + read_message_op(read_message_op&&) = default; + read_message_op(read_message_op const&) = default; + + template + read_message_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(args)...) + { + (*this)(error_code{}, false); + } + + void + operator()(error_code ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, read_message_op* op) + { + return beast_asio_helpers:: + allocate(size, op->d_.handler()); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, read_message_op* op) + { + return beast_asio_helpers:: + deallocate(p, size, op->d_.handler()); + } + + friend + bool asio_handler_is_continuation(read_message_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, read_message_op* op) + { + return beast_asio_helpers:: + invoke(f, op->d_.handler()); + } +}; + +template +void +read_message_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(ec) + goto upcall; + switch(d.state) + { + case 0: + d.state = 1; + beast::http::async_read( + d.s, d.db, d.p, std::move(*this)); + return; + + case 1: + d.m = d.p.release(); + goto upcall; + } +upcall: + d_.invoke(ec); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + beast::async_completion completion{handler}; + switch(parser.state()) + { + case parse_state::header: + case parse_state::chunk_header: + detail::read_some_buffer_op{ + completion.handler, stream, dynabuf, parser}; + break; + + default: + detail::read_some_body_op{ + completion.handler, stream, dynabuf, parser}; + break; + } + return completion.result.get(); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived, + class ReadHandler> +inline +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + beast::async_completion completion{handler}; + detail::read_some_buffer_op{ + completion.handler, stream, dynabuf, parser}; + return completion.result.get(); +} + +} // detail + +//------------------------------------------------------------------------------ + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + return detail::async_read_some(stream, dynabuf, parser, + std::forward(handler)); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code)>::result_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + beast::async_completion completion{handler}; + detail::parse_op{ + completion.handler, stream, dynabuf, parser}; + return completion.result.get(); +} + +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, class Body, class Fields, + class ReadHandler> +typename async_completion< + ReadHandler, void(error_code)>::result_type +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + message& msg, + ReadHandler&& handler) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + static_assert(is_Body::value, + "Body requirements not met"); + static_assert(has_reader::value, + "Body has no reader"); + static_assert(is_Reader>::value, + "Reader requirements not met"); + beast::async_completion completion{handler}; + detail::read_message_op{completion.handler, + stream, dynabuf, msg}; + return completion.result.get(); +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp new file mode 100644 index 00000000..b7e5ba4b --- /dev/null +++ b/include/beast/http/impl/basic_parser.ipp @@ -0,0 +1,1062 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP +#define BEAST_HTTP_IMPL_BASIC_PARSER_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +template +basic_parser:: +basic_parser(basic_parser&& other) + : len_(other.len_) + , buf_(std::move(other.buf_)) + , buf_len_(other.buf_len_) + , skip_(other.skip_) + , x_(other.x_) + , f_(other.f_) + , state_(other.state_) +{ +} + +template +void +basic_parser:: +skip_body() +{ + BOOST_ASSERT(! got_some()); + f_ |= flagSkipBody; +} + +template +bool +basic_parser:: +is_keep_alive() const +{ + BOOST_ASSERT(got_header()); + if(f_ & flagHTTP11) + { + if(f_ & flagConnectionClose) + return false; + } + else + { + if(! (f_ & flagConnectionKeepAlive)) + return false; + } + return (f_ & flagNeedEOF) == 0; +} + +template +template +std::size_t +basic_parser:: +write(ConstBufferSequence const& buffers, + error_code& ec) +{ + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + auto const buffer = maybe_flatten(buffers); + return write(boost::asio::const_buffers_1{ + buffer.data(), buffer.size()}, ec); +} + +template +std::size_t +basic_parser:: +write(boost::asio::const_buffers_1 const& buffer, + error_code& ec) +{ + return do_write(buffer, ec, + std::integral_constant{}); +} + +template +void +basic_parser:: +write_eof(error_code& ec) +{ + BOOST_ASSERT(got_some()); + if(state_ == parse_state::header) + { + ec = error::partial_message; + return; + } + if(f_ & (flagContentLength | flagChunked)) + { + if(state_ != parse_state::complete) + { + ec = error::partial_message; + return; + } + return; + } + do_complete(ec); + if(ec) + return; +} + +template +template +std::size_t +basic_parser:: +copy_body(DynamicBuffer& dynabuf) +{ + // This function not available when isDirect==false + static_assert(isDirect, ""); + + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + BOOST_ASSERT(dynabuf.size() > 0); + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::body_to_eof || + state_ == parse_state::chunk_body); + maybe_do_body_direct(); + switch(state_) + { + case parse_state::body_to_eof: + { + auto const buffers = + impl().on_prepare(dynabuf.size()); + BOOST_ASSERT( + buffer_size(buffers) >= 1 && + buffer_size(buffers) <= + dynabuf.size()); + auto const n = buffer_copy( + buffers, dynabuf.data()); + dynabuf.consume(n); + impl().on_commit(n); + return n; + } + + default: + { + BOOST_ASSERT(len_ > 0); + auto const buffers = + impl().on_prepare( + beast::detail::clamp(len_)); + BOOST_ASSERT( + buffer_size(buffers) >= 1 && + buffer_size(buffers) <= + beast::detail::clamp(len_)); + auto const n = buffer_copy( + buffers, dynabuf.data()); + commit_body(n); + return n; + } + } +} + +template +template +void +basic_parser:: +prepare_body(boost::optional< + MutableBufferSequence>& buffers, std::size_t limit) +{ + // This function not available when isDirect==false + static_assert(isDirect, ""); + + BOOST_ASSERT(limit > 0); + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::body_to_eof || + state_ == parse_state::chunk_body); + maybe_do_body_direct(); + std::size_t n; + switch(state_) + { + case parse_state::body_to_eof: + n = limit; + break; + + default: + BOOST_ASSERT(len_ > 0); + n = beast::detail::clamp(len_, limit); + break; + } + buffers.emplace(impl().on_prepare(n)); +} + +template +void +basic_parser:: +commit_body(std::size_t n) +{ + // This function not available when isDirect==false + static_assert(isDirect, ""); + + BOOST_ASSERT(f_ & flagOnBody); + impl().on_commit(n); + switch(state_) + { + case parse_state::body: + len_ -= n; + if(len_ == 0) + { + // VFALCO This is no good, throwing out ec? + error_code ec; + do_complete(ec); + } + break; + + case parse_state::chunk_body: + len_ -= n; + if(len_ == 0) + state_ = parse_state::chunk_header; + break; + + default: + break; + } +} + +template +void +basic_parser:: +consume_body(error_code& ec) +{ + BOOST_ASSERT( + state_ == parse_state::body || + state_ == parse_state::body_to_eof || + state_ == parse_state::chunk_body); + switch(state_) + { + case parse_state::body: + case parse_state::body_to_eof: + do_complete(ec); + if(ec) + return; + break; + + case parse_state::chunk_body: + len_ = 0; + state_ = parse_state::chunk_header; + break; + + default: + break; + } +} + +template +template +inline +boost::string_ref +basic_parser:: +maybe_flatten( + ConstBufferSequence const& buffers) +{ + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + + auto const it = buffers.begin(); + auto const last = buffers.end(); + if(it == last) + return {nullptr, 0}; + if(std::next(it) == last) + { + // single buffer + auto const b = *it; + return {buffer_cast(b), + buffer_size(b)}; + } + auto const len = buffer_size(buffers); + if(len > buf_len_) + { + // reallocate + buf_.reset(new char[len]); + buf_len_ = len; + } + // flatten + buffer_copy( + buffer(buf_.get(), buf_len_), buffers); + return {buf_.get(), buf_len_}; +} + +template +inline +std::size_t +basic_parser:: +do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::true_type) +{ + BOOST_ASSERT( + state_ == parse_state::header || + state_ == parse_state::chunk_header); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto const p = buffer_cast< + char const*>(*buffer.begin()); + auto const n = + buffer_size(*buffer.begin()); + if(state_ == parse_state::header) + { + if(n > 0) + f_ |= flagGotSome; + return parse_header(p, n, ec); + } + else + { + maybe_do_body_direct(); + return parse_chunk_header(p, n, ec); + } +} + +template +inline +std::size_t +basic_parser:: +do_write(boost::asio::const_buffers_1 const& buffer, + error_code& ec, std::false_type) +{ + BOOST_ASSERT(state_ != parse_state::complete); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto const p = buffer_cast< + char const*>(*buffer.begin()); + auto const n = + buffer_size(*buffer.begin()); + switch(state_) + { + case parse_state::header: + if(n > 0) + f_ |= flagGotSome; + return parse_header(p, n, ec); + + case parse_state::body: + maybe_do_body_indirect(ec); + if(ec) + return 0; + return parse_body(p, n, ec); + + case parse_state::body_to_eof: + maybe_do_body_indirect(ec); + if(ec) + return 0; + return parse_body_to_eof(p, n, ec); + + case parse_state::chunk_header: + maybe_do_body_indirect(ec); + if(ec) + return 0; + return parse_chunk_header(p, n, ec); + + case parse_state::chunk_body: + return parse_chunk_body(p, n, ec); + + case parse_state::complete: + break; + } + return 0; +} + + +template +void +basic_parser:: +parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::true_type) +{ +/* + request-line = method SP request-target SP HTTP-version CRLF + method = token +*/ + auto const method = parse_method(it); + if(method.empty()) + { + ec = error::bad_method; + return; + } + if(*it++ != ' ') + { + ec = error::bad_method; + return; + } + + auto const path = parse_path(it); + if(path.empty()) + { + ec = error::bad_path; + return; + } + if(*it++ != ' ') + { + ec = error::bad_path; + return; + } + + version = parse_version(it); + if(version < 0 || ! parse_crlf(it)) + { + ec = error::bad_version; + return; + } + + impl().on_request( + method, path, version, ec); + if(ec) + return; +} + +template +void +basic_parser:: +parse_startline(char const*& it, + int& version, int& status, + error_code& ec, std::false_type) +{ +/* + status-line = HTTP-version SP status-code SP reason-phrase CRLF + status-code = 3*DIGIT + reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +*/ + version = parse_version(it); + if(version < 0 || *it != ' ') + { + ec = error::bad_version; + return; + } + ++it; + + status = parse_status(it); + if(status < 0 || *it != ' ') + { + ec = error::bad_status; + return; + } + ++it; + + auto const reason = parse_reason(it); + if(! parse_crlf(it)) + { + ec = error::bad_reason; + return; + } + + impl().on_response( + status, reason, version, ec); + if(ec) + return; +} + +template +void +basic_parser:: +parse_fields(char const*& it, + char const* last, error_code& ec) +{ +/* header-field = field-name ":" OWS field-value OWS + + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + ; see Section 3.2.4 +*/ + for(;;) + { + auto term = find_eol(it, last, ec); + if(ec) + return; + BOOST_ASSERT(term); + if(it == term - 2) + { + it = term; + break; + } + auto const name = parse_name(it); + if(name.empty()) + { + ec = error::bad_field; + return; + } + if(*it++ != ':') + { + ec = error::bad_field; + return; + } + if(*term != ' ' && + *term != '\t') + { + auto it2 = term - 2; + detail::skip_ows(it, it2); + detail::skip_ows_rev(it2, it); + auto const value = + make_string(it, it2); + do_field(name, value, ec); + if(ec) + return; + impl().on_field(name, value, ec); + if(ec) + return; + it = term; + } + else + { + // obs-fold + for(;;) + { + auto const it2 = term - 2; + detail::skip_ows(it, it2); + if(it != it2) + break; + it = term; + if(*it != ' ' && *it != '\t') + break; + term = find_eol(it, last, ec); + if(ec) + return; + } + std::string s; + if(it != term) + { + s.append(it, term - 2); + it = term; + for(;;) + { + if(*it != ' ' && *it != '\t') + break; + s.push_back(' '); + detail::skip_ows(it, term - 2); + term = find_eol(it, last, ec); + if(ec) + return; + if(it != term - 2) + s.append(it, term - 2); + it = term; + } + } + boost::string_ref value{ + s.data(), s.size()}; + do_field(name, value, ec); + if(ec) + return; + impl().on_field(name, value, ec); + if(ec) + return; + } + } +} + +template +void +basic_parser:: +do_field( + boost::string_ref const& name, + boost::string_ref const& value, + error_code& ec) +{ + // Connection + if(strieq("connection", name) || + strieq("proxy-connection", name)) + { + auto const list = opt_token_list{value}; + if(! validate_list(list)) + { + // VFALCO Should this be a field specific error? + ec = error::bad_value; + return; + } + for(auto const& s : list) + { + if(strieq("close", s)) + { + f_ |= flagConnectionClose; + continue; + } + + if(strieq("keep-alive", s)) + { + f_ |= flagConnectionKeepAlive; + continue; + } + + if(strieq("upgrade", s)) + { + f_ |= flagConnectionUpgrade; + continue; + } + } + return; + } + + for(auto it = value.begin(); + it != value.end(); ++it) + { + if(! is_text(*it)) + { + ec = error::bad_value; + return; + } + } + + // Content-Length + if(strieq("content-length", name)) + { + if(f_ & flagContentLength) + { + // duplicate + ec = error::bad_content_length; + return; + } + + if(f_ & flagChunked) + { + // conflicting field + ec = error::bad_content_length; + return; + } + + std::uint64_t v; + if(! parse_dec( + value.begin(), value.end(), v)) + { + ec = error::bad_content_length; + return; + } + + len_ = v; + f_ |= flagContentLength; + return; + } + + // Transfer-Encoding + if(strieq("transfer-encoding", name)) + { + if(f_ & flagChunked) + { + // duplicate + ec = error::bad_transfer_encoding; + return; + } + + if(f_ & flagContentLength) + { + // conflicting field + ec = error::bad_transfer_encoding; + return; + } + + auto const v = token_list{value}; + auto const it = std::find_if(v.begin(), v.end(), + [&](typename token_list::value_type const& s) + { + return strieq("chunked", s); + }); + if(it == v.end()) + return; + if(std::next(it) != v.end()) + return; + len_ = 0; + f_ |= flagChunked; + return; + } + + // Upgrade + if(strieq("upgrade", name)) + { + f_ |= flagUpgrade; + ec = {}; + return; + } +} + +template +inline +std::size_t +basic_parser:: +parse_header(char const* p, + std::size_t n, error_code& ec) +{ + if(n < 4) + return 0; + auto const term = find_eom( + p + skip_, p + n, ec); + if(ec) + return 0; + if(! term) + { + skip_ = n - 3; + return 0; + } + + int version; + int status; // ignored for requests + + skip_ = 0; + n = term - p; + parse_startline(p, version, status, ec, + std::integral_constant< + bool, isRequest>{}); + if(ec) + return 0; + if(version >= 11) + f_ |= flagHTTP11; + + parse_fields(p, term, ec); + if(ec) + return 0; + BOOST_ASSERT(p == term); + + do_header(status, + std::integral_constant< + bool, isRequest>{}); + impl().on_header(ec); + if(ec) + return 0; + if(state_ == parse_state::complete) + { + impl().on_complete(ec); + if(ec) + return 0; + } + return n; +} + +template +void +basic_parser:: +do_header(int, std::true_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if(f_ & flagSkipBody) + { + state_ = parse_state::complete; + } + else if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = parse_state::body; + } + else + { + state_ = parse_state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = parse_state::chunk_header; + } + else + { + len_ = 0; + state_ = parse_state::complete; + } +} + +template +void +basic_parser:: +do_header(int status, std::false_type) +{ + // RFC 7230 section 3.3 + // https://tools.ietf.org/html/rfc7230#section-3.3 + + if( (f_ & flagSkipBody) || // e.g. response to a HEAD request + status / 100 == 1 || // 1xx e.g. Continue + status == 204 || // No Content + status == 304) // Not Modified + { + state_ = parse_state::complete; + return; + } + + if(f_ & flagContentLength) + { + if(len_ > 0) + { + f_ |= flagHasBody; + state_ = parse_state::body; + } + else + { + state_ = parse_state::complete; + } + } + else if(f_ & flagChunked) + { + f_ |= flagHasBody; + state_ = parse_state::chunk_header; + } + else + { + f_ |= flagHasBody; + f_ |= flagNeedEOF; + state_ = parse_state::body_to_eof; + } +} + +template +void +basic_parser:: +maybe_do_body_direct() +{ + if(f_ & flagOnBody) + return; + f_ |= flagOnBody; + if(got_content_length()) + impl().on_body(len_); + else + impl().on_body(); +} + +template +void +basic_parser:: +maybe_do_body_indirect(error_code& ec) +{ + if(f_ & flagOnBody) + return; + f_ |= flagOnBody; + if(got_content_length()) + { + impl().on_body(len_, ec); + if(ec) + return; + } + else + { + impl().on_body(ec); + if(ec) + return; + } +} + +template +std::size_t +basic_parser:: +parse_chunk_header(char const* p, + std::size_t n, error_code& ec) +{ +/* + chunked-body = *chunk last-chunk trailer-part CRLF + + chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF + last-chunk = 1*("0") [ chunk-ext ] CRLF + trailer-part = *( header-field CRLF ) + + chunk-size = 1*HEXDIG + chunk-data = 1*OCTET ; a sequence of chunk-size octets + chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token / quoted-string +*/ + + auto const first = p; + auto const last = p + n; + + // Treat the last CRLF in a chunk as + // part of the next chunk, so it can + // be parsed in one call instead of two. + if(f_ & flagExpectCRLF) + { + if(n < 2) + return 0; + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return 0; + } + n -= 2; + } + + char const* term; + + if(! (f_ & flagFinalChunk)) + { + if(n < 2) + return 0; + term = find_eol(p + skip_, last, ec); + if(ec) + return 0; + if(! term) + { + skip_ = n - 1; + return 0; + } + std::uint64_t v; + if(! parse_hex(p, v)) + { + ec = error::bad_chunk; + return 0; + } + if(v != 0) + { + if(*p == ';') + { + // VFALCO We need to parse the chunk + // extension to validate it here. + ext_ = make_string(p, term - 2); + impl().on_chunk(v, ext_, ec); + if(ec) + return 0; + } + else if(p != term - 2) + { + ec = error::bad_chunk; + return 0; + } + p = term; + len_ = v; + skip_ = 0; + f_ |= flagExpectCRLF; + state_ = parse_state::chunk_body; + return p - first; + } + + // This is the offset from the buffer + // to the beginning of the first '\r\n' + x_ = term - 2 - first; + skip_ = x_; + + f_ |= flagFinalChunk; + } + else + { + // We are parsing the value again + // to advance p to the right place. + std::uint64_t v; + auto const result = parse_hex(p, v); + BOOST_ASSERT(result && v == 0); + beast::detail::ignore_unused(result); + beast::detail::ignore_unused(v); + } + + term = find_eom( + first + skip_, last, ec); + if(ec) + return 0; + if(! term) + { + if(n > 3) + skip_ = (last - first) - 3; + return 0; + } + + if(*p == ';') + { + ext_ = make_string(p, first + x_); + impl().on_chunk(0, ext_, ec); + if(ec) + return 0; + p = first + x_; + } + if(! parse_crlf(p)) + { + ec = error::bad_chunk; + return 0; + } + parse_fields(p, term, ec); + if(ec) + return 0; + BOOST_ASSERT(p == term); + + do_complete(ec); + if(ec) + return 0; + return p - first; +} + +template +inline +std::size_t +basic_parser:: +parse_body(char const* p, + std::size_t n, error_code& ec) +{ + n = beast::detail::clamp(len_, n); + body_ = boost::string_ref{p, n}; + impl().on_data(body_, ec); + if(ec) + return 0; + len_ -= n; + if(len_ == 0) + { + do_complete(ec); + if(ec) + return 0; + } + return n; +} + +template +inline +std::size_t +basic_parser:: +parse_body_to_eof(char const* p, + std::size_t n, error_code& ec) +{ + body_ = boost::string_ref{p, n}; + impl().on_data(body_, ec); + if(ec) + return 0; + return n; +} + +template +inline +std::size_t +basic_parser:: +parse_chunk_body(char const* p, + std::size_t n, error_code& ec) +{ + n = beast::detail::clamp(len_, n); + body_ = boost::string_ref{p, n}; + impl().on_data(body_, ec); + if(ec) + return 0; + len_ -= n; + if(len_ == 0) + { + body_ = {}; + state_ = parse_state::chunk_header; + } + return n; +} + +template +void +basic_parser:: +do_complete(error_code& ec) +{ + impl().on_complete(ec); + if(ec) + return; + state_ = parse_state::complete; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/basic_parser_v1.ipp b/include/beast/http/impl/basic_parser_v1.ipp deleted file mode 100644 index 9b1be47d..00000000 --- a/include/beast/http/impl/basic_parser_v1.ipp +++ /dev/null @@ -1,1289 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP -#define BEAST_HTTP_IMPL_BASIC_PARSER_V1_IPP - -#include -#include -#include - -namespace beast { -namespace http { - -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * copyright Joyent, Inc. and other Node contributors. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -/* This code is a modified version of nodejs/http-parser, copyright above: - https://github.com/nodejs/http-parser -*/ - -template -basic_parser_v1:: -basic_parser_v1() - : flags_(0) -{ - init(); -} - -template -template -basic_parser_v1:: -basic_parser_v1(basic_parser_v1< - isRequest, OtherDerived> const& other) - : h_max_(other.h_max_) - , h_left_(other.h_left_) - , b_max_(other.b_max_) - , b_left_(other.b_left_) - , content_length_(other.content_length_) - , cb_(nullptr) - , s_(other.s_) - , fs_(other.fs_) - , pos_(other.pos_) - , http_major_(other.http_major_) - , http_minor_(other.http_minor_) - , status_code_(other.status_code_) - , flags_(other.flags_) - , upgrade_(other.upgrade_) -{ - BOOST_ASSERT(! other.cb_); -} - -template -template -auto -basic_parser_v1:: -operator=(basic_parser_v1< - isRequest, OtherDerived> const& other) -> - basic_parser_v1& -{ - BOOST_ASSERT(! other.cb_); - h_max_ = other.h_max_; - h_left_ = other.h_left_; - b_max_ = other.b_max_; - b_left_ = other.b_left_; - content_length_ = other.content_length_; - cb_ = nullptr; - s_ = other.s_; - fs_ = other.fs_; - pos_ = other.pos_; - http_major_ = other.http_major_; - http_minor_ = other.http_minor_; - status_code_ = other.status_code_; - flags_ = other.flags_; - upgrade_ = other.upgrade_; - flags_ &= ~parse_flag::paused; - return *this; -} - -template -bool -basic_parser_v1:: -keep_alive() const -{ - if(http_major_ >= 1 && http_minor_ >= 1) - { - if(flags_ & parse_flag::connection_close) - return false; - } - else - { - if(! (flags_ & parse_flag::connection_keep_alive)) - return false; - } - return ! needs_eof(); -} - -template -template -typename std::enable_if< - ! std::is_convertible::value, - std::size_t>::type -basic_parser_v1:: -write(ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert(is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - std::size_t used = 0; - for(auto const& buffer : buffers) - { - used += write(buffer, ec); - if(ec) - break; - } - return used; -} - -template -std::size_t -basic_parser_v1:: -write(boost::asio::const_buffer const& buffer, error_code& ec) -{ - using beast::http::detail::is_digit; - using beast::http::detail::is_tchar; - 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; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - - auto const data = buffer_cast(buffer); - auto const size = buffer_size(buffer); - - if(size == 0 && s_ != s_dead) - return 0; - - auto begin = - reinterpret_cast(data); - auto const end = begin + size; - auto p = begin; - auto used = [&] - { - return p - reinterpret_cast(data); - }; - auto err = [&](parse_error ev) - { - ec = ev; - s_ = s_dead; - return used(); - }; - auto errc = [&] - { - s_ = s_dead; - 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_dead: - case s_closed_complete: - return err(parse_error::connection_closed); - break; - - case s_req_start: - flags_ = 0; - cb_ = nullptr; - content_length_ = no_content_length; - s_ = s_req_method0; - goto redo; - - case s_req_method0: - if(! is_tchar(ch)) - return err(parse_error::bad_method); - call_on_start(ec); - if(ec) - return errc(); - BOOST_ASSERT(! cb_); - cb(&self::call_on_method); - s_ = s_req_method; - break; - - case s_req_method: - if(ch == ' ') - { - if(cb(nullptr)) - return errc(); - s_ = s_req_url0; - break; - } - if(! is_tchar(ch)) - return err(parse_error::bad_method); - break; - - case s_req_url0: - { - if(ch == ' ') - return err(parse_error::bad_uri); - // VFALCO TODO Better checking for valid URL characters - if(! is_text(ch)) - return err(parse_error::bad_uri); - BOOST_ASSERT(! cb_); - cb(&self::call_on_uri); - s_ = s_req_url; - break; - } - - case s_req_url: - if(ch == ' ') - { - if(cb(nullptr)) - return errc(); - s_ = s_req_http; - break; - } - // VFALCO TODO Better checking for valid URL characters - if(! is_text(ch)) - return err(parse_error::bad_uri); - break; - - case s_req_http: - 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; - break; - - case s_req_major: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; - s_ = s_req_dot; - break; - - case s_req_dot: - if(ch != '.') - return err(parse_error::bad_version); - s_ = s_req_minor; - break; - - case s_req_minor: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_minor_ = ch - '0'; - s_ = s_req_cr; - break; - - case s_req_cr: - if(ch != '\r') - return err(parse_error::bad_version); - s_ = s_req_lf; - break; - - case s_req_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - call_on_request(ec); - if(ec) - return errc(); - s_ = s_header_name0; - break; - - //---------------------------------------------------------------------- - - case s_res_start: - flags_ = 0; - cb_ = nullptr; - content_length_ = no_content_length; - if(ch != 'H') - return err(parse_error::bad_version); - call_on_start(ec); - if(ec) - return errc(); - s_ = s_res_H; - 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; - break; - - case s_res_major: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_major_ = ch - '0'; - s_ = s_res_dot; - break; - - case s_res_dot: - if(ch != '.') - return err(parse_error::bad_version); - s_ = s_res_minor; - break; - - case s_res_minor: - if(! is_digit(ch)) - return err(parse_error::bad_version); - http_minor_ = ch - '0'; - s_ = s_res_space_1; - break; - - case s_res_space_1: - if(ch != ' ') - return err(parse_error::bad_version); - s_ = s_res_status0; - break; - - case s_res_status0: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = ch - '0'; - s_ = s_res_status1; - break; - - case s_res_status1: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = status_code_ * 10 + ch - '0'; - s_ = s_res_status2; - break; - - case s_res_status2: - if(! is_digit(ch)) - return err(parse_error::bad_status); - status_code_ = status_code_ * 10 + ch - '0'; - s_ = s_res_space_2; - break; - - case s_res_space_2: - if(ch != ' ') - return err(parse_error::bad_status); - s_ = s_res_reason0; - break; - - case s_res_reason0: - if(ch == '\r') - { - s_ = s_res_line_lf; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_reason); - BOOST_ASSERT(! cb_); - cb(&self::call_on_reason); - s_ = s_res_reason; - break; - - case s_res_reason: - if(ch == '\r') - { - if(cb(nullptr)) - return errc(); - s_ = s_res_line_lf; - break; - } - if(! is_text(ch)) - return err(parse_error::bad_reason); - break; - - case s_res_line_lf: - 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 errc(); - s_ = s_header_name0; - goto redo; - - //---------------------------------------------------------------------- - - case s_header_name0: - { - 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; - } - BOOST_ASSERT(! cb_); - cb(&self::call_on_field); - s_ = s_header_name; - break; - } - - case s_header_name: - { - for(; p != end; ++p) - { - ch = *p; - auto c = to_field_char(ch); - if(! c) - break; - switch(fs_) - { - default: - 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(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(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(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_length0; - } - break; - - case h_matching_transfer_encoding: - ++pos_; - if(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(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_length0: - case h_transfer_encoding: - case h_upgrade: - fs_ = h_general; - break; - } - } - if(p == end) - { - --p; - break; - } - if(ch == ':') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_value0; - break; - } - return err(parse_error::bad_field); - } - /* - header-field = field-name ":" OWS field-value OWS - field-name = token - field-value = *( field-content / obs-fold ) - field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - field-vchar = VCHAR / obs-text - obs-fold = CRLF 1*( SP / HTAB ) - ; obsolete line folding - */ - case s_header_value0: - if(ch == ' ' || ch == '\t') - break; - if(ch == '\r') - { - s_ = s_header_value0_lf; - break; - } - if(fs_ == h_content_length0) - { - content_length_ = 0; - flags_ |= parse_flag::contentlength; - } - BOOST_ASSERT(! cb_); - cb(&self::call_on_value); - s_ = s_header_value; - // fall through - - case s_header_value: - { - for(; p != end; ++p) - { - ch = *p; - if(ch == '\r') - { - if(cb(nullptr)) - return errc(); - s_ = s_header_value_lf; - break; - } - auto const c = to_value_char(ch); - if(! c) - return err(parse_error::bad_value); - switch(fs_) - { - case h_general: - default: - break; - - case h_connection: - switch(c) - { - case 'k': - pos_ = 0; - fs_ = h_matching_connection_keep_alive; - break; - case 'c': - pos_ = 0; - fs_ = h_matching_connection_close; - break; - case 'u': - pos_ = 0; - fs_ = h_matching_connection_upgrade; - break; - default: - if(ch == ' ' || ch == '\t' || ch == ',') - break; - if(! is_tchar(ch)) - return err(parse_error::bad_value); - fs_ = h_connection_token; - break; - } - break; - - case h_matching_connection_keep_alive: - ++pos_; - if(c != detail::parser_str::keep_alive[pos_]) - fs_ = h_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(c != detail::parser_str::close[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::close)-2) - fs_ = h_connection_close; - break; - - case h_matching_connection_upgrade: - ++pos_; - if(c != detail::parser_str::upgrade[pos_]) - fs_ = h_connection_token; - else if(pos_ == sizeof(detail::parser_str::upgrade)-2) - fs_ = h_connection_upgrade; - break; - - case h_connection_close: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_close; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_close_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_close_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_close; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_keep_alive: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_keep_alive; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_keep_alive_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_keep_alive_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_keep_alive; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_upgrade: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_upgrade; - } - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_upgrade_ows; - else if(is_tchar(ch)) - fs_ = h_connection_token; - else - return err(parse_error::bad_value); - break; - - case h_connection_upgrade_ows: - if(ch == ',') - { - fs_ = h_connection; - flags_ |= parse_flag::connection_upgrade; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_connection_token: - if(ch == ',') - fs_ = h_connection; - else if(ch == ' ' || ch == '\t') - fs_ = h_connection_token_ows; - else if(! is_tchar(ch)) - return err(parse_error::bad_value); - break; - - case h_connection_token_ows: - if(ch == ',') - { - fs_ = h_connection; - break; - } - if(ch == ' ' || ch == '\t') - break; - return err(parse_error::bad_value); - - case h_content_length0: - if(! is_digit(ch)) - return err(parse_error::bad_content_length); - content_length_ = ch - '0'; - fs_ = h_content_length; - break; - - case h_content_length: - if(ch == ' ' || ch == '\t') - { - fs_ = h_content_length_ows; - 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_content_length_ows: - if(ch != ' ' && ch != '\t') - return err(parse_error::bad_content_length); - break; - - case h_transfer_encoding: - if(c == 'c') - { - pos_ = 0; - fs_ = h_matching_transfer_encoding_chunked; - } - else if(c != ' ' && c != '\t' && c != ',') - { - fs_ = h_matching_transfer_encoding_general; - } - break; - - case h_matching_transfer_encoding_chunked: - ++pos_; - if(c != detail::parser_str::chunked[pos_]) - fs_ = h_matching_transfer_encoding_general; - else if(pos_ == sizeof(detail::parser_str::chunked)-2) - fs_ = h_transfer_encoding_chunked; - break; - - case h_matching_transfer_encoding_general: - if(c == ',') - fs_ = h_transfer_encoding; - break; - - case h_transfer_encoding_chunked: - if(c != ' ' && c != '\t' && c != ',') - fs_ = h_transfer_encoding; - break; - - case h_upgrade: - flags_ |= parse_flag::upgrade; - fs_ = h_general; - break; - } - } - if(p == end) - --p; - break; - } - - case s_header_value0_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_header_value0_almost_done; - break; - - case s_header_value0_almost_done: - if(ch == ' ' || ch == '\t') - { - s_ = s_header_value0; - break; - } - if(fs_ == h_content_length0) - return err(parse_error::bad_content_length); - if(fs_ == h_upgrade) - flags_ |= parse_flag::upgrade; - BOOST_ASSERT(! cb_); - call_on_value(ec, boost::string_ref{"", 0}); - if(ec) - return errc(); - s_ = s_header_name0; - goto redo; - - case s_header_value_lf: - 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') - { - switch(fs_) - { - case h_matching_connection_keep_alive: - case h_matching_connection_close: - case h_matching_connection_upgrade: - fs_ = h_connection_token_ows; - break; - - case h_connection_close: - fs_ = h_connection_close_ows; - break; - - case h_connection_keep_alive: - fs_ = h_connection_keep_alive_ows; - break; - - case h_connection_upgrade: - fs_ = h_connection_upgrade_ows; - break; - - case h_content_length: - fs_ = h_content_length_ows; - break; - - case h_matching_transfer_encoding_chunked: - fs_ = h_matching_transfer_encoding_general; - break; - - default: - break; - } - call_on_value(ec, boost::string_ref(" ", 1)); - s_ = s_header_value_unfold; - break; - } - switch(fs_) - { - case h_connection_keep_alive: - case h_connection_keep_alive_ows: - flags_ |= parse_flag::connection_keep_alive; - break; - case h_connection_close: - case h_connection_close_ows: - flags_ |= parse_flag::connection_close; - break; - - case h_connection_upgrade: - case h_connection_upgrade_ows: - flags_ |= parse_flag::connection_upgrade; - break; - - case h_transfer_encoding_chunked: - case h_transfer_encoding_chunked_ows: - flags_ |= parse_flag::chunked; - break; - - default: - break; - } - s_ = s_header_name0; - goto redo; - - case s_header_value_unfold: - BOOST_ASSERT(! cb_); - cb(&self::call_on_value); - s_ = s_header_value; - 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 errc(); - 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"*/; - call_on_headers(ec); - if(ec) - return errc(); - auto const what = call_on_body_what(ec); - if(ec) - return errc(); - switch(what) - { - case body_what::normal: - break; - case body_what::upgrade: - upgrade_ = true; - // fall through - case body_what::skip: - flags_ |= parse_flag::skipbody; - break; - case body_what::pause: - ++p; - s_ = s_body_pause; - flags_ |= parse_flag::paused; - return used(); - } - s_ = s_headers_done; - goto redo; - } - - case s_body_pause: - { - auto const what = call_on_body_what(ec); - if(ec) - return errc(); - switch(what) - { - case body_what::normal: - break; - case body_what::upgrade: - upgrade_ = true; - // fall through - case body_what::skip: - flags_ |= parse_flag::skipbody; - break; - case body_what::pause: - return used(); - } - --p; - s_ = s_headers_done; - // fall through - } - - case s_headers_done: - { - BOOST_ASSERT(! cb_); - if(ec) - return errc(); - 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_size0; - break; - } - 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: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_body_identity; - // 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_); - BOOST_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: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - s_ = s_body_identity_eof; - // fall through - - case s_body_identity_eof: - p = end - 1; - break; - - case s_chunk_size0: - { - 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_lf; - break; - } - if(ch == ';') - { - s_ = s_chunk_ext_name0; - break; - } - auto v = unhex(ch); - if(v == -1) - 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_ext_name0: - if(! is_tchar(ch)) - return err(parse_error::invalid_ext_name); - s_ = s_chunk_ext_name; - break; - - case s_chunk_ext_name: - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - if(ch == '=') - { - s_ = s_chunk_ext_val; - break; - } - if(ch == ';') - { - s_ = s_chunk_ext_name0; - break; - } - if(! is_tchar(ch)) - return err(parse_error::invalid_ext_name); - break; - - case s_chunk_ext_val: - if(ch == '\r') - { - s_ = s_chunk_size_lf; - break; - } - break; - - case s_chunk_size_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - if(content_length_ == 0) - { - flags_ |= parse_flag::trailing; - s_ = s_header_name0; - break; - } - //call_chunk_header(ec); if(ec) return errc(); - s_ = s_chunk_data0; - break; - - case s_chunk_data0: - BOOST_ASSERT(! cb_); - cb(&self::call_on_body); - 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_cr; - break; - } - - case s_chunk_data_cr: - if(ch != '\r') - return err(parse_error::bad_crlf); - if(cb(nullptr)) - return errc(); - s_ = s_chunk_data_lf; - break; - - case s_chunk_data_lf: - if(ch != '\n') - return err(parse_error::bad_crlf); - s_ = s_chunk_size0; - break; - - case s_complete: - ++p; - if(cb(nullptr)) - return errc(); - call_on_complete(ec); - if(ec) - return errc(); - s_ = s_restart; - return used(); - - case s_restart: - if(keep_alive()) - reset(); - else - s_ = s_dead; - goto redo; - } - } - if(cb_) - { - (this->*cb_)(ec, piece()); - if(ec) - return errc(); - } - return used(); -} - -template -void -basic_parser_v1:: -write_eof(error_code& ec) -{ - switch(s_) - { - case s_restart: - s_ = s_closed_complete; - break; - - case s_dead: - case s_closed_complete: - break; - - case s_body_identity_eof0: - case s_body_identity_eof: - cb_ = nullptr; - call_on_complete(ec); - if(ec) - { - s_ = s_dead; - break; - } - s_ = s_closed_complete; - break; - - default: - s_ = s_dead; - ec = parse_error::short_read; - break; - } -} - -template -void -basic_parser_v1:: -reset() -{ - cb_ = nullptr; - h_left_ = h_max_; - b_left_ = b_max_; - reset(std::integral_constant{}); -} - -template -bool -basic_parser_v1:: -needs_eof(std::true_type) const -{ - return false; -} - -template -bool -basic_parser_v1:: -needs_eof(std::false_type) const -{ - // 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 -} // beast - -#endif diff --git a/include/beast/http/impl/error.ipp b/include/beast/http/impl/error.ipp new file mode 100644 index 00000000..579663ff --- /dev/null +++ b/include/beast/http/impl/error.ipp @@ -0,0 +1,106 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_ERROR_IPP +#define BEAST_HTTP_IMPL_ERROR_IPP + +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +namespace beast { +namespace http { +namespace detail { + +class http_error_category : public error_category +{ +public: + const char* + name() const noexcept override + { + return "http"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + default: + case error::end_of_stream: return "end of stream"; + case error::partial_message: return "partial message"; + case error::buffer_overflow: return "buffer overflow"; + case error::bad_line_ending: return "bad line ending"; + case error::bad_method: return "bad method"; + case error::bad_path: return "bad path"; + case error::bad_version: return "bad version"; + case error::bad_status: return "bad status"; + case error::bad_reason: return "bad reason"; + case error::bad_field: return "bad field"; + case error::bad_value: return "bad value"; + case error::bad_content_length: return "bad Content-Length"; + case error::bad_transfer_encoding: return "bad Transfer-Encoding"; + case error::bad_chunk: return "bad chunk"; + } + } + + error_condition + default_error_condition( + int ev) const noexcept override + { + return error_condition{ev, *this}; + } + + bool + equivalent(int ev, + 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 +error_category const& +get_http_error_category() +{ + static http_error_category const cat{}; + return cat; +} + +} // detail + +inline +error_code +make_error_code(error ev) +{ + return error_code{ + static_cast::type>(ev), + detail::get_http_error_category()}; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/header_parser.ipp b/include/beast/http/impl/header_parser.ipp new file mode 100644 index 00000000..5892564a --- /dev/null +++ b/include/beast/http/impl/header_parser.ipp @@ -0,0 +1,25 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_HEADER_PARSER_IPP +#define BEAST_HTTP_IMPL_HEADER_PARSER_IPP + +namespace beast { +namespace http { + +template +template +header_parser:: +header_parser(Args&&... args) + : h_(std::forward(args)...) +{ +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/message_parser.ipp b/include/beast/http/impl/message_parser.ipp new file mode 100644 index 00000000..276c6a4a --- /dev/null +++ b/include/beast/http/impl/message_parser.ipp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_MESSAGE_PARSER_IPP +#define BEAST_HTTP_IMPL_MESSAGE_PARSER_IPP + +namespace beast { +namespace http { + +template +template +message_parser:: +message_parser(Arg1&& arg1, ArgN&&... argn) + : m_(std::forward(arg1), + std::forward(argn)...) +{ +} + +template +template +message_parser:: +message_parser(header_parser< + isRequest, Fields>&& parser, Args&&... args) + : base_type(std::move(static_cast>&>(parser))) + , m_(parser.release(), std::forward(args)...) +{ +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/parse.ipp b/include/beast/http/impl/parse.ipp deleted file mode 100644 index 17219fc3..00000000 --- a/include/beast/http/impl/parse.ipp +++ /dev/null @@ -1,302 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_PARSE_IPP_HPP -#define BEAST_HTTP_IMPL_PARSE_IPP_HPP - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -template -class parse_op -{ - struct data - { - bool cont; - Stream& s; - DynamicBuffer& db; - Parser& p; - bool got_some = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, Parser& p_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , p(p_) - { - BOOST_ASSERT(! p.complete()); - } - }; - - handler_ptr d_; - -public: - parse_op(parse_op&&) = default; - parse_op(parse_op const&) = default; - - template - parse_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, 0, false); - } - - void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, parse_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, parse_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(parse_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, parse_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -parse_op:: -operator()(error_code ec, std::size_t bytes_transferred, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(d.state != 99) - { - switch(d.state) - { - case 0: - { - // Parse any bytes left over in the buffer - auto const used = - d.p.write(d.db.data(), ec); - if(ec) - { - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - if(used > 0) - { - d.got_some = true; - d.db.consume(used); - } - if(d.p.complete()) - { - // call handler - d.state = 99; - d.s.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - return; - } - // Buffer must be empty, - // otherwise parse should be complete - BOOST_ASSERT(d.db.size() == 0); - d.state = 1; - break; - } - - case 1: - { - // read - d.state = 2; - auto const size = - read_size_helper(d.db, 65536); - BOOST_ASSERT(size > 0); - d.s.async_read_some( - d.db.prepare(size), std::move(*this)); - return; - } - - // got data - case 2: - { - if(ec == boost::asio::error::eof) - { - // If we haven't processed any bytes, - // give the eof to the handler immediately. - if(! d.got_some) - { - // call handler - d.state = 99; - break; - } - // Feed the eof to the parser to complete - // the parse, and call the handler. The - // next call to parse will deliver the eof. - ec = {}; - d.p.write_eof(ec); - BOOST_ASSERT(ec || d.p.complete()); - // call handler - d.state = 99; - break; - } - if(ec) - { - // call handler - d.state = 99; - break; - } - BOOST_ASSERT(bytes_transferred > 0); - d.db.commit(bytes_transferred); - auto const used = d.p.write(d.db.data(), ec); - if(ec) - { - // call handler - d.state = 99; - break; - } - // The parser must either consume - // bytes or generate an error. - BOOST_ASSERT(used > 0); - d.got_some = true; - d.db.consume(used); - if(d.p.complete()) - { - // call handler - d.state = 99; - break; - } - // If the parse is not complete, - // all input must be consumed. - BOOST_ASSERT(used == bytes_transferred); - d.state = 1; - break; - } - } - } - d_.invoke(ec); -} - -} // detail - -//------------------------------------------------------------------------------ - -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - error_code ec; - parse(stream, dynabuf, parser, ec); - if(ec) - throw system_error{ec}; -} - -template -void -parse(SyncReadStream& stream, DynamicBuffer& dynabuf, - Parser& parser, error_code& ec) -{ - static_assert(is_SyncReadStream::value, - "SyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - bool got_some = false; - for(;;) - { - auto used = - parser.write(dynabuf.data(), ec); - if(ec) - return; - dynabuf.consume(used); - if(used > 0) - got_some = true; - if(parser.complete()) - break; - dynabuf.commit(stream.read_some( - dynabuf.prepare(read_size_helper( - dynabuf, 65536)), ec)); - if(ec && ec != boost::asio::error::eof) - return; - if(ec == boost::asio::error::eof) - { - if(! got_some) - return; - // Caller will see eof on next read. - ec = {}; - parser.write_eof(ec); - if(ec) - return; - BOOST_ASSERT(parser.complete()); - break; - } - } -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_parse(AsyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser, ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Parser::value, - "Parser requirements not met"); - beast::async_completion completion{handler}; - detail::parse_op{ - completion.handler, stream, dynabuf, parser}; - return completion.result.get(); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/parse_error.ipp b/include/beast/http/impl/parse_error.ipp deleted file mode 100644 index d7943a0e..00000000 --- a/include/beast/http/impl/parse_error.ipp +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_IMPL_PARSE_ERROR_IPP -#define BEAST_HTTP_IMPL_PARSE_ERROR_IPP - -namespace boost { -namespace system { -template<> -struct is_error_code_enum -{ - static bool const value = true; -}; -} // system -} // boost - -namespace beast { -namespace http { -namespace detail { - -class parse_error_category : public 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-target"; - case parse_error::bad_version: return "bad HTTP-Version"; - case parse_error::bad_crlf: return "missing CRLF"; - case parse_error::bad_status: return "bad status-code"; - case parse_error::bad_reason: return "bad reason-phrase"; - 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::invalid_chunk_size: return "invalid chunk size"; - case parse_error::invalid_ext_name: return "invalid ext name"; - case parse_error::invalid_ext_val: return "invalid ext val"; - case parse_error::header_too_big: return "header size limit exceeded"; - case parse_error::body_too_big: return "body size limit exceeded"; - default: - case parse_error::short_read: return "unexpected end of data"; - } - } - - error_condition - default_error_condition(int ev) const noexcept override - { - return error_condition{ev, *this}; - } - - bool - equivalent(int ev, - 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 -error_category const& -get_parse_error_category() -{ - static parse_error_category const cat{}; - return cat; -} - -} // detail - -inline -error_code -make_error_code(parse_error ev) -{ - return error_code{ - static_cast::type>(ev), - detail::get_parse_error_category()}; -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index 3bcf27c7..e14b5dd9 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -9,306 +9,275 @@ #define BEAST_HTTP_IMPL_READ_IPP_HPP #include -#include -#include -#include +#include +#include +#include #include #include #include #include #include +#include namespace beast { namespace http { namespace detail { -template -class read_header_op +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +inline +std::size_t +read_some_buffer( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) { - using parser_type = - header_parser_v1; - - using message_type = - header; - - struct data + std::size_t bytes_used; + if(dynabuf.size() == 0) + goto do_read; + for(;;) { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , m(m_) + bytes_used = parser.write( + dynabuf.data(), ec); + if(ec) + return 0; + if(bytes_used > 0) + goto do_finish; + do_read: + boost::optional mb; + auto const size = + read_size_helper(dynabuf, 65536); + BOOST_ASSERT(size > 0); + try { + mb.emplace(dynabuf.prepare(size)); } - }; - - handler_ptr d_; - -public: - read_header_op(read_header_op&&) = default; - read_header_op(read_header_op const&) = default; - - template - read_header_op( - DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, read_header_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, read_header_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(read_header_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, read_header_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -read_header_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) + catch(std::length_error const&) { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); - return; - - case 1: - // call handler - d.state = 99; - d.m = d.p.release(); + ec = error::buffer_overflow; + return 0; + } + auto const bytes_transferred = + stream.read_some(*mb, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + bytes_used = 0; + if(parser.got_some()) + { + // caller sees EOF on next read + ec = {}; + parser.write_eof(ec); + if(ec) + return 0; + BOOST_ASSERT(parser.is_complete()); + } break; } + else if(ec) + { + return 0; + } + BOOST_ASSERT(bytes_transferred > 0); + dynabuf.commit(bytes_transferred); } - d_.invoke(ec); +do_finish: + return bytes_used; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +inline +std::size_t +read_some_body( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) +{ + if(dynabuf.size() > 0) + return parser.copy_body(dynabuf); + boost::optional mb; + try + { + parser.prepare_body(mb, 65536); + } + catch(std::length_error const&) + { + ec = error::buffer_overflow; + return 0; + } + auto const bytes_transferred = + stream.read_some(*mb, ec); + if(ec == boost::asio::error::eof) + { + BOOST_ASSERT(bytes_transferred == 0); + // caller sees EOF on next read + ec = {}; + parser.write_eof(ec); + if(ec) + return 0; + BOOST_ASSERT(parser.is_complete()); + } + else if(! ec) + { + parser.commit_body(bytes_transferred); + return 0; + } + return 0; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +inline +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) +{ + switch(parser.state()) + { + case parse_state::header: + case parse_state::chunk_header: + return detail::read_some_buffer( + stream, dynabuf, parser, ec); + + default: + return detail::read_some_body( + stream, dynabuf, parser, ec); + } +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, class Derived> +inline +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) +{ + return detail::read_some_buffer( + stream, dynabuf, parser, ec); } } // detail -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg) +//------------------------------------------------------------------------------ + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); static_assert(is_DynamicBuffer::value, "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); error_code ec; - beast::http::read(stream, dynabuf, msg, ec); + auto const bytes_used = + read_some(stream, dynabuf, parser, ec); + if(ec) + throw system_error{ec}; + return bytes_used; +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) +{ + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + return detail::read_some(stream, dynabuf, parser, ec); +} + +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser) +{ + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + BOOST_ASSERT(! parser.is_complete()); + error_code ec; + read(stream, dynabuf, parser, ec); if(ec) throw system_error{ec}; } -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - error_code& ec) +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); static_assert(is_DynamicBuffer::value, "DynamicBuffer requirements not met"); - header_parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); - if(ec) - return; - BOOST_ASSERT(p.complete()); - m = p.release(); -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& m, - ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - beast::async_completion completion{handler}; - detail::read_header_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); -} - -//------------------------------------------------------------------------------ - -namespace detail { - -template -class read_op -{ - using parser_type = - parser_v1; - - using message_type = - message; - - struct data + BOOST_ASSERT(! parser.is_complete()); + do { - bool cont; - Stream& s; - DynamicBuffer& db; - message_type& m; - parser_type p; - bool started = false; - int state = 0; - - data(Handler& handler, Stream& s_, - DynamicBuffer& sb_, message_type& m_) - : cont(beast_asio_helpers:: - is_continuation(handler)) - , s(s_) - , db(sb_) - , m(m_) - { - } - }; - - handler_ptr d_; - -public: - read_op(read_op&&) = default; - read_op(read_op const&) = default; - - template - read_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(args)...) - { - (*this)(error_code{}, false); - } - - void - operator()(error_code ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, read_op* op) - { - return beast_asio_helpers:: - allocate(size, op->d_.handler()); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, read_op* op) - { - return beast_asio_helpers:: - deallocate(p, size, op->d_.handler()); - } - - friend - bool asio_handler_is_continuation(read_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, read_op* op) - { - return beast_asio_helpers:: - invoke(f, op->d_.handler()); - } -}; - -template -void -read_op:: -operator()(error_code ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - d.state = 1; - async_parse(d.s, d.db, d.p, std::move(*this)); + auto const bytes_used = + read_some(stream, dynabuf, parser, ec); + if(ec) return; - - case 1: - // call handler - d.state = 99; - d.m = d.p.release(); - break; - } + dynabuf.consume(bytes_used); } - d_.invoke(ec); + while(! parser.is_complete()); } -} // detail - -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg) { static_assert(is_SyncReadStream::value, @@ -328,12 +297,16 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, throw system_error{ec}; } -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - error_code& ec) +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + message& msg, + error_code& ec) { static_assert(is_SyncReadStream::value, "SyncReadStream requirements not met"); @@ -346,41 +319,11 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, static_assert(is_Reader>::value, "Reader requirements not met"); - parser_v1 p; - beast::http::parse(stream, dynabuf, p, ec); + message_parser p; + beast::http::read(stream, dynabuf, p, ec); if(ec) return; - BOOST_ASSERT(p.complete()); - m = p.release(); -} - -template -typename async_completion< - ReadHandler, void(error_code)>::result_type -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - message& m, - ReadHandler&& handler) -{ - static_assert(is_AsyncReadStream::value, - "AsyncReadStream requirements not met"); - static_assert(is_DynamicBuffer::value, - "DynamicBuffer requirements not met"); - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader>::value, - "Reader requirements not met"); - beast::async_completion completion{handler}; - detail::read_op{completion.handler, - stream, dynabuf, m}; - return completion.result.get(); + msg = p.release(); } } // http diff --git a/include/beast/http/impl/rfc7230.ipp b/include/beast/http/impl/rfc7230.ipp index 5ad65c28..c31638fc 100644 --- a/include/beast/http/impl/rfc7230.ipp +++ b/include/beast/http/impl/rfc7230.ipp @@ -542,6 +542,26 @@ exists(T const& s) ) != end(); } +template +bool +validate_list(detail::basic_parsed_list< + Policy> const& list) +{ + auto const last = list.end(); + auto it = list.begin(); + if(it.error()) + return false; + while(it != last) + { + ++it; + if(it.error()) + return false; + if(it == last) + break; + } + return true; +} + } // http } // beast diff --git a/include/beast/http/message_parser.hpp b/include/beast/http/message_parser.hpp new file mode 100644 index 00000000..2c3a16e8 --- /dev/null +++ b/include/beast/http/message_parser.hpp @@ -0,0 +1,286 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_MESSAGE_PARSER_HPP +#define BEAST_HTTP_MESSAGE_PARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A parser for producing HTTP/1 messages. + + This class uses the basic HTTP/1 wire format parser to convert + a series of octets into a @ref message. + + @tparam isRequest Indicates whether a request or response + will be parsed. + + @tparam Body The type used to represent the body. + + @tparam Fields The type of container used to represent the fields. + + @note A new instance of the parser is required for each message. +*/ +template +class message_parser + : public basic_parser> +{ + using base_type = basic_parser>; + + using reader_type = typename Body::reader; + + message m_; + boost::optional r_; + +public: + /// The type of message returned by the parser + using value_type = message; + + /// The type of buffer sequence representing the body + using mutable_buffers_type = + typename reader_type::mutable_buffers_type; + + /// Constructor (default) + message_parser() = default; + + /// Copy constructor (disallowed) + message_parser(message_parser const&) = delete; + + /// Copy assignment (disallowed) + message_parser& operator=(message_parser const&) = delete; + + /** Move constructor. + + After the move, the only valid operation + on the moved-from object is destruction. + */ + message_parser(message_parser&& other); + + /** Constructor + + @param args Optional arguments forwarded to the + @ref http::header constructor. + + @note This function participates in overload + resolution only if the first argument is not a + @ref http::header_parser or @ref message_parser. + */ +#if GENERATING_DOCS + template + explicit + msesage_parser(Args&&... args); +#else + template::type, + header_parser>::value && + ! std::is_same::type, message_parser>::value + >::type> + explicit + message_parser(Arg1&& arg1, ArgN&&... argn); +#endif + + /** Construct a message parser from a @ref header_parser. + + @param parser The header parser to construct from. + + @param args Optional arguments forwarded to the message + constructor. + */ + template + explicit + message_parser(header_parser< + isRequest, Fields>&& parser, Args&&... args); + + /** Returns the parsed message. + + Depending on the progress of the parser, portions + of this object may be incomplete. + */ + value_type const& + get() const + { + return m_; + } + + /** Returns the parsed message. + + Depending on the progress of the parser, portions + of this object may be incomplete. + */ + value_type& + get() + { + return m_; + } + + /** Returns ownership of the parsed message. + + Ownership is transferred to the caller. + Depending on the progress of the parser, portions + of this object may be incomplete. + + @par Requires + + @ref value_type is @b MoveConstructible + */ + value_type + release() + { + static_assert(std::is_move_constructible::value, + "MoveConstructible requirements not met"); + return std::move(m_); + } + +private: + friend class basic_parser< + isRequest, Body::reader::is_direct, + message_parser>; + + void + on_request( + boost::string_ref const& method, + boost::string_ref const& path, + int version, error_code&) + { + m_.url = std::string{ + path.data(), path.size()}; + m_.method = std::string{ + method.data(), method.size()}; + m_.version = version; + } + + void + on_response(int status, + boost::string_ref const& reason, + int version, error_code&) + { + m_.status = status; + m_.reason = std::string{ + reason.data(), reason.size()}; + m_.version = version; + } + + void + on_field(boost::string_ref const& name, + boost::string_ref const& value, + error_code&) + { + m_.fields.insert(name, value); + } + + void + on_header(error_code& ec) + { + } + + void + on_body() + { + r_.emplace(m_); + r_->init(); + } + + void + on_body(std::uint64_t content_length) + { + r_.emplace(m_); + r_->init(content_length); + } + + void + on_body(error_code& ec) + { + r_.emplace(m_); + r_->init(ec); + if(ec) + return; + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + r_.emplace(m_); + r_->init(content_length, ec); + if(ec) + return; + } + + void + on_data(boost::string_ref const& s, + error_code& ec) + { + static_assert(! Body::reader::is_direct, ""); + r_->write(s, ec); + } + + mutable_buffers_type + on_prepare(std::size_t n) + { + return r_->prepare(n); + } + + void + on_commit(std::size_t n) + { + r_->commit(n); + } + + void + on_chunk(std::uint64_t, + boost::string_ref const&, + error_code&) + { + } + + void + on_complete(error_code& ec) + { + if(r_) + do_on_complete(ec, + std::integral_constant{}); + } + + void + do_on_complete( + error_code& ec, std::true_type) + { + r_->finish(); + } + + void + do_on_complete( + error_code& ec, std::false_type) + { + r_->finish(ec); + if(ec) + return; + } +}; + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/parse.hpp b/include/beast/http/parse.hpp deleted file mode 100644 index 58065bfe..00000000 --- a/include/beast/http/parse.hpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_PARSE_HPP -#define BEAST_HTTP_PARSE_HPP - -#include -#include -#include - -namespace beast { -namespace http { - -/** Parse an object from a stream. - - This function synchronously reads from a stream and passes - data to the specified parser. The call will block until one - of the following conditions are met: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the object - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @note This algorithm is generic, and not specific to HTTP - messages. It is up to the parser to determine what predicate - defines a complete operation. - - @param stream The stream from which the data is to be read. - The type must support the @b SyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. - - @throws system_error Thrown on failure. -*/ -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser); - -/** Parse an object from a stream. - - This function synchronously reads from a stream and passes - data to the specified parser. The call will block until one - of the following conditions are met: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This function is implemented in terms of one or more calls - to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the object - being parsed. This additional data is stored in the stream - buffer, which may be used in subsequent calls. - - @note This algorithm is generic, and not specific to HTTP - messages. It is up to the parser to determine what predicate - defines a complete operation. - - @param stream The stream from which the data is to be read. - The type must support the @b SyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. - - @param ec Set to the error, if any occurred. -*/ -template -void -parse(SyncReadStream& stream, - DynamicBuffer& dynabuf, Parser& parser, error_code& ec); - -/** Start an asynchronous operation to parse an object from a stream. - - This function is used to asynchronously read from a stream and - pass the data to the specified parser. The function call always - returns immediately. The asynchronous operation will continue - until one of the following conditions is true: - - @li The parser indicates that parsing is complete. - - @li An error occurs in the stream or parser. - - This operation is implemented in terms of one or more calls to - the next layer's `async_read_some` function, and is known as a - composed operation. The program must ensure that the - stream performs no other operations until this operation completes. - The implementation may read additional octets that lie past the - end of the object being parsed. This additional data is stored - in the stream buffer, which may be used in subsequent calls. - - @param stream The stream from which the data is to be read. - The type must support the @b AsyncReadStream concept. - - @param dynabuf A @b DynamicBuffer holding additional bytes - read by the implementation from the stream. This is both - an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser - first. - - @param parser An object meeting the requirements of @b Parser - which will receive the data. This object must remain valid - until the completion handler is invoked. - - @param handler The handler to be called when the request - completes. Copies will be made of the handler as required. - The equivalent function signature of the handler must be: - @code void handler( - error_code const& error // result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using `boost::asio::io_service::post`. -*/ -template -#if BEAST_DOXYGEN -void_or_deduced -#else -typename async_completion< - ReadHandler, void(error_code)>::result_type -#endif -async_parse(AsyncReadStream& stream, DynamicBuffer& dynabuf, - Parser& parser, ReadHandler&& handler); - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/parse_error.hpp b/include/beast/http/parse_error.hpp deleted file mode 100644 index 7b0dc08c..00000000 --- a/include/beast/http/parse_error.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_PARSE_ERROR_HPP -#define BEAST_HTTP_PARSE_ERROR_HPP - -#include -#include - -namespace beast { -namespace http { - -enum class parse_error -{ - connection_closed = 1, - - bad_method, - bad_uri, - bad_version, - bad_crlf, - - bad_status, - bad_reason, - - bad_field, - bad_value, - bad_content_length, - illegal_content_length, - - invalid_chunk_size, - invalid_ext_name, - invalid_ext_val, - - header_too_big, - body_too_big, - short_read -}; - -} // http -} // beast - -#include - -#endif diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp deleted file mode 100644 index 86c626b8..00000000 --- a/include/beast/http/parser_v1.hpp +++ /dev/null @@ -1,339 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_PARSER_V1_HPP -#define BEAST_HTTP_PARSER_V1_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -/** Skip body option. - - The options controls whether or not the parser expects to see a - HTTP body, regardless of the presence or absence of certain fields - such as Content-Length. - - Depending on the request, some responses do not carry a body. - For example, a 200 response to a CONNECT request from a tunneling - proxy. In these cases, callers use the @ref skip_body option to - inform the parser that no body is expected. The parser will consider - the message complete after the header has been received. - - Example: - @code - parser_v1 p; - p.set_option(skip_body{true}); - @endcode - - @note Objects of this type are passed to @ref parser_v1::set_option. -*/ -struct skip_body -{ - bool value; - - explicit - skip_body(bool v) - : value(v) - { - } -}; - -/** A parser for producing HTTP/1 messages. - - This class uses the basic HTTP/1 wire format parser to convert - a series of octets into a `message`. - - @note A new instance of the parser is required for each message. -*/ -template -class parser_v1 - : public basic_parser_v1> - , private std::conditional::type -{ -public: - /// The type of message this parser produces. - using message_type = - message; - -private: - using reader = - typename message_type::body_type::reader; - - static_assert(is_Body::value, - "Body requirements not met"); - static_assert(has_reader::value, - "Body has no reader"); - static_assert(is_Reader::value, - "Reader requirements not met"); - - std::string field_; - std::string value_; - message_type m_; - boost::optional r_; - std::uint8_t skip_body_ = 0; - bool flush_ = false; - -public: - /// Default constructor - parser_v1() = default; - - /// Move constructor - parser_v1(parser_v1&&) = default; - - /// Copy constructor (disallowed) - parser_v1(parser_v1 const&) = delete; - - /// Move assignment (disallowed) - parser_v1& operator=(parser_v1&&) = delete; - - /// Copy assignment (disallowed) - parser_v1& operator=(parser_v1 const&) = delete; - - /** Construct the parser. - - @param args Forwarded to the message constructor. - - @note This function participates in overload resolution only - if the first argument is not a parser or fields parser. - */ -#if BEAST_DOXYGEN - template - explicit - parser_v1(Args&&... args); -#else - template::type, - header_parser_v1>::value && - ! std::is_same::type, parser_v1>::value - >::type> - explicit - parser_v1(Arg1&& arg1, ArgN&&... argn) - : m_(std::forward(arg1), - std::forward(argn)...) - { - } -#endif - - /** Construct the parser from a fields parser. - - @param parser The fields parser to construct from. - @param args Forwarded to the message body constructor. - */ - template - explicit - parser_v1(header_parser_v1& parser, - Args&&... args) - : m_(parser.release(), std::forward(args)...) - { - static_cast>&>(*this) = parser; - } - - /// Set the skip body option. - void - set_option(skip_body const& o) - { - skip_body_ = o.value ? 1 : 0; - } - - /** Returns the parsed message. - - Only valid if @ref complete would return `true`. - */ - message_type const& - get() const - { - return m_; - } - - /** Returns the parsed message. - - Only valid if @ref complete would return `true`. - */ - message_type& - get() - { - return m_; - } - - /** Returns ownership of the parsed message. - - Ownership is transferred to the caller. Only - valid if @ref complete would return `true`. - - Requires: - `message` is @b MoveConstructible - */ - message_type - release() - { - static_assert(std::is_move_constructible::value, - "MoveConstructible requirements not met"); - return std::move(m_); - } - -private: - friend class basic_parser_v1; - - void flush() - { - if(! flush_) - return; - flush_ = false; - BOOST_ASSERT(! field_.empty()); - m_.fields.insert(field_, value_); - field_.clear(); - value_.clear(); - } - - void on_start(error_code&) - { - } - - void on_method(boost::string_ref const& s, error_code&) - { - this->method_.append(s.data(), s.size()); - } - - void on_uri(boost::string_ref const& s, error_code&) - { - this->uri_.append(s.data(), s.size()); - } - - void on_reason(boost::string_ref const& s, error_code&) - { - this->reason_.append(s.data(), s.size()); - } - - void on_request_or_response(std::true_type) - { - m_.method = std::move(this->method_); - m_.url = std::move(this->uri_); - } - - void on_request_or_response(std::false_type) - { - m_.status = this->status_code(); - m_.reason = std::move(this->reason_); - } - - void on_request(error_code&) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_response(error_code&) - { - on_request_or_response( - std::integral_constant{}); - } - - void on_field(boost::string_ref const& s, error_code&) - { - flush(); - field_.append(s.data(), s.size()); - } - - void on_value(boost::string_ref const& s, error_code&) - { - value_.append(s.data(), s.size()); - flush_ = true; - } - - void - on_header(std::uint64_t, error_code&) - { - flush(); - m_.version = 10 * this->http_major() + this->http_minor(); - } - - body_what - on_body_what(std::uint64_t, error_code& ec) - { - if(skip_body_) - return body_what::skip; - r_.emplace(m_); - r_->init(ec); - return body_what::normal; - } - - void on_body(boost::string_ref const& s, error_code& ec) - { - r_->write(s.data(), s.size(), ec); - } - - void on_complete(error_code&) - { - } -}; - -/** Create a new parser from a fields parser. - - Associates a Body type with a fields parser, and returns - a new parser which parses a complete message object - containing the original message fields and a new body - of the specified body type. - - This function allows HTTP messages to be parsed in two stages. - First, the fields are parsed and control is returned. Then, - the caller can choose at run-time, the type of Body to - associate with the message. And finally, complete the parse - in a second call. - - @param parser The fields parser to construct from. Ownership - of the message fields in the fields parser is transferred - as if by call to @ref header_parser_v1::release. - - @param args Forwarded to the body constructor of the message - in the new parser. - - @return A parser for a message with the specified @b Body type. - - @par Example - @code - headers_parser ph; - ... - auto p = with_body(ph); - ... - message m = p.release(); - @endcode -*/ -template -parser_v1 -with_body(header_parser_v1& parser, - Args&&... args) -{ - return parser_v1( - parser, std::forward(args)...); -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/read.hpp b/include/beast/http/read.hpp index 56cc4324..e0823da5 100644 --- a/include/beast/http/read.hpp +++ b/include/beast/http/read.hpp @@ -11,135 +11,298 @@ #include #include #include +#include #include namespace beast { namespace http { -/** Read a HTTP/1 header from a stream. +/** Read some HTTP/1 message data from a stream. - This function is used to synchronously read a header - from a stream. The call blocks until one of the following - conditions is true: + This function synchronously advances the state of the + parser using the provided dynamic buffer and reading + from the input stream as needed. The call will block + until one of the following conditions is true: - @li An entire header is read in. + @li When expecting a message header, and the complete + header is received. + + @li When expecting a chunk header, and the complete + chunk header is received. + + @li When expecting body octets, one or more body octets + are received. @li An error occurs in the stream or parser. This function is implemented in terms of one or more calls to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. - - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + buffer, which may be used in subsequent calls. @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param dynabuf A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment - or move assignment. + @param parser The parser to use. + + @return The number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling + `consume` on the dynamic buffer. @throws system_error Thrown on failure. */ -template -void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg); +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser); -/** Read a HTTP/1 header from a stream. +/** Read some HTTP/1 message data from a stream. - This function is used to synchronously read a header - from a stream. The call blocks until one of the following - conditions is true: + This function synchronously advances the state of the + parser using the provided dynamic buffer and reading + from the input stream as needed. The call will block + until one of the following conditions is true: - @li An entire header is read in. + @li When expecting a message header, and the complete + header is received. + + @li When expecting a chunk header, and the complete + chunk header is received. + + @li When expecting body octets, one or more body octets + are received. @li An error occurs in the stream or parser. This function is implemented in terms of one or more calls to the stream's `read_some` function. The implementation may - read additional octets that lie past the end of the message - fields being parsed. This additional data is stored in the - stream buffer, which may be used in subsequent calls. - - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + buffer, which may be used in subsequent calls. @param stream The stream from which the data is to be read. - The type must support the @b `SyncReadStream` concept. + The type must support the @b SyncReadStream concept. - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param dynabuf A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment - or move assignment. + @param parser The parser to use. + + @param ec Set to the error, if any occurred. + + @return The number of bytes processed from the dynamic + buffer. The caller should remove these bytes by calling + `consume` on the dynamic buffer. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +std::size_t +read_some( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec); + +/** Start an asynchronous operation to read some HTTP/1 message data from a stream. + + This function asynchronously advances the state of the + parser using the provided dynamic buffer and reading from + the input stream as needed. The function call always + returns immediately. The asynchronous operation will + continue until one of the following conditions is true: + + @li When expecting a message header, and the complete + header is received. + + @li When expecting a chunk header, and the complete + chunk header is received. + + @li When expecting body octets, one or more body octets + are received. + + @li An error occurs in the stream or parser. + + This operation is implemented in terms of zero or more calls to + the next layer's `async_read_some` function, and is known as a + composed operation. The program must ensure that the + stream performs no other operations until this operation completes. + The implementation may read additional octets that lie past the + end of the object being parsed. This additional data is stored + in the stream buffer, which may be used in subsequent calls. + + The completion handler will be called with the number of bytes + processed from the dynamic buffer. The caller should remove + these bytes by calling `consume` on the dynamic buffer. + + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. + + @param dynabuf A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. + The equivalent function signature of the handler must be: + @code void handler( + error_code const& error, // result of operation + std::size_t bytes_used // the number of bytes to consume + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> +#if GENERATING_DOCS +void_or_deduced +#else +typename async_completion< + ReadHandler, void(error_code, std::size_t)>::result_type +#endif +async_read_some( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler); + +//------------------------------------------------------------------------------ + +/** Read an HTTP/1 message from a stream. + + This function synchronously reads from a stream and passes + data to the specified parser. The call will block until one + of the following conditions is true: + + @li The parser indicates no more additional data is needed. + + @li An error occurs in the stream or parser. + + This function is implemented in terms of one or more calls + to the stream's `read_some` function. The implementation may + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + buffer, which may be used in subsequent calls. + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param dynabuf A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. + + @throws system_error Thrown on failure. +*/ +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> +void +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser); + +/** Read an HTTP/1 message from a stream. + + This function synchronously reads from a stream and passes + data to the specified parser. The call will block until one + of the following conditions is true: + + @li The parser indicates that no more data is needed. + + @li An error occurs in the stream or parser. + + This function is implemented in terms of one or more calls + to the stream's `read_some` function. The implementation may + read additional octets that lie past the end of the object + being parsed. This additional data is stored in the dynamic + buffer, which may be used in subsequent calls. + + @param stream The stream from which the data is to be read. + The type must support the @b SyncReadStream concept. + + @param dynabuf A @b DynamicBuffer holding additional bytes + read by the implementation from the stream. This is both + an input and an output parameter; on entry, any data in the + dynamic buffer's input sequence will be given to the parser + first. + + @param parser The parser to use. @param ec Set to the error, if any occurred. */ -template +template< + class SyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived> void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - error_code& ec); +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + error_code& ec); -/** Read a HTTP/1 header asynchronously from a stream. +/** Start an asynchronous operation to read an HTTP/1 message from a stream. - This function is used to asynchronously read a header from - a stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: + This function is used to asynchronously read from a stream and + pass the data to the specified parser. The function call always + returns immediately. The asynchronous operation will continue + until one of the following conditions is true: - @li An entire header is read in. + @li The parser indicates that no more data is needed. @li An error occurs in the stream or parser. This operation is implemented in terms of one or more calls to - the stream's `async_read_some` function, and is known as a + the next layer's `async_read_some` function, and is known as a composed operation. The program must ensure that the stream performs no other operations until this operation completes. The implementation may read additional octets that lie past the - end of the message fields being parsed. This additional data is - stored in the stream buffer, which may be used in subsequent calls. + end of the object being parsed. This additional data is stored + in the stream buffer, which may be used in subsequent calls. - If the message corresponding to the header being received - contains a message body, it is the callers responsibility - to cause the body to be read in before attempting to read - the next message. + @param stream The stream from which the data is to be read. + The type must support the @b AsyncReadStream concept. - @param stream The stream to read the message from. - The type must support the @b `AsyncReadStream` concept. - - @param dynabuf A @b `DynamicBuffer` holding additional bytes + @param dynabuf A @b DynamicBuffer holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. - @param msg An object used to store the header. Any contents - will be overwritten. The type must support copy assignment or - move assignment. The object must remain valid at least until - the completion handler is called; ownership is not transferred. + @param parser The parser to use. - @param handler The handler to be called when the operation + @param handler The handler to be called when the request completes. Copies will be made of the handler as required. The equivalent function signature of the handler must be: @code void handler( @@ -150,20 +313,24 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ -template +template< + class AsyncReadStream, + class DynamicBuffer, + bool isRequest, bool isDirect, class Derived, + class ReadHandler> #if BEAST_DOXYGEN void_or_deduced #else typename async_completion< ReadHandler, void(error_code)>::result_type #endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, - header& msg, - ReadHandler&& handler); +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, + basic_parser& parser, + ReadHandler&& handler); -/** Read a HTTP/1 message from a stream. +/** Read an HTTP/1 message from a stream. This function is used to synchronously read a message from a stream. The call blocks until one of the following conditions @@ -176,7 +343,7 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, This function is implemented in terms of one or more calls to the stream's `read_some` function. The implementation may read additional octets that lie past the end of the message - being parsed. This additional data is stored in the stream + being parsed. This additional data is stored in the dynamic buffer, which may be used in subsequent calls. @param stream The stream from which the data is to be read. @@ -185,7 +352,7 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. @param msg An object used to store the message. Any @@ -194,10 +361,14 @@ async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, @throws system_error Thrown on failure. */ -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg); /** Read a HTTP/1 message from a stream. @@ -213,7 +384,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, This function is implemented in terms of one or more calls to the stream's `read_some` function. The implementation may read additional octets that lie past the end of the message - being parsed. This additional data is stored in the stream + being parsed. This additional data is stored in the dynamic buffer, which may be used in subsequent calls. @param stream The stream from which the data is to be read. @@ -222,7 +393,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. @param msg An object used to store the message. Any @@ -231,12 +402,16 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @param ec Set to the error, if any occurred. */ -template void -read(SyncReadStream& stream, DynamicBuffer& dynabuf, +read( + SyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg, - error_code& ec); + error_code& ec); /** Read a HTTP/1 message asynchronously from a stream. @@ -255,7 +430,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, stream performs no other operations until this operation completes. The implementation may read additional octets that lie past the end of the message being parsed. This additional data is stored - in the stream buffer, which may be used in subsequent calls. + in the dynamic buffer, which may be used in subsequent calls. @param stream The stream to read the message from. The type must support the @b `AsyncReadStream` concept. @@ -263,7 +438,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, @param dynabuf A @b `DynamicBuffer` holding additional bytes read by the implementation from the stream. This is both an input and an output parameter; on entry, any data in the - stream buffer's input sequence will be given to the parser + dynamic buffer's input sequence will be given to the parser first. @param msg An object used to store the header. Any contents @@ -282,22 +457,27 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ -template + class ReadHandler> #if BEAST_DOXYGEN void_or_deduced #else typename async_completion< ReadHandler, void(error_code)>::result_type #endif -async_read(AsyncReadStream& stream, DynamicBuffer& dynabuf, +async_read( + AsyncReadStream& stream, + DynamicBuffer& dynabuf, message& msg, - ReadHandler&& handler); + ReadHandler&& handler); } // http } // beast +#include #include #endif diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp index 4eca51e1..aa303120 100644 --- a/include/beast/http/rfc7230.hpp +++ b/include/beast/http/rfc7230.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace beast { namespace http { @@ -276,6 +277,46 @@ public: exists(T const& s); }; +/** A list of tokens in a comma separated HTTP field value. + + This container allows iteration of a list of items in a + header field value. The input is a comma separated list of + tokens. + + If a parsing error is encountered while iterating the string, + the behavior of the container will be as if a string containing + only characters up to but excluding the first invalid character + was used to construct the list. + + @par BNF + @code + token-list = *( "," OWS ) token *( OWS "," [ OWS token ] ) + @endcode + + To use this class, construct with the string to be parsed and + then use `begin` and `end`, or range-for to iterate each item: + + @par Example + @code + for(auto const& token : token_list{"apple, pear, banana"}) + std::cout << token << "\n"; + @endcode +*/ +using opt_token_list = + detail::basic_parsed_list< + detail::opt_token_list_policy>; + +/** Returns `true` if a parsed list is parsed without errors. + + This function iterates a single pass through a parsed list + and returns `true` if there were no parsing errors, else + returns `false`. +*/ +template +bool +validate_list(detail::basic_parsed_list< + Policy> const& list); + } // http } // beast diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp index 7f2e63a1..34274864 100644 --- a/include/beast/http/string_body.hpp +++ b/include/beast/http/string_body.hpp @@ -34,29 +34,58 @@ private: class reader { - value_type& s_; + value_type& body_; + std::size_t len_ = 0; public: + static bool constexpr is_direct = true; + + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + template explicit reader(message& m) noexcept - : s_(m.body) + string_body, Fields>& m) + : body_(m.body) { } void - init(error_code&) noexcept + init() { } void - write(void const* data, - std::size_t size, error_code&) noexcept + init(std::uint64_t content_length) { - auto const n = s_.size(); - s_.resize(n + size); - std::memcpy(&s_[n], data, size); + if(content_length > + (std::numeric_limits::max)()) + throw std::length_error{ + "Content-Length overflow"}; + body_.reserve(static_cast< + std::size_t>(content_length)); + } + + mutable_buffers_type + prepare(std::size_t n) + { + body_.resize(len_ + n); + return {&body_[len_], n}; + } + + void + commit(std::size_t n) + { + if(body_.size() > len_ + n) + body_.resize(len_ + n); + len_ = body_.size(); + } + + void + finish() + { + body_.resize(len_); } }; diff --git a/include/beast/websocket/detail/decorator.hpp b/include/beast/websocket/detail/decorator.hpp index 558ff9e3..bb40e58f 100644 --- a/include/beast/websocket/detail/decorator.hpp +++ b/include/beast/websocket/detail/decorator.hpp @@ -8,7 +8,6 @@ #ifndef BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP #define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP -#include #include #include #include @@ -19,9 +18,9 @@ namespace beast { namespace websocket { namespace detail { -using request_type = http::request; +using request_type = http::request_header; -using response_type = http::response; +using response_type = http::response_header; struct abstract_decorator { diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp index 7ba05260..5b5aaa99 100644 --- a/include/beast/websocket/detail/stream_base.hpp +++ b/include/beast/websocket/detail/stream_base.hpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/include/beast/websocket/impl/accept.ipp b/include/beast/websocket/impl/accept.ipp index 877be120..259b3ab8 100644 --- a/include/beast/websocket/impl/accept.ipp +++ b/include/beast/websocket/impl/accept.ipp @@ -9,7 +9,7 @@ #define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #include -#include +#include #include #include #include @@ -35,13 +35,13 @@ class stream::response_op { bool cont; stream& ws; - http::response res; + http::response_header res; error_code final_ec; int state = 0; - template + template data(Handler&, stream& ws_, - http::request const& req, + http::header const& req, bool cont_) : cont(cont_) , ws(ws_) @@ -151,7 +151,7 @@ class stream::accept_op { bool cont; stream& ws; - http::request req; + http::header_parser p; int state = 0; template @@ -190,8 +190,8 @@ public: (*this)(ec, 0); } - void operator()(error_code const& ec, - std::size_t bytes_transferred, bool again = true); + void operator()(error_code ec, + std::size_t bytes_used, bool again = true); friend void* asio_handler_allocate( @@ -228,36 +228,39 @@ template template void stream::accept_op:: -operator()(error_code const& ec, - std::size_t bytes_transferred, bool again) +operator()(error_code ec, + std::size_t bytes_used, bool again) { - beast::detail::ignore_unused(bytes_transferred); auto& d = *d_; d.cont = d.cont || again; - while(! ec && d.state != 99) + if(ec) + goto upcall; + switch(d.state) { - switch(d.state) - { - case 0: - // read message - d.state = 1; - http::async_read(d.ws.next_layer(), - d.ws.stream_.buffer(), d.req, - std::move(*this)); - return; + case 0: + // read message + d.state = 1; + http::async_read_some(d.ws.next_layer(), + d.ws.stream_.buffer(), d.p, + std::move(*this)); + return; - // got message - case 1: - { - // respond to request - auto& ws = d.ws; - auto req = std::move(d.req); - response_op{ - d_.release_handler(), ws, req, true}; - return; - } - } + case 1: + { + BOOST_ASSERT(d.p.got_header()); + d.ws.stream_.buffer().consume(bytes_used); + // Arguments from our state must be + // moved to the stack before releasing + // the handler. + auto& ws = d.ws; + auto m = d.p.release(); + response_op{ + d_.release_handler(), + ws, std::move(m), true}; + return; } + } +upcall: d_.invoke(ec); } @@ -295,11 +298,11 @@ async_accept(ConstBufferSequence const& bs, AcceptHandler&& handler) } template -template +template typename async_completion< AcceptHandler, void(error_code)>::result_type stream:: -async_accept(http::request const& req, +async_accept(http::header const& req, AcceptHandler&& handler) { static_assert(is_AsyncStream::value, @@ -372,18 +375,21 @@ accept(ConstBufferSequence const& buffers, error_code& ec) stream_.buffer().commit(buffer_copy( stream_.buffer().prepare( buffer_size(buffers)), buffers)); - http::request m; - http::read(next_layer(), stream_.buffer(), m, ec); + http::header_parser p; + auto const bytes_used = http::read_some( + next_layer(), stream_.buffer(), p, ec); if(ec) return; - accept(m, ec); + BOOST_ASSERT(p.got_header()); + stream_.buffer().consume(bytes_used); + accept(p.get(), ec); } template -template +template void stream:: -accept(http::request const& request) +accept(http::header const& request) { static_assert(is_SyncStream::value, "SyncStream requirements not met"); @@ -394,10 +400,10 @@ accept(http::request const& request) } template -template +template void stream:: -accept(http::request const& req, +accept(http::header const& req, error_code& ec) { static_assert(is_SyncStream::value, @@ -418,8 +424,6 @@ accept(http::request const& req, open(detail::role_type::server); } -//------------------------------------------------------------------------------ - } // websocket } // beast diff --git a/include/beast/websocket/impl/handshake.ipp b/include/beast/websocket/impl/handshake.ipp index 11ea5618..b788e4ed 100644 --- a/include/beast/websocket/impl/handshake.ipp +++ b/include/beast/websocket/impl/handshake.ipp @@ -8,9 +8,9 @@ #ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP -#include #include #include +#include #include #include #include @@ -34,8 +34,8 @@ class stream::handshake_op bool cont; stream& ws; std::string key; - http::request req; - http::response resp; + http::request_header req; + http::response res; int state = 0; data(Handler& handler, stream& ws_, @@ -129,14 +129,14 @@ operator()(error_code ec, bool again) // read http response d.state = 2; http::async_read(d.ws.next_layer(), - d.ws.stream_.buffer(), d.resp, + d.ws.stream_.buffer(), d.res, std::move(*this)); return; // got response case 2: { - d.ws.do_response(d.resp, d.key, ec); + d.ws.do_response(d.res, d.key, ec); // call handler d.state = 99; break; @@ -196,7 +196,7 @@ handshake(boost::string_ref const& host, } if(ec) return; - http::response res; + http::response res; http::read(next_layer(), stream_.buffer(), res, ec); if(ec) return; diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index fa932ffc..6e7b8ea8 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -83,17 +83,18 @@ reset() } template -http::request +http::request_header stream:: build_request(boost::string_ref const& host, boost::string_ref const& resource, std::string& key) { - http::request req; + http::request_header req; req.url = { resource.data(), resource.size() }; req.version = 11; req.method = "GET"; req.fields.insert("Host", host); req.fields.insert("Upgrade", "websocket"); + req.fields.insert("Connection", "upgrade"); key = detail::make_sec_ws_key(maskgen_); req.fields.insert("Sec-WebSocket-Key", key); req.fields.insert("Sec-WebSocket-Version", "13"); @@ -113,15 +114,13 @@ build_request(boost::string_ref const& host, req.fields, config); } d_(req); - http::prepare(req, http::connection::upgrade); return req; } template -template -http::response +http::response_header stream:: -build_response(http::request const& req) +build_response(http::request_header const& req) { auto err = [&](std::string const& text) @@ -170,7 +169,7 @@ build_response(http::request const& req) return res; } } - http::response res; + http::response_header res; { detail::pmd_offer offer; detail::pmd_offer unused; @@ -182,23 +181,22 @@ build_response(http::request const& req) res.reason = http::reason_string(res.status); res.version = req.version; res.fields.insert("Upgrade", "websocket"); + res.fields.insert("Connection", "upgrade"); { auto const key = req.fields["Sec-WebSocket-Key"]; res.fields.insert("Sec-WebSocket-Accept", detail::make_sec_ws_accept(key)); } - res.fields.replace("Server", "Beast.WSProto"); + res.fields.replace("Server", "Beast.WebSocket"); d_(res); - http::prepare(res, http::connection::upgrade); return res; } template -template void stream:: -do_response(http::response const& res, +do_response(http::response_header const& res, boost::string_ref const& key, error_code& ec) { // VFALCO Review these error codes diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index ccffa16e..47f48bef 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -597,9 +597,9 @@ public: @throws system_error Thrown on failure. */ // VFALCO TODO This should also take a DynamicBuffer with any leftover bytes. - template + template void - accept(http::request const& request); + accept(http::header const& request); /** Respond to a WebSocket HTTP Upgrade request @@ -629,10 +629,9 @@ public: @param ec Set to indicate what error occurred, if any. */ - template + template void - accept(http::request const& request, - error_code& ec); + accept(http::header const& request, error_code& ec); /** Start responding to a WebSocket HTTP Upgrade request. @@ -675,15 +674,15 @@ public: this function. Invocation of the handler will be performed in a manner equivalent to using `boost::asio::io_service::post`. */ - template + template #if BEAST_DOXYGEN void_or_deduced #else typename async_completion< AcceptHandler, void(error_code)>::result_type #endif - async_accept(http::request const& request, - AcceptHandler&& handler); + async_accept(http::header const& request, AcceptHandler&& handler); /** Send a HTTP WebSocket Upgrade request and receive the response. @@ -1674,18 +1673,16 @@ private: void reset(); - http::request + http::request_header build_request(boost::string_ref const& host, boost::string_ref const& resource, std::string& key); - template - http::response - build_response(http::request const& req); + http::response_header + build_response(http::request_header const& req); - template void - do_response(http::response const& resp, + do_response(http::response_header const& resp, boost::string_ref const& key, error_code& ec); }; diff --git a/include/beast/zlib/deflate_stream.hpp b/include/beast/zlib/deflate_stream.hpp index aae355d0..70552263 100644 --- a/include/beast/zlib/deflate_stream.hpp +++ b/include/beast/zlib/deflate_stream.hpp @@ -136,7 +136,7 @@ public: of bytes needed to store the result of compressing a block of data based on the current compression level and strategy. - @param bytes The size of the uncompressed data. + @param sourceLen The size of the uncompressed data. @return The maximum number of resulting compressed bytes. */ diff --git a/include/beast/zlib/inflate_stream.hpp b/include/beast/zlib/inflate_stream.hpp index e6c42021..bd818335 100644 --- a/include/beast/zlib/inflate_stream.hpp +++ b/include/beast/zlib/inflate_stream.hpp @@ -181,7 +181,7 @@ public: `Flush::trees` is used, and when `write` avoids the allocation of memory for a sliding window when `Flush::finsih` is used. - If a preset dictionary is needed after this call (see @ref dictionary below), + If a preset dictionary is needed after this call, `write` sets `zs.adler` to the Adler-32 checksum of the dictionary chosen by the compressor and returns `error::need_dictionary`; otherwise it sets `zs.adler` to the Adler-32 checksum of all output produced so far (that is, diff --git a/test/Jamfile b/test/Jamfile index cae47088..75f0d2fc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -49,15 +49,14 @@ unit-test http-tests : ../extras/beast/unit_test/main.cpp http/basic_dynabuf_body.cpp http/basic_fields.cpp - http/basic_parser_v1.cpp + http/basic_parser.cpp http/concepts.cpp - http/empty_body.cpp + http/design.cpp + http/error.cpp http/fields.cpp - http/header_parser_v1.cpp + http/header_parser.cpp http/message.cpp - http/parse.cpp - http/parse_error.cpp - http/parser_v1.cpp + http/message_parser.cpp http/read.cpp http/reason.cpp http/rfc7230.cpp diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 5505e37d..c262eddd 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -8,19 +8,18 @@ add_executable (http-tests ${BEAST_INCLUDES} ${EXTRAS_INCLUDES} message_fuzz.hpp - fail_parser.hpp + test_parser.hpp ../../extras/beast/unit_test/main.cpp basic_dynabuf_body.cpp basic_fields.cpp - basic_parser_v1.cpp + basic_parser.cpp concepts.cpp - empty_body.cpp + design.cpp + error.cpp fields.cpp - header_parser_v1.cpp + header_parser.cpp message.cpp - parse.cpp - parse_error.cpp - parser_v1.cpp + message_parser.cpp read.cpp reason.cpp rfc7230.cpp diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp new file mode 100644 index 00000000..9c73f318 --- /dev/null +++ b/test/http/basic_parser.cpp @@ -0,0 +1,960 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "test_parser.hpp" + +#include +#include +#include +#include + +namespace beast { +namespace http { + +class basic_parser_test : public beast::unit_test::suite +{ +public: + enum parse_flag + { + chunked = 1, + connection_keep_alive = 2, + connection_close = 4, + connection_upgrade = 8, + upgrade = 16, + }; + + class expect_version + { + suite& s_; + int version_; + + public: + expect_version(suite& s, int version) + : s_(s) + , version_(version) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.version == version_); + } + }; + + class expect_status + { + suite& s_; + int status_; + + public: + expect_status(suite& s, int status) + : s_(s) + , status_(status) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.status == status_); + } + }; + + class expect_flags + { + suite& s_; + unsigned flags_; + + public: + expect_flags(suite& s, unsigned flags) + : s_(s) + , flags_(flags) + { + } + + template + void + operator()(Parser const& p) const + { + if(flags_ & parse_flag::chunked) + s_.BEAST_EXPECT(p.is_chunked()); + if(flags_ & parse_flag::connection_keep_alive) + s_.BEAST_EXPECT(p.is_keep_alive()); + if(flags_ & parse_flag::connection_close) + s_.BEAST_EXPECT(! p.is_keep_alive()); + if(flags_ & parse_flag::upgrade) + s_.BEAST_EXPECT(! p.is_upgrade()); + } + }; + + class expect_keepalive + { + suite& s_; + bool v_; + + public: + expect_keepalive(suite& s, bool v) + : s_(s) + , v_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.is_keep_alive() == v_); + } + }; + + class expect_body + { + suite& s_; + std::string const& body_; + + public: + expect_body(expect_body&&) = default; + + expect_body(suite& s, std::string const& v) + : s_(s) + , body_(v) + { + } + + template + void + operator()(Parser const& p) const + { + s_.BEAST_EXPECT(p.body == body_); + } + }; + + template + static + boost::asio::const_buffers_1 + buf(char const (&s)[N]) + { + return {s, N-1}; + } + + template< + bool isRequest, bool isDirect, class Derived> + static + std::size_t + feed(boost::asio::const_buffer buffer, + basic_parser& parser, + error_code& ec) + { + using boost::asio::const_buffers_1; + std::size_t used = 0; + for(;;) + { + auto const n = parser.write( + const_buffers_1{buffer}, ec); + if(ec) + return 0; + if(n == 0) + break; + buffer = buffer + n; + used += n; + if(parser.is_complete()) + break; + if(buffer_size(buffer) == 0) + break; + } + return used; + } + + template + static + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& parser, + error_code& ec) + { + using boost::asio::buffer_size; + consuming_buffers< + ConstBufferSequence> cb{buffers}; + std::size_t used = 0; + for(;;) + { + auto const n = + parser.write(cb, ec); + if(ec) + return 0; + if(n == 0) + break; + cb.consume(n); + used += n; + if(parser.is_complete()) + break; + if(buffer_size(cb) == 0) + break; + } + return used; + } + + template< + bool isRequest, bool isDirect, class Derived> + static + std::size_t + feed(boost::asio::const_buffers_1 buffers, + basic_parser& parser, + error_code& ec) + { + return feed(*buffers.begin(), parser, ec); + } + + template + void + good(boost::string_ref const& s, + Pred const& pred, bool skipBody = false) + { + using boost::asio::buffer; + test_parser p; + if(skipBody) + p.skip_body(); + error_code ec; + auto const n = feed(buffer( + s.data(), s.size()), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + if(! BEAST_EXPECT(n == s.size())) + return; + if(p.state() == parse_state::body_to_eof) + p.write_eof(ec); + if(BEAST_EXPECTS(! ec, ec.message())) + pred(p); + } + + template + void + good(boost::string_ref const& s) + { + good(s, + [](test_parser const&) + { + }); + } + + template + void + bad(boost::string_ref const& s, + error_code const& ev, bool skipBody = false) + { + using boost::asio::buffer; + test_parser p; + if(skipBody) + p.skip_body(); + error_code ec; + feed(buffer( + s.data(), s.size()), p, ec); + if(! ec && ev) + p.write_eof(ec); + BEAST_EXPECTS(ec == ev, ec.message()); + } + + void + testFlatten() + { + using boost::asio::buffer; + { + std::string const s = + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + for(std::size_t i = 1; + i < s.size() - 1; ++i) + { + auto const b1 = + buffer(s.data(), i); + auto const b2 = buffer( + s.data() + i, s.size() - i); + test_parser p; + error_code ec; + feed(b1, p, ec); + BEAST_EXPECTS(! ec, ec.message()); + feed(buffer_cat(b1, b2), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + } + { + std::string const s = + "HTTP/1.1 200 OK\r\n" + "\r\n"; + for(std::size_t i = 1; + i < s.size() - 1; ++i) + { + auto const b1 = + buffer(s.data(), i); + auto const b2 = buffer( + s.data() + i, s.size() - i); + test_parser p; + error_code ec; + feed(b1, p, ec); + BEAST_EXPECTS(! ec, ec.message()); + ec = {}; + feed(buffer_cat(b1, b2), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + p.write_eof(ec); + } + } + } + + // Check that all callbacks are invoked + void + testCallbacks() + { + using boost::asio::buffer; + { + test_parser p; + error_code ec; + std::string const s = + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + feed(buffer(s), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(p.got_on_complete); + } + { + test_parser p; + error_code ec; + std::string const s = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + feed(buffer(s), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(p.got_on_complete); + } + } + + void + testRequestLine() + { + good("GET /x HTTP/1.0\r\n\r\n"); + good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); + good("GET / HTTP/1.0\r\n\r\n", expect_version{*this, 10}); + good("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11}); + // VFALCO TODO various forms of good request-target (uri) + good("GET / HTTP/0.1\r\n\r\n", expect_version{*this, 1}); + good("GET / HTTP/2.3\r\n\r\n", expect_version{*this, 23}); + good("GET / HTTP/4.5\r\n\r\n", expect_version{*this, 45}); + good("GET / HTTP/6.7\r\n\r\n", expect_version{*this, 67}); + good("GET / HTTP/8.9\r\n\r\n", expect_version{*this, 89}); + + bad("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method); + bad("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method); + bad("GET / HTTP/1.0\r\n" "\r\n", error::bad_path); + bad("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_path); + bad("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_path); + // VFALCO TODO various forms of bad request-target (uri) + bad("GET / HTTP/1.0\r\n" "\r\n", error::bad_version); + bad("GET / _TTP/1.0\r\n" "\r\n", error::bad_version); + bad("GET / H_TP/1.0\r\n" "\r\n", error::bad_version); + bad("GET / HT_P/1.0\r\n" "\r\n", error::bad_version); + bad("GET / HTT_/1.0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP_1.0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/01.2\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/3.45\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/67.89\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/x.0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.x\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1_0\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version); + bad("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_line_ending); + bad("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_line_ending); + } + + void + testStatusLine() + { + good("HTTP/0.1 200 OK\r\n" "\r\n", expect_version{*this, 1}); + good("HTTP/2.3 200 OK\r\n" "\r\n", expect_version{*this, 23}); + good("HTTP/4.5 200 OK\r\n" "\r\n", expect_version{*this, 45}); + good("HTTP/6.7 200 OK\r\n" "\r\n", expect_version{*this, 67}); + good("HTTP/8.9 200 OK\r\n" "\r\n", expect_version{*this, 89}); + good("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0}); + good("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12}); + good("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345}); + good("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678}); + good("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999}); + good("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10}); + good("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11}); + good("HTTP/1.0 200 \r\n" "\r\n"); + good("HTTP/1.1 200 X \r\n" "\r\n"); + good("HTTP/1.1 200 X\t\r\n" "\r\n"); + good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); + good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); + + bad("\rHTTP/1.0 200 OK\r\n" "\r\n", error::bad_line_ending); + bad("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version); + bad("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 200\r\n" "\r\n", error::bad_status); + bad("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason); + bad("HTTP/1.0 200 OK\r\r\n" "\r\n", error::bad_line_ending); + } + + void + testFields() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("f:\r\n")); + good(m("f: \r\n")); + good(m("f:\t\r\n")); + good(m("f: \t\r\n")); + good(m("f: v\r\n")); + good(m("f:\tv\r\n")); + good(m("f:\tv \r\n")); + good(m("f:\tv\t\r\n")); + good(m("f:\tv\t \r\n")); + good(m("f:\r\n \r\n")); + good(m("f:v\r\n")); + good(m("f: v\r\n u\r\n")); + good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); + good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); + + bad(m(" f: v\r\n"), error::bad_field); + bad(m("\tf: v\r\n"), error::bad_field); + bad(m("f : v\r\n"), error::bad_field); + bad(m("f\t: v\r\n"), error::bad_field); + bad(m("f: \n\r\n"), error::bad_value); + bad(m("f: v\r \r\n"), error::bad_line_ending); + bad(m("f: \r v\r\n"), error::bad_line_ending); + bad("GET / HTTP/1.1\r\n\r \n\r\n\r\n",error::bad_line_ending); + } + + void + testConnectionField() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + auto const cn = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; + }; + #if 0 + auto const keepalive = + [&](bool v) + { + //return keepalive_f{*this, v}; + return true; + }; + #endif + + good(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed + + good(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + + good(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + + // VFALCO What's up with these? + //good(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + good(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + good(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + //good(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + good("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true)); + good("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false)); + good("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true)); + good("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", expect_keepalive(*this, false)); + + good(cn("x\r\n"), expect_flags{*this, 0}); + good(cn("x,y\r\n"), expect_flags{*this, 0}); + good(cn("x ,y\r\n"), expect_flags{*this, 0}); + good(cn("x\t,y\r\n"), expect_flags{*this, 0}); + good(cn("keep\r\n"), expect_flags{*this, 0}); + good(cn(",keep\r\n"), expect_flags{*this, 0}); + good(cn(" keep\r\n"), expect_flags{*this, 0}); + good(cn("\tnone\r\n"), expect_flags{*this, 0}); + good(cn("keep,\r\n"), expect_flags{*this, 0}); + good(cn("keep\t\r\n"), expect_flags{*this, 0}); + good(cn("keep\r\n"), expect_flags{*this, 0}); + good(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0}); + good(cn("\r\n keep\r\n"), expect_flags{*this, 0}); + good(cn("keep\r\n \r\n"), expect_flags{*this, 0}); + good(cn("closet\r\n"), expect_flags{*this, 0}); + good(cn(",closet\r\n"), expect_flags{*this, 0}); + good(cn(" closet\r\n"), expect_flags{*this, 0}); + good(cn("\tcloset\r\n"), expect_flags{*this, 0}); + good(cn("closet,\r\n"), expect_flags{*this, 0}); + good(cn("closet\t\r\n"), expect_flags{*this, 0}); + good(cn("closet\r\n"), expect_flags{*this, 0}); + good(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0}); + good(cn("\r\n closet\r\n"), expect_flags{*this, 0}); + good(cn("closet\r\n \r\n"), expect_flags{*this, 0}); + good(cn("clog\r\n"), expect_flags{*this, 0}); + good(cn("key\r\n"), expect_flags{*this, 0}); + good(cn("uptown\r\n"), expect_flags{*this, 0}); + good(cn("keeper\r\n \r\n"), expect_flags{*this, 0}); + good(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0}); + good(cn("up\r\n \r\n"), expect_flags{*this, 0}); + good(cn("upgrader\r\n \r\n"), expect_flags{*this, 0}); + good(cn("none\r\n"), expect_flags{*this, 0}); + good(cn("\r\n none\r\n"), expect_flags{*this, 0}); + + good(m("ConnectioX: close\r\n"), expect_flags{*this, 0}); + good(m("Condor: close\r\n"), expect_flags{*this, 0}); + good(m("Connect: close\r\n"), expect_flags{*this, 0}); + good(m("Connections: close\r\n"), expect_flags{*this, 0}); + + good(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close}); + good(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + good(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + good(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0}); + good(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0}); + good(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0}); + + bad(cn("[\r\n"), error::bad_value); + bad(cn("close[\r\n"), error::bad_value); + bad(cn("close [\r\n"), error::bad_value); + bad(cn("close, upgrade [\r\n"), error::bad_value); + bad(cn("upgrade[]\r\n"), error::bad_value); + bad(cn("keep\r\n -alive\r\n"), error::bad_value); + bad(cn("keep-alive[\r\n"), error::bad_value); + bad(cn("keep-alive []\r\n"), error::bad_value); + bad(cn("no[ne]\r\n"), error::bad_value); + } + + void + testContentLengthField() + { + auto const length = + [&](std::string const& s, std::uint64_t v) + { + good(s, + [&](test_parser const& p) + { + BEAST_EXPECT(p.content_length()); + BEAST_EXPECT(p.content_length() && *p.content_length() == v); + }, true); + }; + auto const c = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; + }; + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + + length(c("0\r\n"), 0); + length(c("00\r\n"), 0); + length(c("1\r\n"), 1); + length(c("01\r\n"), 1); + length(c("9\r\n"), 9); + length(c("123456789\r\n"), 123456789); + length(c("42 \r\n"), 42); + length(c("42\t\r\n"), 42); + length(c("42 \t \r\n"), 42); + + // VFALCO Investigate this failure + //length(c("42\r\n \t \r\n"), 42); + + good(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0}); + good(m("Content-Lengths: many\r\n"), expect_flags{*this, 0}); + good(m("Content: full\r\n"), expect_flags{*this, 0}); + + bad(c("\r\n"), error::bad_content_length); + bad(c("18446744073709551616\r\n"), error::bad_content_length); + bad(c("0 0\r\n"), error::bad_content_length); + bad(c("0 1\r\n"), error::bad_content_length); + bad(c(",\r\n"), error::bad_content_length); + bad(c("0,\r\n"), error::bad_content_length); + bad(m( + "Content-Length: 0\r\nContent-Length: 0\r\n"), error::bad_content_length); + } + + void + testTransferEncodingField() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + auto const ce = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; + }; + auto const te = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; + }; + good(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + good(ce("gzip, \r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + // Technically invalid but beyond the parser's scope to detect + // VFALCO Look into this + //good(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + good(te("gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked, gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); + good(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); + good(te("bigchunked\r\n"), expect_flags{*this, 0}); + good(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); + good(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); + good(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); + + good(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); + good(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); + good(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); + + bad( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", error::bad_transfer_encoding, true); + } + + void + testUpgradeField() + { + auto const m = + [](std::string const& s) + { + return "GET / HTTP/1.1\r\n" + s + "\r\n"; + }; + good(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade}); + good(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade}); + good(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade}); + + good(m("Up: yes\r\n"), expect_flags{*this, 0}); + good(m("UpgradX: none\r\n"), expect_flags{*this, 0}); + good(m("Upgrades: 2\r\n"), expect_flags{*this, 0}); + good(m("Upsample: 4x\r\n"), expect_flags{*this, 0}); + + good( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.is_upgrade()); + }); + } + + void testBody() + { + using boost::asio::buffer; + good( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "1", + expect_body(*this, "1")); + + good( + "HTTP/1.0 200 OK\r\n" + "\r\n" + "hello", + expect_body(*this, "hello")); + + // write the body in 3 pieces + { + error_code ec; + test_parser p; + feed(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890")), + p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // request without Content-Length or + // Transfer-Encoding: chunked has no body. + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // response without Content-Length or + // Transfer-Encoding: chunked requires eof. + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.0 200 OK\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.state() == parse_state::body_to_eof); + feed(buf( + "hello" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.state() == parse_state::body_to_eof); + p.write_eof(ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // 304 "Not Modified" response does not require eof + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.0 304 Not Modified\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // Chunked response does not require eof + { + error_code ec; + test_parser p; + feed(buf( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(! p.is_complete()); + feed(buf( + "0\r\n\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // restart: 1.0 assumes Connection: close + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.0\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + // restart: 1.1 assumes Connection: keep-alive + { + error_code ec; + test_parser p; + feed(buf( + "GET / HTTP/1.1\r\n" + "\r\n" + ), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + + bad( + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n", + error::partial_message); + } + + template + void + check_header( + test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(! p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(! p.got_on_end_body); + BEAST_EXPECT(! p.got_on_complete); + BEAST_EXPECT(p.state() != parse_state::header); + } + + void + testSplit() + { +#if 0 + streambuf sb; + sb << + "POST / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****"; + error_code ec; + test_parser p; + p.pause(); + auto n = feed(sb.data(), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(! p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(! p.got_on_complete); + BEAST_EXPECT(p.state() != parse_state::header); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.body.empty()); + sb.consume(n); + p.resume(); + n = feed(sb.data(), p, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.got_on_begin); + BEAST_EXPECT(p.got_on_field); + BEAST_EXPECT(p.got_on_header); + BEAST_EXPECT(p.got_on_body); + BEAST_EXPECT(! p.got_on_chunk); + BEAST_EXPECT(p.got_on_complete); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(p.body == "*****"); +#endif + } + + void + run() override + { + testFlatten(); + testCallbacks(); + testRequestLine(); + testStatusLine(); + testFields(); + testConnectionField(); + testContentLengthField(); + testTransferEncodingField(); + testUpgradeField(); + testBody(); + testSplit(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_parser,http,beast); + +} // http +} // beast diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp deleted file mode 100644 index 5c88a98d..00000000 --- a/test/http/basic_parser_v1.cpp +++ /dev/null @@ -1,1175 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include "fail_parser.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class basic_parser_v1_test : public beast::unit_test::suite -{ -public: - struct cb_req_checker - { - bool method = false; - bool uri = false; - bool request = false; - }; - - struct cb_res_checker - { - bool reason = false; - bool response = false; - }; - - template - struct cb_checker - : public basic_parser_v1> - , std::conditional::type - - { - bool start = false; - bool field = false; - bool value = false; - bool fields = false; - bool _body_what = false; - bool body = false; - bool complete = false; - - private: - friend class basic_parser_v1>; - - void on_start(error_code&) - { - this->start = true; - } - void on_method(boost::string_ref const&, error_code&) - { - this->method = true; - } - void on_uri(boost::string_ref const&, error_code&) - { - this->uri = true; - } - void on_reason(boost::string_ref const&, error_code&) - { - this->reason = true; - } - void on_request(error_code&) - { - this->request = true; - } - void on_response(error_code&) - { - this->response = true; - } - void on_field(boost::string_ref const&, error_code&) - { - field = true; - } - void on_value(boost::string_ref const&, error_code&) - { - value = true; - } - void - on_header(std::uint64_t, error_code&) - { - fields = true; - } - body_what - on_body_what(std::uint64_t, error_code&) - { - _body_what = true; - return body_what::normal; - } - void on_body(boost::string_ref const&, error_code&) - { - body = true; - } - void on_complete(error_code&) - { - complete = true; - } - }; - - // Check that all callbacks are invoked - void - testCallbacks() - { - using boost::asio::buffer; - { - cb_checker p; - error_code ec; - std::string const s = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - if(BEAST_EXPECT(! ec)) - { - BEAST_EXPECT(p.start); - BEAST_EXPECT(p.method); - BEAST_EXPECT(p.uri); - BEAST_EXPECT(p.request); - BEAST_EXPECT(p.field); - BEAST_EXPECT(p.value); - BEAST_EXPECT(p.fields); - BEAST_EXPECT(p._body_what); - BEAST_EXPECT(p.body); - BEAST_EXPECT(p.complete); - } - } - { - cb_checker p; - error_code ec; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - if(BEAST_EXPECT(! ec)) - { - BEAST_EXPECT(p.start); - BEAST_EXPECT(p.reason); - BEAST_EXPECT(p.response); - BEAST_EXPECT(p.field); - BEAST_EXPECT(p.value); - BEAST_EXPECT(p.fields); - BEAST_EXPECT(p.body); - BEAST_EXPECT(p.complete); - } - } - } - - //-------------------------------------------------------------------------- - - template - static - void - for_split(boost::string_ref const& s, F const& f) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - for(std::size_t i = 0; i < s.size(); ++i) - { - // Use separately allocated buffers so - // address sanitizer has something to chew on. - // - auto const n1 = s.size() - i; - auto const n2 = i; - std::unique_ptr p1(new char[n1]); - std::unique_ptr p2(new char[n2]); - buffer_copy(buffer(p1.get(), n1), buffer(s.data(), n1)); - buffer_copy(buffer(p2.get(), n2), buffer(s.data() + n1, n2)); - f( - boost::string_ref{p1.get(), n1}, - boost::string_ref{p2.get(), n2}); - } - } - - struct none - { - template - void - operator()(Parser const&) const - { - } - }; - - template - void - good(body_what onBodyRv, std::string const& s, F const& f) - { - using boost::asio::buffer; - for_split(s, - [&](boost::string_ref const& s1, boost::string_ref const& s2) - { - static std::size_t constexpr Limit = 200; - std::size_t n; - for(n = 0; n < Limit; ++n) - { - test::fail_counter fc(n); - fail_parser p(fc); - p.on_body_rv(onBodyRv); - error_code ec; - p.write(buffer(s1.data(), s1.size()), ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - if(! BEAST_EXPECT(s2.empty() || ! p.complete())) - break; - p.write(buffer(s2.data(), s2.size()), ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - p.write_eof(ec); - if(ec == test::error::fail_error) - continue; - if(! BEAST_EXPECT(! ec)) - break; - BEAST_EXPECT(p.complete()); - f(p); - break; - } - BEAST_EXPECT(n < Limit); - }); - } - - template - void - good(std::string const& s, F const& f = {}) - { - return good(body_what::normal, s, f); - } - - template - void - bad(body_what onBodyRv, std::string const& s, error_code ev) - { - using boost::asio::buffer; - for_split(s, - [&](boost::string_ref const& s1, boost::string_ref const& s2) - { - static std::size_t constexpr Limit = 200; - std::size_t n; - for(n = 0; n < Limit; ++n) - { - test::fail_counter fc(n); - fail_parser p(fc); - p.on_body_rv(onBodyRv); - error_code ec; - p.write(buffer(s1.data(), s1.size()), ec); - if(ec == test::error::fail_error) - continue; - if(ec) - { - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - if(! BEAST_EXPECT(! p.complete())) - break; - if(! s2.empty()) - { - p.write(buffer(s2.data(), s2.size()), ec); - if(ec == test::error::fail_error) - continue; - if(ec) - { - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - if(! BEAST_EXPECT(! p.complete())) - break; - } - p.write_eof(ec); - if(ec == test::error::fail_error) - continue; - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT((ec && ! ev) || ec == ev); - break; - } - BEAST_EXPECT(n < Limit); - }); - } - - template - void - bad(std::string const& s, error_code ev = {}) - { - return bad(body_what::normal, s, ev); - } - - //-------------------------------------------------------------------------- - - class version - { - suite& s_; - unsigned major_; - unsigned minor_; - - public: - version(suite& s, unsigned major, unsigned minor) - : s_(s) - , major_(major) - , minor_(minor) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.http_major() == major_); - s_.BEAST_EXPECT(p.http_minor() == minor_); - } - }; - - class status - { - suite& s_; - unsigned code_; - public: - status(suite& s, int code) - : s_(s) - , code_(code) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.status_code() == code_); - } - }; - - void testRequestLine() - { - /* - request-line = method SP request-target SP HTTP-version CRLF - method = token - request-target = origin-form / absolute-form / authority-form / asterisk-form - HTTP-version = "HTTP/" DIGIT "." DIGIT - */ - good("GET /x HTTP/1.0\r\n\r\n"); - good("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); - good("GET / HTTP/1.0\r\n\r\n", version{*this, 1, 0}); - good("G / HTTP/1.1\r\n\r\n", version{*this, 1, 1}); - // VFALCO TODO various forms of good request-target (uri) - good("GET / HTTP/0.1\r\n\r\n", version{*this, 0, 1}); - good("GET / HTTP/2.3\r\n\r\n", version{*this, 2, 3}); - good("GET / HTTP/4.5\r\n\r\n", version{*this, 4, 5}); - good("GET / HTTP/6.7\r\n\r\n", version{*this, 6, 7}); - good("GET / HTTP/8.9\r\n\r\n", version{*this, 8, 9}); - - bad("\tGET / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); - bad("GET\x01 / HTTP/1.0\r\n" "\r\n", parse_error::bad_method); - bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - bad("GET \x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - bad("GET /\x01 HTTP/1.0\r\n" "\r\n", parse_error::bad_uri); - // VFALCO TODO various forms of bad request-target (uri) - bad("GET / HTTP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / _TTP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / H_TP/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HT_P/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTT_/1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP_1.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/01.2\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/3.45\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/67.89\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/x.0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.x\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0 \r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1_0\r\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\n" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\n\r" "\r\n", parse_error::bad_version); - bad("GET / HTTP/1.0\r\r\n" "\r\n", parse_error::bad_crlf); - - // write a bad request line in 2 pieces - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buffer_cat( - buf("GET / "), buf("_TTP/1.1\r\n"), - buf("\r\n") - ), ec); - BEAST_EXPECT(ec == parse_error::bad_version); - } - } - - void testStatusLine() - { - /* - status-line = HTTP-version SP status-code SP reason-phrase CRLF - HTTP-version = "HTTP/" DIGIT "." DIGIT - status-code = 3DIGIT - reason-phrase = *( HTAB / SP / VCHAR / obs-text ) - */ - good("HTTP/0.1 200 OK\r\n" "\r\n", version{*this, 0, 1}); - good("HTTP/2.3 200 OK\r\n" "\r\n", version{*this, 2, 3}); - good("HTTP/4.5 200 OK\r\n" "\r\n", version{*this, 4, 5}); - good("HTTP/6.7 200 OK\r\n" "\r\n", version{*this, 6, 7}); - good("HTTP/8.9 200 OK\r\n" "\r\n", version{*this, 8, 9}); - good("HTTP/1.0 000 OK\r\n" "\r\n", status{*this, 0}); - good("HTTP/1.1 012 OK\r\n" "\r\n", status{*this, 12}); - good("HTTP/1.0 345 OK\r\n" "\r\n", status{*this, 345}); - good("HTTP/1.0 678 OK\r\n" "\r\n", status{*this, 678}); - good("HTTP/1.0 999 OK\r\n" "\r\n", status{*this, 999}); - good("HTTP/1.0 200 \tX\r\n" "\r\n", version{*this, 1, 0}); - good("HTTP/1.1 200 X\r\n" "\r\n", version{*this, 1, 1}); - good("HTTP/1.0 200 \r\n" "\r\n"); - good("HTTP/1.1 200 X \r\n" "\r\n"); - good("HTTP/1.1 200 X\t\r\n" "\r\n"); - good("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); - good("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); - - bad("\rHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("\nHTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad(" HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("_TTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("H_TP/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HT_P/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTT_/1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP_1.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/01.2 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/3.45 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/67.89 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/x.0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1.x 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1_0 200 OK\r\n" "\r\n", parse_error::bad_version); - bad("HTTP/1.0 200 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 0 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 12 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 3456 OK\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 200\r\n" "\r\n", parse_error::bad_status); - bad("HTTP/1.0 200 \n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 \x01\r\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 \x7f\r\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 OK\n" "\r\n", parse_error::bad_reason); - bad("HTTP/1.0 200 OK\r\r\n" "\r\n", parse_error::bad_crlf); - } - - //-------------------------------------------------------------------------- - - void testHeaders() - { - /* - header-field = field-name ":" OWS field-value OWS - field-name = token - field-value = *( field-content / obs-fold ) - field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] - field-vchar = VCHAR / obs-text - obs-fold = CRLF 1*( SP / HTAB ) - ; obsolete line folding - */ - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - good(m("f:\r\n")); - good(m("f: \r\n")); - good(m("f:\t\r\n")); - good(m("f: \t\r\n")); - good(m("f: v\r\n")); - good(m("f:\tv\r\n")); - good(m("f:\tv \r\n")); - good(m("f:\tv\t\r\n")); - good(m("f:\tv\t \r\n")); - good(m("f:\r\n \r\n")); - good(m("f:v\r\n")); - good(m("f: v\r\n u\r\n")); - good(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); - good(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); - - bad(m(" f: v\r\n"), parse_error::bad_field); - bad(m("\tf: v\r\n"), parse_error::bad_field); - bad(m("f : v\r\n"), parse_error::bad_field); - bad(m("f\t: v\r\n"), parse_error::bad_field); - bad(m("f: \n\r\n"), parse_error::bad_value); - bad(m("f: v\r \r\n"), parse_error::bad_crlf); - bad(m("f: \r v\r\n"), parse_error::bad_crlf); - bad("GET / HTTP/1.1\r\n\r \n", parse_error::bad_crlf); - } - - //-------------------------------------------------------------------------- - - class flags - { - suite& s_; - std::size_t value_; - - public: - flags(suite& s, std::size_t value) - : s_(s) - , value_(value) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.flags() == value_); - } - }; - - class keepalive_f - { - suite& s_; - bool value_; - - public: - keepalive_f(suite& s, bool value) - : s_(s) - , value_(value) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.keep_alive() == value_); - } - }; - - void testConnectionHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - auto const cn = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nConnection: " + s + "\r\n"; - }; - auto const keepalive = - [&](bool v) - { - return keepalive_f{*this, v}; - }; - - good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(",close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(" close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("\tclose\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\t\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn(" ,\t,,close,, ,\t,,\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("\r\n close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n \r\n"), flags{*this, parse_flag::connection_close}); - good(cn("any,close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,any\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("any\r\n ,close\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close\r\n ,any\r\n"), flags{*this, parse_flag::connection_close}); - good(cn("close,close\r\n"), flags{*this, parse_flag::connection_close}); // weird but allowed - - good(cn("keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\t \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\t ,x\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("\r\n keep-alive \t\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive \r\n \t \r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(cn("keep-alive\r\n \r\n"), flags{*this, parse_flag::connection_keep_alive}); - - good(cn("upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\t \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\t ,x\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("\r\n upgrade \t\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade \r\n \t \r\n"), flags{*this, parse_flag::connection_upgrade}); - good(cn("upgrade\r\n \r\n"), flags{*this, parse_flag::connection_upgrade}); - - good(cn("close,keep-alive\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); - good(cn("upgrade,keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); - good(cn("upgrade,\r\n keep-alive\r\n"), flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); - good(cn("close,keep-alive,upgrade\r\n"), flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); - - good("GET / HTTP/1.1\r\n\r\n", keepalive(true)); - good("GET / HTTP/1.0\r\n\r\n", keepalive(false)); - good("GET / HTTP/1.0\r\n" - "Connection: keep-alive\r\n\r\n", keepalive(true)); - good("GET / HTTP/1.1\r\n" - "Connection: close\r\n\r\n", keepalive(false)); - - good(cn("x\r\n"), flags{*this, 0}); - good(cn("x,y\r\n"), flags{*this, 0}); - good(cn("x ,y\r\n"), flags{*this, 0}); - good(cn("x\t,y\r\n"), flags{*this, 0}); - good(cn("keep\r\n"), flags{*this, 0}); - good(cn(",keep\r\n"), flags{*this, 0}); - good(cn(" keep\r\n"), flags{*this, 0}); - good(cn("\tnone\r\n"), flags{*this, 0}); - good(cn("keep,\r\n"), flags{*this, 0}); - good(cn("keep\t\r\n"), flags{*this, 0}); - good(cn("keep\r\n"), flags{*this, 0}); - good(cn(" ,\t,,keep,, ,\t,,\r\n"), flags{*this, 0}); - good(cn("\r\n keep\r\n"), flags{*this, 0}); - good(cn("keep\r\n \r\n"), flags{*this, 0}); - good(cn("closet\r\n"), flags{*this, 0}); - good(cn(",closet\r\n"), flags{*this, 0}); - good(cn(" closet\r\n"), flags{*this, 0}); - good(cn("\tcloset\r\n"), flags{*this, 0}); - good(cn("closet,\r\n"), flags{*this, 0}); - good(cn("closet\t\r\n"), flags{*this, 0}); - good(cn("closet\r\n"), flags{*this, 0}); - good(cn(" ,\t,,closet,, ,\t,,\r\n"), flags{*this, 0}); - good(cn("\r\n closet\r\n"), flags{*this, 0}); - good(cn("closet\r\n \r\n"), flags{*this, 0}); - good(cn("clog\r\n"), flags{*this, 0}); - good(cn("key\r\n"), flags{*this, 0}); - good(cn("uptown\r\n"), flags{*this, 0}); - good(cn("keeper\r\n \r\n"), flags{*this, 0}); - good(cn("keep-alively\r\n \r\n"), flags{*this, 0}); - good(cn("up\r\n \r\n"), flags{*this, 0}); - good(cn("upgrader\r\n \r\n"), flags{*this, 0}); - good(cn("none\r\n"), flags{*this, 0}); - good(cn("\r\n none\r\n"), flags{*this, 0}); - - good(m("ConnectioX: close\r\n"), flags{*this, 0}); - good(m("Condor: close\r\n"), flags{*this, 0}); - good(m("Connect: close\r\n"), flags{*this, 0}); - good(m("Connections: close\r\n"), flags{*this, 0}); - - good(m("Proxy-Connection: close\r\n"), flags{*this, parse_flag::connection_close}); - good(m("Proxy-Connection: keep-alive\r\n"), flags{*this, parse_flag::connection_keep_alive}); - good(m("Proxy-Connection: upgrade\r\n"), flags{*this, parse_flag::connection_upgrade}); - good(m("Proxy-ConnectioX: none\r\n"), flags{*this, 0}); - good(m("Proxy-Connections: 1\r\n"), flags{*this, 0}); - good(m("Proxy-Connotes: see-also\r\n"), flags{*this, 0}); - - bad(cn("["), parse_error::bad_value); - bad(cn("\"\r\n"), parse_error::bad_value); - bad(cn("close[\r\n"), parse_error::bad_value); - bad(cn("close [\r\n"), parse_error::bad_value); - bad(cn("close, upgrade [\r\n"), parse_error::bad_value); - bad(cn("upgrade[]\r\n"), parse_error::bad_value); - bad(cn("keep\r\n -alive\r\n"), parse_error::bad_value); - bad(cn("keep-alive[\r\n"), parse_error::bad_value); - bad(cn("keep-alive []\r\n"), parse_error::bad_value); - bad(cn("no[ne]\r\n"), parse_error::bad_value); - } - - void testContentLengthHeader() - { - auto const length = - [&](std::string const& s, std::uint64_t v) - { - good(body_what::skip, s, - [&](fail_parser const& p) - { - BEAST_EXPECT(p.content_length() == v); - if(v != no_content_length) - BEAST_EXPECT(p.flags() & parse_flag::contentlength); - }); - }; - auto const c = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nContent-Length: " + s + "\r\n"; - }; - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - - length(c("0\r\n"), 0); - length(c("00\r\n"), 0); - length(c("1\r\n"), 1); - length(c("01\r\n"), 1); - length(c("9\r\n"), 9); - length(c("123456789\r\n"), 123456789); - length(c("42 \r\n"), 42); - length(c("42\t\r\n"), 42); - length(c("42 \t \r\n"), 42); - length(c("42\r\n \t \r\n"), 42); - - good(m("Content-LengtX: 0\r\n"), flags{*this, 0}); - good(m("Content-Lengths: many\r\n"), flags{*this, 0}); - good(m("Content: full\r\n"), flags{*this, 0}); - - bad(c("\r\n"), parse_error::bad_content_length); - bad(c("18446744073709551616\r\n"), parse_error::bad_content_length); - bad(c("0 0\r\n"), parse_error::bad_content_length); - bad(c("0 1\r\n"), parse_error::bad_content_length); - bad(c(",\r\n"), parse_error::bad_content_length); - bad(c("0,\r\n"), parse_error::bad_content_length); - bad(m( - "Content-Length: 0\r\nContent-Length: 0\r\n"), parse_error::bad_content_length); - } - - void testTransferEncodingHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - auto const ce = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n0\r\n\r\n"; - }; - auto const te = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\nTransfer-Encoding: " + s + "\r\n"; - }; - good(ce("chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked\t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked \t\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(" chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\tchunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked ,\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked, \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(", chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(" ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("chunked\r\n \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("\r\n ,chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce(",\r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, chunked \r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - good(ce("gzip, \r\n chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - - // Technically invalid but beyond the parser's scope to detect - good(ce("custom;key=\",chunked\r\n"), flags{*this, parse_flag::chunked | parse_flag::trailing}); - - good(te("gzip\r\n"), flags{*this, 0}); - good(te("chunked, gzip\r\n"), flags{*this, 0}); - good(te("chunked\r\n , gzip\r\n"), flags{*this, 0}); - good(te("chunked,\r\n gzip\r\n"), flags{*this, 0}); - good(te("chunked,\r\n ,gzip\r\n"), flags{*this, 0}); - good(te("bigchunked\r\n"), flags{*this, 0}); - good(te("chunk\r\n ked\r\n"), flags{*this, 0}); - good(te("bar\r\n ley chunked\r\n"), flags{*this, 0}); - good(te("barley\r\n chunked\r\n"), flags{*this, 0}); - - good(m("Transfer-EncodinX: none\r\n"), flags{*this, 0}); - good(m("Transfer-Encodings: 2\r\n"), flags{*this, 0}); - good(m("Transfer-Encoded: false\r\n"), flags{*this, 0}); - - bad(body_what::skip, - "HTTP/1.1 200 OK\r\n" - "Content-Length: 1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n", parse_error::illegal_content_length); - } - - void testUpgradeHeader() - { - auto const m = - [](std::string const& s) - { - return "GET / HTTP/1.1\r\n" + s + "\r\n"; - }; - good(m("Upgrade:\r\n"), flags{*this, parse_flag::upgrade}); - good(m("Upgrade: \r\n"), flags{*this, parse_flag::upgrade}); - good(m("Upgrade: yes\r\n"), flags{*this, parse_flag::upgrade}); - - good(m("Up: yes\r\n"), flags{*this, 0}); - good(m("UpgradX: none\r\n"), flags{*this, 0}); - good(m("Upgrades: 2\r\n"), flags{*this, 0}); - good(m("Upsample: 4x\r\n"), flags{*this, 0}); - - good( - "GET / HTTP/1.1\r\n" - "Connection: upgrade\r\n" - "Upgrade: WebSocket\r\n" - "\r\n", - [&](fail_parser const& p) - { - BEAST_EXPECT(p.upgrade()); - }); - } - - //-------------------------------------------------------------------------- - - class body_f - { - suite& s_; - std::string const& body_; - - public: - body_f(body_f&&) = default; - - body_f(suite& s, std::string const& v) - : s_(s) - , body_(v) - { - } - - template - void - operator()(Parser const& p) const - { - s_.BEAST_EXPECT(p.body == body_); - } - }; - - template - static - boost::asio::const_buffers_1 - buf(char const (&s)[N]) - { - return { s, N-1 }; - } - - void testBody() - { - using boost::asio::buffer; - auto const body = - [&](std::string const& s) - { - return body_f{*this, s}; - }; - good( - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - "1", - body("1")); - - good( - "HTTP/1.0 200 OK\r\n" - "\r\n" - "hello", - body("hello")); - - // on_body returns 2, meaning upgrade - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p{fc}; - p.on_body_rv(body_what::upgrade); - p.write(buf( - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - - // write the body in 3 pieces - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buffer_cat( - buf("GET / HTTP/1.1\r\n" - "Content-Length: 10\r\n" - "\r\n"), - buf("12"), - buf("345"), - buf("67890")), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - BEAST_EXPECT(! p.needs_eof()); - p.write_eof(ec); - BEAST_EXPECT(! ec); - p.write_eof(ec); - BEAST_EXPECT(! ec); - p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // request without Content-Length or - // Transfer-Encoding: chunked has no body. - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // response without Content-Length or - // Transfer-Encoding: chunked requires eof. - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.0 200 OK\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT(p.needs_eof()); - p.write(buf( - "hello" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.complete()); - BEAST_EXPECT(p.needs_eof()); - p.write_eof(ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf("GET / HTTP/1.1\r\n\r\n"), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // 304 "Not Modified" response does not require eof - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.0 304 Not Modified\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // Chunked response does not require eof - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(! p.complete()); - p.write(buf( - "0\r\n\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(! p.needs_eof()); - BEAST_EXPECT(p.complete()); - } - - // restart: 1.0 assumes Connection: close - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(ec == parse_error::connection_closed); - } - - // restart: 1.1 assumes Connection: keep-alive - { - error_code ec; - test::fail_counter fc(1000); - fail_parser p(fc); - p.write(buf( - "GET / HTTP/1.1\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - p.write(buf( - "GET / HTTP/1.0\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - - bad(body_what::normal, - "GET / HTTP/1.1\r\n" - "Content-Length: 1\r\n" - "\r\n", - parse_error::short_read); - } - - void testChunkedBody() - { - auto const body = - [&](std::string const& s) - { - return body_f{*this, s}; - }; - auto const ce = - [](std::string const& s) - { - return - "GET / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" + s; - }; - - /* - chunked-body = *chunk - last-chunk - trailer-part - CRLF - chunk = chunk-size [ chunk-ext ] CRLF - chunk-data CRLF - chunk-size = 1*HEXDIG - last-chunk = 1*("0") [ chunk-ext ] CRLF - chunk-data = 1*OCTET ; a sequence of chunk-size octets - chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) - chunk-ext-name = token - chunk-ext-val = token / quoted-string - trailer-part = *( header-field CRLF ) - */ - good(ce( - "1;xy\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;x\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;x;y\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1;i;j=2;k=\"3\"\r\n*\r\n" "0\r\n\r\n" - ), body("*")); - - good(ce( - "1\r\n" "a\r\n" "0\r\n" "\r\n" - ), body("a")); - - good(ce( - "2\r\n" "ab\r\n" "0\r\n" "\r\n" - ), body("ab")); - - good(ce( - "2\r\n" "ab\r\n" "1\r\n" "c\r\n" "0\r\n" "\r\n" - ), body("abc")); - - good(ce( - "10\r\n" "1234567890123456\r\n" "0\r\n" "\r\n" - ), body("1234567890123456")); - - bad(ce("ffffffffffffffff0\r\n0\r\n\r\n"), parse_error::bad_content_length); - bad(ce("g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); - bad(ce("0g\r\n0\r\n\r\n"), parse_error::invalid_chunk_size); - bad(ce("0\r_\n"), parse_error::bad_crlf); - bad(ce("1\r\n*_\r\n"), parse_error::bad_crlf); - bad(ce("1\r\n*\r_\n"), parse_error::bad_crlf); - bad(ce("1;,x\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); - bad(ce("1;x,\r\n*\r\n" "0\r\n\r\n"), parse_error::invalid_ext_name); - } - - void testLimits() - { - std::size_t n; - static std::size_t constexpr Limit = 100; - { - for(n = 1; n < Limit; ++n) - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(header_max_size{n}); - error_code ec; - p.write(buf( - "GET / HTTP/1.1\r\n" - "User-Agent: beast\r\n" - "\r\n" - ), ec); - if(! ec) - break; - BEAST_EXPECT(ec == parse_error::header_too_big); - } - BEAST_EXPECT(n < Limit); - } - { - for(n = 1; n < Limit; ++n) - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(header_max_size{n}); - error_code ec; - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Content-Length: 4\r\n" - "\r\n" - "****" - ), ec); - if(! ec) - break; - BEAST_EXPECT(ec == parse_error::header_too_big); - } - BEAST_EXPECT(n < Limit); - } - { - test::fail_counter fc(1000); - fail_parser p(fc); - p.set_option(body_max_size{2}); - error_code ec; - p.write(buf( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Content-Length: 4\r\n" - "\r\n" - "****" - ), ec); - BEAST_EXPECT(ec == parse_error::body_too_big); - } - } - - void run() override - { - testCallbacks(); - testRequestLine(); - testStatusLine(); - testHeaders(); - testConnectionHeader(); - testContentLengthHeader(); - testTransferEncodingHeader(); - testUpgradeHeader(); - testBody(); - testChunkedBody(); - testLimits(); - } -}; - -BEAST_DEFINE_TESTSUITE(basic_parser_v1,http,beast); - -} // http -} // beast diff --git a/test/http/design.cpp b/test/http/design.cpp new file mode 100644 index 00000000..70908ed3 --- /dev/null +++ b/test/http/design.cpp @@ -0,0 +1,534 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class design_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + //-------------------------------------------------------------------------- + /* + Read a message with a direct Reader Body. + */ + struct direct_body + { + using value_type = std::string; + + class reader + { + value_type& body_; + std::size_t len_ = 0; + + public: + static bool constexpr is_direct = true; + + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + template + explicit + reader(message& m) + : body_(m.body) + { + } + + void + init() + { + } + + void + init(std::uint64_t content_length) + { + if(content_length > + (std::numeric_limits::max)()) + throw std::length_error( + "Content-Length max exceeded"); + body_.reserve(static_cast< + std::size_t>(content_length)); + } + + mutable_buffers_type + prepare(std::size_t n) + { + body_.resize(len_ + n); + return {&body_[len_], n}; + } + + void + commit(std::size_t n) + { + if(body_.size() > len_ + n) + body_.resize(len_ + n); + len_ = body_.size(); + } + + void + finish() + { + body_.resize(len_); + } + }; + }; + + void + testDirectBody() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + message m; + flat_streambuf sb{1024}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*" + }; + message m; + flat_streambuf sb{20}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*\r\n" + "0\r\n\r\n" + }; + message m; + flat_streambuf sb{100}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + } + + //-------------------------------------------------------------------------- + /* + Read a message with an indirect Reader Body. + */ + struct indirect_body + { + using value_type = std::string; + + class reader + { + value_type& body_; + + public: + static bool constexpr is_direct = false; + + using mutable_buffers_type = + boost::asio::null_buffers; + + template + explicit + reader(message& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + } + + void + init(std::uint64_t content_length, + error_code& ec) + { + } + + void + write(boost::string_ref const& s, + error_code& ec) + { + body_.append(s.data(), s.size()); + } + + void + finish(error_code& ec) + { + } + }; + }; + + void + testIndirectBody() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + message m; + flat_streambuf sb{1024}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*" + }; + message m; + flat_streambuf sb{20}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*\r\n" + "0\r\n\r\n" + }; + message m; + flat_streambuf sb{1024}; + read(is, sb, m); + BEAST_EXPECT(m.body == "*"); + } + } + + //-------------------------------------------------------------------------- + /* + Read a message header and manually read the body. + */ + void + testManualBody() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" // 37 byte header + "*****" + }; + header_parser p; + flat_streambuf sb{38}; + auto const bytes_used = + read_some(is, sb, p); + sb.consume(bytes_used); + BEAST_EXPECT(p.size() == 5); + BEAST_EXPECT(sb.size() < 5); + sb.commit(boost::asio::read( + is, sb.prepare(5 - sb.size()))); + BEAST_EXPECT(sb.size() == 5); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*****" + }; + header_parser p; + flat_streambuf sb{20}; + auto const bytes_used = + read_some(is, sb, p); + sb.consume(bytes_used); + BEAST_EXPECT(p.state() == + parse_state::body_to_eof); + BEAST_EXPECT(sb.size() < 5); + sb.commit(boost::asio::read( + is, sb.prepare(5 - sb.size()))); + BEAST_EXPECT(sb.size() == 5); + } + } + + //-------------------------------------------------------------------------- + /* + Read a header, check for Expect: 100-continue, + then conditionally read the body. + */ + void + testExpect100Continue() + { + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + }; + + header_parser p; + flat_streambuf sb{128}; + auto const bytes_used = + read_some(is, sb, p); + sb.consume(bytes_used); + BEAST_EXPECT(p.got_header()); + BEAST_EXPECT( + p.get().fields["Expect"] == + "100-continue"); + message_parser< + true, string_body, fields> p1{ + std::move(p)}; + read(is, sb, p1); + BEAST_EXPECT( + p1.get().body == "*****"); + } + } + + //-------------------------------------------------------------------------- + /* + Efficiently relay a message from one stream to another + */ + template< + bool isRequest, + class SyncWriteStream, + class DynamicBuffer, + class SyncReadStream> + void + relay( + SyncWriteStream& out, + DynamicBuffer& sb, + SyncReadStream& in) + { + flat_streambuf buffer{4096}; // 4K limit + header_parser parser; + error_code ec; + do + { + auto const state0 = parser.state(); + auto const bytes_used = + read_some(in, buffer, parser, ec); + BEAST_EXPECTS(! ec, ec.message()); + switch(state0) + { + case parse_state::header: + { + BEAST_EXPECT(parser.got_header()); + write(out, parser.get()); + break; + } + + case parse_state::chunk_header: + { + // inspect parser.chunk_extension() here + if(parser.is_complete()) + boost::asio::write(out, + chunk_encode_final()); + break; + } + + case parse_state::body: + case parse_state::body_to_eof: + case parse_state::chunk_body: + { + if(! parser.is_complete()) + { + auto const body = parser.body(); + boost::asio::write(out, chunk_encode( + false, boost::asio::buffer( + body.data(), body.size()))); + } + break; + } + + case parse_state::complete: + break; + } + buffer.consume(bytes_used); + } + while(! parser.is_complete()); + } + + void + testRelay() + { + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" // 37 byte header + "*****", + 3 // max_read + }; + test::string_ostream os{ios_}; + flat_streambuf sb{16}; + relay(os, sb, is); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*****", + 3 // max_read + }; + test::string_ostream os{ios_}; + flat_streambuf sb{16}; + relay(os, sb, is); + } + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x;y=1;z=\"-\"\r\n*****\r\n" + "3\r\n---\r\n" + "1\r\n+\r\n" + "0\r\n\r\n", + 2 // max_read + }; + test::string_ostream os{ios_}; + flat_streambuf sb{16}; + relay(os, sb, is); + } + } + + //-------------------------------------------------------------------------- + /* + Read the request header, then read the request body content using + a fixed-size buffer, i.e. read the body in chunks of 4k for instance. + The end of the body should be indicated somehow and chunk-encoding + should be decoded by beast. + */ + template + void + doFixedRead(SyncReadStream& stream, BodyCallback const& cb) + { + flat_streambuf buffer{4096}; // 4K limit + header_parser parser; + std::size_t bytes_used; + bytes_used = read_some(stream, buffer, parser); + BEAST_EXPECT(parser.got_header()); + buffer.consume(bytes_used); + do + { + bytes_used = + read_some(stream, buffer, parser); + if(! parser.body().empty()) + cb(parser.body()); + buffer.consume(bytes_used); + } + while(! parser.is_complete()); + } + + struct bodyHandler + { + void + operator()(boost::string_ref const& body) const + { + // called for each piece of the body, + } + }; + + void + testFixedRead() + { + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + + // Content-Length + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + doFixedRead(is, bodyHandler{}); + } + + // end of file + { + test::string_istream is{ios_, + "HTTP/1.1 200 OK\r\n" + "\r\n" // 19 byte header + "*****" + }; + doFixedRead(is, bodyHandler{}); + } + + // chunked + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x;y=1;z=\"-\"\r\n*****\r\n" + "3\r\n---\r\n" + "1\r\n+\r\n" + "0\r\n\r\n", + 2 // max_read + }; + doFixedRead(is, bodyHandler{}); + } + } + + //-------------------------------------------------------------------------- + + void + run() + { + testDirectBody(); + testIndirectBody(); + testManualBody(); + testExpect100Continue(); + testRelay(); + testFixedRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(design,http,beast); + +} // http +} // beast diff --git a/test/http/empty_body.cpp b/test/http/empty_body.cpp deleted file mode 100644 index fa190ed3..00000000 --- a/test/http/empty_body.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include diff --git a/test/http/error.cpp b/test/http/error.cpp new file mode 100644 index 00000000..1dcf99b6 --- /dev/null +++ b/test/http/error.cpp @@ -0,0 +1,59 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include + +namespace beast { +namespace http { + +class error_test : public unit_test::suite +{ +public: + void + check(char const* name, error ev) + { + auto const ec = make_error_code(ev); + BEAST_EXPECT(std::string(ec.category().name()) == name); + BEAST_EXPECT(! ec.message().empty()); + BEAST_EXPECT(std::addressof(ec.category()) == + std::addressof(detail::get_http_error_category())); + BEAST_EXPECT(detail::get_http_error_category().equivalent( + static_cast::type>(ev), + ec.category().default_error_condition( + static_cast::type>(ev)))); + BEAST_EXPECT(detail::get_http_error_category().equivalent( + ec, static_cast::type>(ev))); + } + + void + run() override + { + check("http", error::end_of_stream); + check("http", error::partial_message); + check("http", error::buffer_overflow); + check("http", error::bad_line_ending); + check("http", error::bad_method); + check("http", error::bad_path); + check("http", error::bad_version); + check("http", error::bad_status); + check("http", error::bad_reason); + check("http", error::bad_field); + check("http", error::bad_value); + check("http", error::bad_content_length); + check("http", error::bad_transfer_encoding); + check("http", error::bad_chunk); + } +}; + +BEAST_DEFINE_TESTSUITE(error,http,beast); + +} // http +} // beast diff --git a/test/http/fail_parser.hpp b/test/http/fail_parser.hpp deleted file mode 100644 index 65651633..00000000 --- a/test/http/fail_parser.hpp +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_TEST_FAIL_PARSER_HPP -#define BEAST_HTTP_TEST_FAIL_PARSER_HPP - -#include -#include - -namespace beast { -namespace http { - -template -class fail_parser - : public basic_parser_v1> -{ - test::fail_counter& fc_; - std::uint64_t content_length_ = no_content_length; - body_what body_rv_ = body_what::normal; - -public: - std::string body; - - template - explicit - fail_parser(test::fail_counter& fc, Args&&... args) - : fc_(fc) - { - } - - void - on_body_rv(body_what rv) - { - body_rv_ = rv; - } - - // valid on successful parse - std::uint64_t - content_length() const - { - return content_length_; - } - - void on_start(error_code& ec) - { - fc_.fail(ec); - } - - void on_method(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_uri(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_reason(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_request(error_code& ec) - { - fc_.fail(ec); - } - - void on_response(error_code& ec) - { - fc_.fail(ec); - } - - void on_field(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void on_value(boost::string_ref const&, error_code& ec) - { - fc_.fail(ec); - } - - void - on_header(std::uint64_t content_length, error_code& ec) - { - if(fc_.fail(ec)) - return; - } - - body_what - on_body_what(std::uint64_t content_length, error_code& ec) - { - if(fc_.fail(ec)) - return body_what::normal; - content_length_ = content_length; - return body_rv_; - } - - void on_body(boost::string_ref const& s, error_code& ec) - { - if(fc_.fail(ec)) - return; - body.append(s.data(), s.size()); - } - - void on_complete(error_code& ec) - { - fc_.fail(ec); - } -}; - -} // http -} // beast - -#endif diff --git a/test/http/header_parser.cpp b/test/http/header_parser.cpp new file mode 100644 index 00000000..473355c3 --- /dev/null +++ b/test/http/header_parser.cpp @@ -0,0 +1,66 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class header_parser_test + : public beast::unit_test::suite + , public test::enable_yield_to +{ +public: + void + testParse() + { + { + test::string_istream is{ios_, + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "\r\n" + }; + flat_streambuf db{1024}; + header_parser p; + read_some(is, db, p); + BEAST_EXPECT(p.is_complete()); + } + { + test::string_istream is{ios_, + "POST / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + }; + flat_streambuf db{1024}; + header_parser p; + read_some(is, db, p); + BEAST_EXPECT(! p.is_complete()); + BEAST_EXPECT(p.state() == parse_state::body); + } + } + + void + run() override + { + testParse(); + } +}; + +BEAST_DEFINE_TESTSUITE(header_parser,http,beast); + +} // http +} // beast + diff --git a/test/http/header_parser_v1.cpp b/test/http/header_parser_v1.cpp deleted file mode 100644 index 8200fe8c..00000000 --- a/test/http/header_parser_v1.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include - -namespace beast { -namespace http { - -class header_parser_v1_test : public beast::unit_test::suite -{ -public: - void testParser() - { - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "\r\n" - ), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - BEAST_EXPECT(n == 36); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - ), ec); - BEAST_EXPECT(n == 55); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "\r\n" - ), ec); - BEAST_EXPECT(n == 33); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - { - error_code ec; - header_parser_v1 p; - BEAST_EXPECT(! p.complete()); - auto const n = p.write(boost::asio::buffer( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****" - ), ec); - BEAST_EXPECT(n == 52); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.complete()); - } - } - - void run() override - { - testParser(); - } -}; - -BEAST_DEFINE_TESTSUITE(header_parser_v1,http,beast); - -} // http -} // beast diff --git a/test/http/message.cpp b/test/http/message.cpp index 3b1c711a..54f49f1b 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -8,7 +8,7 @@ // Test that header file is self-contained. #include -#include +#include #include #include #include @@ -195,7 +195,7 @@ public: void testFreeFunctions() { { - request m; + request m; m.method = "GET"; m.url = "/"; m.version = 11; @@ -213,7 +213,7 @@ public: void testPrepare() { - request m; + request m; m.version = 10; BEAST_EXPECT(! is_upgrade(m)); m.fields.insert("Transfer-Encoding", "chunked"); diff --git a/test/http/message_fuzz.hpp b/test/http/message_fuzz.hpp index c0780a08..9317add0 100644 --- a/test/http/message_fuzz.hpp +++ b/test/http/message_fuzz.hpp @@ -9,7 +9,6 @@ #define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP #include -#include #include #include #include diff --git a/test/http/message_parser.cpp b/test/http/message_parser.cpp new file mode 100644 index 00000000..de3842d9 --- /dev/null +++ b/test/http/message_parser.cpp @@ -0,0 +1,244 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "test_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class message_parser_test + : public beast::unit_test::suite + , public beast::test::enable_yield_to +{ +public: + template + void + testMatrix(std::string const& s, Pred const& pred) + { + beast::test::string_istream ss{get_io_service(), s}; + error_code ec; + #if 0 + streambuf dynabuf; + #else + flat_streambuf dynabuf{1024}; + #endif + message m; + read(ss, dynabuf, m, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + pred(m); + } + + void + testRead() + { + testMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "\r\n" + "*******", + [&](message const& m) + { + BEAST_EXPECTS(m.body == "*******", + "body='" + m.body + "'"); + } + ); + testMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\n" + "*****\r\n" + "2;a;b=1;c=\"2\"\r\n" + "--\r\n" + "0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n" + "\r\n", + [&](message const& m) + { + BEAST_EXPECT(m.body == "*****--"); + } + ); + testMatrix( + "HTTP/1.0 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****", + [&](message const& m) + { + BEAST_EXPECT(m.body == "*****"); + } + ); + testMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "\r\n", + [&](message const& m) + { + } + ); + testMatrix( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "X: \t x \t \r\n" + "\r\n", + [&](message const& m) + { + BEAST_EXPECT(m.fields["X"] == "x"); + } + ); + } + + void + testParse() + { + using boost::asio::buffer; + { + error_code ec; + beast::test::string_istream is{ + get_io_service(), + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"}; + flat_streambuf sb{1024}; + message_parser p; + read(is, sb, p, ec); + auto const& m = p.get(); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(m.method == "GET"); + BEAST_EXPECT(m.url == "/"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m.fields["User-Agent"] == "test"); + BEAST_EXPECT(m.body == "*"); + } +#if 0 + { + // test partial parsing of final chunk + // parse through the chunk body + beast::test::string_istream is{ + get_io_service(), ""}; + streambuf sb; + sb << + "PUT / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*"; + error_code ec; + message_parser p; + read(is, sb, p, ec); + BEAST_EXPECT(sb.size() == 0); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(!p.is_complete()); + BEAST_EXPECT(p.get().body == "*"); + sb << "\r\n0;d;e=3;f=\"4\"\r\n" + "Expires: never\r\n" + "MD5-Fingerprint: -\r\n"; + // incomplete parse, missing the final crlf + BEAST_EXPECT(p.write(sb.data(), ec) == 0); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(!p.is_complete()); + sb << "\r\n"; // final crlf to end message + BEAST_EXPECT(p.write(sb.data(), ec) == sb.size()); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } + { + error_code ec; + message_parser p; + std::string const s = + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*"; + p.write(buffer(s), ec); + auto const& m = p.get(); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + BEAST_EXPECT(m.status == 200); + BEAST_EXPECT(m.reason == "OK"); + BEAST_EXPECT(m.version == 11); + BEAST_EXPECT(m.fields["Server"] == "test"); + BEAST_EXPECT(m.body == "*"); + } + // skip body + { + error_code ec; + message_parser p; + std::string const s = + "HTTP/1.1 200 Connection Established\r\n" + "Proxy-Agent: Zscaler/5.1\r\n" + "\r\n"; + p.skip_body(); + p.write(buffer(s), ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p.is_complete()); + } +#endif + } + + void + testExpect100Continue() + { + test::string_istream ss{ios_, + "POST / HTTP/1.1\r\n" + "Expect: 100-continue\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****"}; + streambuf sb; + error_code ec; + header_parser p0; + auto const bytes_used = + read_some(ss, sb, p0, ec); + sb.consume(bytes_used); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p0.state() != parse_state::header); + BEAST_EXPECT(! p0.is_complete()); + message_parser p1{std::move(p0)}; + read(ss, sb, p1, ec); + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(p1.get().body == "*****"); + } + + void + run() override + { + testRead(); + testParse(); + testExpect100Continue(); + } +}; + +BEAST_DEFINE_TESTSUITE(message_parser,http,beast); + +} // http +} // beast + diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp index 83ee3665..f2fbe62d 100644 --- a/test/http/nodejs_parser.hpp +++ b/test/http/nodejs_parser.hpp @@ -740,11 +740,6 @@ 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: @@ -752,7 +747,6 @@ public: nodejs_parser() : http::nodejs_basic_parser(isRequest) - , r_(m_) { } @@ -763,12 +757,6 @@ public: return started_; } - message_type - release() - { - return std::move(m_); - } - private: friend class http::nodejs_basic_parser; @@ -781,7 +769,6 @@ private: void on_field(std::string const& field, std::string const& value) { - m_.fields.insert(field, value); } void @@ -798,9 +785,6 @@ private: int major, int minor, bool /*keep_alive*/, bool /*upgrade*/, std::true_type) { - m_.method = detail::method_to_string(method); - m_.url = url; - m_.version = major * 10 + minor; return true; } @@ -819,7 +803,7 @@ private: return on_request(method, url, major, minor, keep_alive, upgrade, std::integral_constant< - bool, message_type::is_request>{}); + bool, isRequest>{}); } bool @@ -828,10 +812,6 @@ private: std::true_type) { beast::detail::ignore_unused(keep_alive, upgrade); - m_.status = status; - m_.reason = reason; - m_.version = major * 10 + minor; - // VFALCO TODO return expect_body_ return true; } @@ -848,14 +828,13 @@ private: { return on_response( status, reason, major, minor, keep_alive, upgrade, - std::integral_constant{}); + std::integral_constant{}); } void on_body(void const* data, std::size_t size, error_code& ec) { - r_.write(data, size, ec); } void diff --git a/test/http/parse.cpp b/test/http/parse.cpp deleted file mode 100644 index b15e9e43..00000000 --- a/test/http/parse.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include diff --git a/test/http/parse_error.cpp b/test/http/parse_error.cpp deleted file mode 100644 index ea23bc1a..00000000 --- a/test/http/parse_error.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include - -namespace beast { -namespace http { - -class parse_error_test : public unit_test::suite -{ -public: - void check(char const* name, parse_error ev) - { - auto const ec = make_error_code(ev); - BEAST_EXPECT(std::string{ec.category().name()} == name); - BEAST_EXPECT(! ec.message().empty()); - BEAST_EXPECT(std::addressof(ec.category()) == - std::addressof(detail::get_parse_error_category())); - BEAST_EXPECT(detail::get_parse_error_category().equivalent( - static_cast::type>(ev), - ec.category().default_error_condition( - static_cast::type>(ev)))); - BEAST_EXPECT(detail::get_parse_error_category().equivalent( - ec, static_cast::type>(ev))); - } - - void run() override - { - check("http", parse_error::connection_closed); - check("http", parse_error::bad_method); - check("http", parse_error::bad_uri); - check("http", parse_error::bad_version); - check("http", parse_error::bad_crlf); - check("http", parse_error::bad_status); - check("http", parse_error::bad_reason); - check("http", parse_error::bad_field); - check("http", parse_error::bad_value); - check("http", parse_error::bad_content_length); - check("http", parse_error::illegal_content_length); - check("http", parse_error::invalid_chunk_size); - check("http", parse_error::invalid_ext_name); - check("http", parse_error::invalid_ext_val); - check("http", parse_error::header_too_big); - check("http", parse_error::body_too_big); - check("http", parse_error::short_read); - } -}; - -BEAST_DEFINE_TESTSUITE(parse_error,http,beast); - -} // http -} // beast diff --git a/test/http/parser_bench.cpp b/test/http/parser_bench.cpp index 768d267d..5015738e 100644 --- a/test/http/parser_bench.cpp +++ b/test/http/parser_bench.cpp @@ -9,6 +9,7 @@ #include "message_fuzz.hpp" #include +#include #include #include #include @@ -64,9 +65,39 @@ public: return v; } + template + static + std::size_t + feed(ConstBufferSequence const& buffers, + basic_parser& parser, + error_code& ec) + { + using boost::asio::buffer_size; + beast::consuming_buffers< + ConstBufferSequence> cb{buffers}; + std::size_t used = 0; + for(;;) + { + auto const n = + parser.write(cb, ec); + if(ec) + return 0; + if(n == 0) + break; + cb.consume(n); + used += n; + if(parser.is_complete()) + break; + if(buffer_size(cb) == 0) + break; + } + return used; + } + template void - testParser(std::size_t repeat, corpus const& v) + testParser1(std::size_t repeat, corpus const& v) { while(repeat--) for(auto const& sb : v) @@ -79,6 +110,21 @@ public: } } + template + void + testParser2(std::size_t repeat, corpus const& v) + { + while(repeat--) + for(auto const& sb : v) + { + Parser p; + error_code ec; + feed(sb.data(), p, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + log << to_string(sb.data()) << std::endl; + } + } + template void timedTest(std::size_t repeat, std::string const& name, Function&& f) @@ -98,21 +144,92 @@ public: } template - struct null_parser : basic_parser_v1> + struct null_parser : + basic_parser> { }; + template + struct bench_parser : basic_parser< + isRequest, false, bench_parser> + { + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + void + on_request(boost::string_ref const&, + boost::string_ref const&, + int, error_code&) + { + } + + void + on_response(int, + boost::string_ref const&, + int, error_code&) + { + } + + void + on_field(boost::string_ref const&, + boost::string_ref const&, + error_code&) + { + } + + void + on_header(error_code& ec) + { + } + + void + on_body(error_code& ec) + { + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + } + + void + on_data(boost::string_ref const&, + error_code& ec) + { + } + + void + on_chunk(std::uint64_t, + boost::string_ref const&, + error_code&) + { + } + + void + on_body(boost::string_ref const&, + error_code&) + { + } + + void + on_complete(error_code&) + { + } + }; + void testSpeed() { static std::size_t constexpr Trials = 3; - static std::size_t constexpr Repeat = 50; + static std::size_t constexpr Repeat = 500; log << "sizeof(request parser) == " << - sizeof(basic_parser_v1>) << '\n'; + sizeof(null_parser) << '\n'; log << "sizeof(response parser) == " << - sizeof(basic_parser_v1>)<< '\n'; + sizeof(null_parser)<< '\n'; testcase << "Parser speed test, " << ((Repeat * size_ + 512) / 1024) << "KB in " << @@ -121,20 +238,20 @@ public: timedTest(Trials, "nodejs_parser", [&] { - testParser>( Repeat, creq_); - testParser>( Repeat, cres_); }); - timedTest(Trials, "http::basic_parser_v1", + timedTest(Trials, "http::basic_parser", [&] { - testParser>( + testParser2 >( Repeat, creq_); - testParser>( Repeat, cres_); }); diff --git a/test/http/parser_v1.cpp b/test/http/parser_v1.cpp deleted file mode 100644 index 20c207d7..00000000 --- a/test/http/parser_v1.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// -// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -class parser_v1_test - : public beast::unit_test::suite - , public test::enable_yield_to -{ -public: - void - testParse() - { - using boost::asio::buffer; - { - error_code ec; - parser_v1>> p; - std::string const s = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - auto m = p.release(); - BEAST_EXPECT(m.method == "GET"); - BEAST_EXPECT(m.url == "/"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["User-Agent"] == "test"); - BEAST_EXPECT(m.body == "*"); - } - { - error_code ec; - parser_v1>> p; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - auto m = p.release(); - BEAST_EXPECT(m.status == 200); - BEAST_EXPECT(m.reason == "OK"); - BEAST_EXPECT(m.version == 11); - BEAST_EXPECT(m.fields["Server"] == "test"); - BEAST_EXPECT(m.body == "*"); - } - // skip body - { - error_code ec; - parser_v1 p; - std::string const s = - "HTTP/1.1 200 Connection Established\r\n" - "Proxy-Agent: Zscaler/5.1\r\n" - "\r\n"; - p.set_option(skip_body{true}); - p.write(buffer(s), ec); - BEAST_EXPECT(! ec); - BEAST_EXPECT(p.complete()); - } - } - - void - testWithBody() - { - std::string const raw = - "GET / HTTP/1.1\r\n" - "User-Agent: test\r\n" - "Content-Length: 1\r\n" - "\r\n" - "*"; - test::string_istream ss{ - ios_, raw, raw.size() - 1}; - - streambuf rb; - header_parser_v1 p0; - parse(ss, rb, p0); - request_header const& reqh = p0.get(); - BEAST_EXPECT(reqh.method == "GET"); - BEAST_EXPECT(reqh.url == "/"); - BEAST_EXPECT(reqh.version == 11); - BEAST_EXPECT(reqh.fields["User-Agent"] == "test"); - BEAST_EXPECT(reqh.fields["Content-Length"] == "1"); - parser_v1 p = - with_body(p0); - BEAST_EXPECT(p.get().method == "GET"); - BEAST_EXPECT(p.get().url == "/"); - BEAST_EXPECT(p.get().version == 11); - BEAST_EXPECT(p.get().fields["User-Agent"] == "test"); - BEAST_EXPECT(p.get().fields["Content-Length"] == "1"); - parse(ss, rb, p); - request req = p.release(); - BEAST_EXPECT(req.body == "*"); - } - - void - testRegressions() - { - using boost::asio::buffer; - - // consecutive empty header values - { - error_code ec; - parser_v1 p; - std::string const s = - "GET / HTTP/1.1\r\n" - "X1:\r\n" - "X2:\r\n" - "X3:x\r\n" - "\r\n"; - p.write(buffer(s), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - BEAST_EXPECT(p.complete()); - auto const msg = p.release(); - BEAST_EXPECT(msg.fields.exists("X1")); - BEAST_EXPECT(msg.fields["X1"] == ""); - BEAST_EXPECT(msg.fields.exists("X2")); - BEAST_EXPECT(msg.fields["X2"] == ""); - BEAST_EXPECT(msg.fields.exists("X3")); - BEAST_EXPECT(msg.fields["X3"] == "x"); - } - } - - void run() override - { - testParse(); - testWithBody(); - testRegressions(); - } -}; - -BEAST_DEFINE_TESTSUITE(parser_v1,http,beast); - -} // http -} // beast diff --git a/test/http/read.cpp b/test/http/read.cpp index 54356d4f..3e80b8eb 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -8,10 +8,12 @@ // Test that header file is self-contained. #include -#include "fail_parser.hpp" +#include "test_parser.hpp" #include +#include #include +#include #include #include #include @@ -27,62 +29,9 @@ class read_test , public test::enable_yield_to { public: - struct fail_body - { - class reader; - - class value_type - { - friend class reader; - - std::string s_; - test::fail_counter& fc_; - - public: - explicit - value_type(test::fail_counter& fc) - : fc_(fc) - { - } - - value_type& - operator=(std::string s) - { - s_ = std::move(s); - return *this; - } - }; - - class reader - { - value_type& body_; - - public: - template - explicit - reader(message& msg) noexcept - : body_(msg.body) - { - } - - void - init(error_code& ec) noexcept - { - body_.fc_.fail(ec); - } - - void - write(void const* data, - std::size_t size, error_code& ec) noexcept - { - if(body_.fc_.fail(ec)) - return; - } - }; - }; - template - void failMatrix(char const* s, yield_context do_yield) + void + failMatrix(char const* s, yield_context do_yield) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -97,9 +46,9 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - parse(fs, sb, p, ec); + read(fs, sb, p, ec); if(! ec) break; } @@ -113,9 +62,9 @@ public: test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - parse(fs, sb, p, ec); + read(fs, sb, p, ec); if(! ec) break; } @@ -128,9 +77,9 @@ public: test::fail_counter fc(n); test::fail_stream< test::string_istream> fs{fc, ios_, ""}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - async_parse(fs, sb, p, do_yield[ec]); + async_read(fs, sb, p, do_yield[ec]); if(! ec) break; } @@ -144,23 +93,9 @@ public: test::fail_counter fc(n); test::fail_stream fs{ fc, ios_, std::string{s + pre, len - pre}}; - fail_parser p(fc); + test_parser p(fc); error_code ec; - async_parse(fs, sb, p, do_yield[ec]); - if(! ec) - break; - } - BEAST_EXPECT(n < limit); - for(n = 0; n < limit; ++n) - { - streambuf sb; - sb.commit(buffer_copy( - sb.prepare(len), buffer(s, len))); - test::fail_counter fc{n}; - test::string_istream ss{ios_, s}; - parser_v1 p{fc}; - error_code ec; - parse(ss, sb, p, ec); + async_read(fs, sb, p, do_yield[ec]); if(! ec) break; } @@ -173,8 +108,8 @@ public: { streambuf sb; test::string_istream ss(ios_, "GET / X"); - parser_v1 p; - parse(ss, sb, p); + message_parser p; + read(ss, sb, p); fail(); } catch(std::exception const&) @@ -245,52 +180,6 @@ public: failMatrix(res[i], do_yield); } - void testReadHeaders(yield_context do_yield) - { - static std::size_t constexpr limit = 100; - std::size_t n; - - for(n = 0; n < limit; ++n) - { - test::fail_stream fs{n, ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - }; - request_header m; - try - { - streambuf sb; - read(fs, sb, m); - break; - } - catch(std::exception const&) - { - } - } - BEAST_EXPECT(n < limit); - - for(n = 0; n < limit; ++n) - { - test::fail_stream fs(n, ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "User-Agent: test\r\n" - "Content-Length: 0\r\n" - "\r\n" - ); - request_header m; - error_code ec; - streambuf sb; - async_read(fs, sb, m, do_yield[ec]); - if(! ec) - break; - } - BEAST_EXPECT(n < limit); - } - void testRead(yield_context do_yield) { static std::size_t constexpr limit = 100; @@ -355,22 +244,23 @@ public: BEAST_EXPECT(n < limit); } - void testEof(yield_context do_yield) + void + testEof(yield_context do_yield) { { streambuf sb; test::string_istream ss(ios_, ""); - parser_v1 p; + message_parser p; error_code ec; - parse(ss, sb, p, ec); + read(ss, sb, p, ec); BEAST_EXPECT(ec == boost::asio::error::eof); } { streambuf sb; test::string_istream ss(ios_, ""); - parser_v1 p; + message_parser p; error_code ec; - async_parse(ss, sb, p, do_yield[ec]); + async_read(ss, sb, p, do_yield[ec]); BEAST_EXPECT(ec == boost::asio::error::eof); } } @@ -424,12 +314,12 @@ public: } } - void run() override + void + run() override { testThrow(); yield_to(&read_test::testFailures, this); - yield_to(&read_test::testReadHeaders, this); yield_to(&read_test::testRead, this); yield_to(&read_test::testEof, this); diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp index 1e486bfa..a02b2cc7 100644 --- a/test/http/rfc7230.cpp +++ b/test/http/rfc7230.cpp @@ -13,9 +13,10 @@ #include #include +#include + namespace beast { namespace http { -namespace test { class rfc7230_test : public beast::unit_test::suite { @@ -135,9 +136,9 @@ public: BEAST_EXPECTS(got == good, fmt(got)); }; /* - ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) - ext = token param-list - param-list = *( OWS ";" OWS param ) + ext-basic_parsed_list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-basic_parsed_list + param-basic_parsed_list = *( OWS ";" OWS param ) param = token OWS "=" OWS ( token / quoted-string ) */ cs(",", ""); @@ -237,17 +238,120 @@ public: cs("x y", "x"); } + template + static + std::vector + to_vector(boost::string_ref const& in) + { + std::vector v; + detail::basic_parsed_list list{in}; + for(auto const& s : + detail::basic_parsed_list{in}) + v.emplace_back(s.data(), s.size()); + return v; + } + + template + void + validate(boost::string_ref const& in, + std::vector const& v) + { + BEAST_EXPECT(to_vector(in) == v); + } + + template + void + good(boost::string_ref const& in) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + } + + template + void + good(boost::string_ref const& in, + std::vector const& v) + { + BEAST_EXPECT(validate_list( + detail::basic_parsed_list{in})); + validate(in, v); + } + + template + void + bad(boost::string_ref const& in) + { + BEAST_EXPECT(! validate_list( + detail::basic_parsed_list{in})); + } + + void + testOptTokenList() + { + /* + #token = [ ( "," / token ) *( OWS "," [ OWS token ] ) ] + */ + using type = detail::opt_token_list_policy; + + good("", {}); + good(" ", {}); + good("\t", {}); + good(" \t", {}); + good(",", {}); + good(",,", {}); + good(", ,", {}); + good(",\t,", {}); + good(", \t,", {}); + good(", \t, ", {}); + good(", \t,\t", {}); + good(", \t, \t", {}); + + good("x", {"x"}); + good(" x", {"x"}); + good("x,,", {"x"}); + good("x, ,", {"x"}); + good("x,, ", {"x"}); + good("x,,,", {"x"}); + + good("x,y", {"x","y"}); + good("x ,y", {"x","y"}); + good("x\t,y", {"x","y"}); + good("x \t,y", {"x","y"}); + good(" x,y", {"x","y"}); + good(" x,y ", {"x","y"}); + good(",x,y", {"x","y"}); + good("x,y,", {"x","y"}); + good(",,x,y", {"x","y"}); + good(",x,,y", {"x","y"}); + good(",x,y,", {"x","y"}); + good("x ,, y", {"x","y"}); + good("x , ,y", {"x","y"}); + + good("x,y,z", {"x","y","z"}); + + bad("("); + bad("x("); + bad("(x"); + bad(",("); + bad("(,"); + bad("x,("); + bad("(,x"); + bad("x y"); + } + void run() { + testOptTokenList(); +#if 0 testParamList(); testExtList(); testTokenList(); +#endif } }; BEAST_DEFINE_TESTSUITE(rfc7230,http,beast); -} // test } // http } // beast diff --git a/test/http/streambuf_body.cpp b/test/http/streambuf_body.cpp index 2b5ed5f7..9e796c6b 100644 --- a/test/http/streambuf_body.cpp +++ b/test/http/streambuf_body.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -34,11 +34,12 @@ public: "\r\n" "xyz"; test::string_istream ss(ios_, s); - parser_v1 p; + message_parser p; streambuf sb; - parse(ss, sb, p); - BEAST_EXPECT(to_string(p.get().body.data()) == "xyz"); - BEAST_EXPECT(boost::lexical_cast(p.get()) == s); + read(ss, sb, p); + auto const& m = p.get(); + BEAST_EXPECT(to_string(m.body.data()) == "xyz"); + BEAST_EXPECT(boost::lexical_cast(m) == s); } }; diff --git a/test/http/test_parser.hpp b/test/http/test_parser.hpp new file mode 100644 index 00000000..7d2611c2 --- /dev/null +++ b/test/http/test_parser.hpp @@ -0,0 +1,147 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_TEST_PARSER_HPP +#define BEAST_HTTP_TEST_PARSER_HPP + +#include +#include + +namespace beast { +namespace http { + +template +class test_parser + : public basic_parser> +{ + test::fail_counter* fc_ = nullptr; + +public: + using mutable_buffers_type = + boost::asio::mutable_buffers_1; + + int status = 0; + int version = 0; + std::string method; + std::string path; + std::string reason; + std::string body; + bool got_on_begin = false; + bool got_on_field = false; + bool got_on_header = false; + bool got_on_body = false; + bool got_content_length = false; + bool got_on_prepare = false; + bool got_on_commit = false; + bool got_on_chunk = false; + bool got_on_complete = false; + + test_parser() = default; + + explicit + test_parser(test::fail_counter& fc) + : fc_(&fc) + { + } + + void + on_request( + boost::string_ref const& method_, + boost::string_ref const& path_, + int version_, error_code& ec) + { + method = std::string( + method_.data(), method_.size()); + path = std::string( + path_.data(), path_.size()); + version = version_; + got_on_begin = true; + if(fc_) + fc_->fail(ec); + } + + void + on_response(int status_, + boost::string_ref const& reason_, + int version_, error_code& ec) + { + status = status_; + reason = std::string( + reason_.data(), reason_.size()); + version = version_; + got_on_begin = true; + if(fc_) + fc_->fail(ec); + } + + void + on_field(boost::string_ref const&, + boost::string_ref const&, + error_code& ec) + { + got_on_field = true; + if(fc_) + fc_->fail(ec); + } + + void + on_header(error_code& ec) + { + got_on_header = true; + if(fc_) + fc_->fail(ec); + } + + void + on_body(error_code& ec) + { + got_on_body = true; + if(fc_) + fc_->fail(ec); + } + + void + on_body(std::uint64_t content_length, + error_code& ec) + { + got_on_body = true; + got_content_length = true; + if(fc_) + fc_->fail(ec); + } + + void + on_data(boost::string_ref const& s, + error_code& ec) + { + body.append(s.data(), s.size()); + } + + void + on_chunk(std::uint64_t, + boost::string_ref const&, + error_code& ec) + { + got_on_chunk = true; + if(fc_) + fc_->fail(ec); + } + + void + on_complete(error_code& ec) + { + got_on_complete = true; + if(fc_) + fc_->fail(ec); + } +}; + +} // http +} // beast + +#endif diff --git a/test/http/write.cpp b/test/http/write.cpp index 79fe11ab..2ffe7a15 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -510,7 +509,7 @@ public: } // upgrade HTTP/1.1 { - message m; + message m; m.method = "GET"; m.url = "/"; m.version = 11; diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index 47e0c833..867366d4 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -174,7 +174,7 @@ public: for(n = 0; n < limit; ++n) { // valid - http::request req; + http::request_header req; req.method = "GET"; req.url = "/"; req.version = 11;