diff --git a/CHANGELOG.md b/CHANGELOG.md index 16fa22e7..8ee5423e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Version 73: + +HTTP: + +* basic_parser optimizations + +-------------------------------------------------------------------------------- + Version 72: HTTP: @@ -14,6 +22,7 @@ WebSocket: * Add websocket-server-async example + -------------------------------------------------------------------------------- Version 71: diff --git a/CMakeLists.txt b/CMakeLists.txt index bf658271..ccc4617b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,7 @@ endfunction() if ("${VARIANT}" STREQUAL "coverage") if (MSVC) else() - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -fprofile-arcs -ftest-coverage") set (CMAKE_BUILD_TYPE RELWITHDEBINFO) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") endif() @@ -125,7 +125,7 @@ elseif ("${VARIANT}" STREQUAL "ubasan") if (MSVC) else() set (CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -DBEAST_NO_SLOW_TESTS=1 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist.supp") + "${CMAKE_CXX_FLAGS} -DBEAST_NO_SLOW_TESTS=1 -msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/scripts/blacklist.supp") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined") set (CMAKE_BUILD_TYPE RELWITHDEBINFO) endif() diff --git a/Jamroot b/Jamroot index f18adf51..55edf0a1 100644 --- a/Jamroot +++ b/Jamroot @@ -55,7 +55,7 @@ if [ os.name ] = MACOSX variant coverage : release : - "-fprofile-arcs -ftest-coverage" + "-msse4.2 -fprofile-arcs -ftest-coverage" "-lgcov" ; @@ -63,7 +63,7 @@ variant ubasan : release : - "-funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=scripts/blacklist.supp" + "-msse4.2 -funsigned-char -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize-blacklist=scripts/blacklist.supp" "-fsanitize=address,undefined" ; diff --git a/include/beast/core/detail/cpu_info.hpp b/include/beast/core/detail/cpu_info.hpp new file mode 100644 index 00000000..6a69f0e5 --- /dev/null +++ b/include/beast/core/detail/cpu_info.hpp @@ -0,0 +1,108 @@ +// +// Copyright (c) 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_DETAIL_CPU_INFO_HPP +#define BEAST_DETAIL_CPU_INFO_HPP + +#ifndef BEAST_NO_INTRINSICS +# if defined(_MSC_VER) || \ + (defined(__i386__) && defined(__PIC__) && \ + defined(__GNUC__) && ! defined(__clang__)) || \ + defined(__i386__) +# define BEAST_NO_INTRINSICS 0 +# else +# define BEAST_NO_INTRINSICS 1 +# endif +#endif + +#if ! BEAST_NO_INTRINSICS + +#if defined(_MSC_VER) +#include // __cpuid +#endif + +namespace beast { +namespace detail { + +/* Portions from Boost, + Copyright Andrey Semashev 2007 - 2015. +*/ +template +void +cpuid( + std::uint32_t id, + std::uint32_t& eax, + std::uint32_t& ebx, + std::uint32_t& ecx, + std::uint32_t& edx) +{ +#if defined(_MSC_VER) + int regs[4]; + __cpuid(regs, id); + eax = regs[0]; + ebx = regs[1]; + ecx = regs[2]; + edx = regs[3]; + +#elif defined(__i386__) && defined(__PIC__) && \ + defined(__GNUC__) && ! defined(__clang__) + // We have to backup ebx in 32 bit PIC code because it is reserved by the ABI + uint32_t ebx_backup; + __asm__ __volatile__ + ( + "movl %%ebx, %0\n\t" + "movl %1, %%ebx\n\t" + "cpuid\n\t" + "movl %%ebx, %1\n\t" + "movl %0, %%ebx\n\t" + : "=m" (ebx_backup), "+m"(ebx), "+a"(eax), "+c"(ecx), "+d"(edx) + ); + +#elif defined(__i386__) + __asm__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(id) : "ebx"); + +#else +# error Unknown compiler! + +#endif +} + +struct cpu_info +{ + bool sse42 = false; + + cpu_info(); +}; + +inline +cpu_info:: +cpu_info() +{ + std::uint32_t eax, ebx, ecx, edx; + + cpuid(0, eax, ebx, ecx, edx); + if(eax >= 1) + { + cpuid(1, eax, ebx, ecx, edx); + sse42 = (ecx & (1 << 20)) != 0; + } +} + +template +cpu_info const& +get_cpu_info() +{ + static cpu_info const ci; + return ci; +} + +} // detail +} // beast + +#endif + +#endif diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp index 46303c17..16de78ef 100644 --- a/include/beast/http/basic_parser.hpp +++ b/include/beast/http/basic_parser.hpp @@ -75,9 +75,6 @@ class basic_parser template friend class basic_parser; - // limit on the size of the obs-fold buffer - static std::size_t constexpr max_obs_fold = 4096; - // limit on the size of the stack flat buffer static std::size_t constexpr max_stack_buffer = 8192; @@ -129,6 +126,7 @@ class basic_parser std::size_t skip_ = 0; // resume search here std::uint32_t header_limit_ = 8192; // max header size + unsigned short status_; // response status state state_ = // initial state state::nothing_yet; unsigned f_ = 0; // flags @@ -212,7 +210,7 @@ public: bool is_header_done() const { - return state_ > state::header; + return state_ > state::fields; } /** Returns `true` if the message is an upgrade message. @@ -312,6 +310,9 @@ public: input. If the end of the header is not found within the limit of the header size, the error @ref error::header_limit is returned by @ref put. + + Setting the limit after any header octets have been parsed + results in undefined behavior. */ void header_limit(std::uint32_t v) @@ -452,15 +453,31 @@ private: error_code& ec); void - parse_header(char const*& p, - std::size_t n, error_code& ec); + maybe_need_more( + char const* p, std::size_t n, + error_code& ec); void - parse_header(char const*& p, char const* term, + parse_start_line( + char const*& p, char const* last, + error_code& ec, std::true_type); + + void + parse_start_line( + char const*& p, char const* last, + error_code& ec, std::false_type); + + void + parse_fields( + char const*& p, char const* last, + error_code& ec); + + void + finish_header( error_code& ec, std::true_type); void - parse_header(char const*& p, char const* term, + finish_header( error_code& ec, std::false_type); void @@ -479,10 +496,6 @@ private: parse_chunk_body(char const*& p, std::size_t n, error_code& ec); - void - parse_fields(char const*& p, - char const* last, error_code& ec); - void do_field(field f, string_view value, error_code& ec); diff --git a/include/beast/http/detail/basic_parser.hpp b/include/beast/http/detail/basic_parser.hpp index c3c534cf..6b052389 100644 --- a/include/beast/http/detail/basic_parser.hpp +++ b/include/beast/http/detail/basic_parser.hpp @@ -8,9 +8,12 @@ #ifndef BEAST_HTTP_DETAIL_BASIC_PARSER_HPP #define BEAST_HTTP_DETAIL_BASIC_PARSER_HPP +#include #include +#include #include #include +#include #include #include #include @@ -47,25 +50,41 @@ * IN THE SOFTWARE. */ +#if ! BEAST_NO_INTRINSICS +# ifdef BOOST_MSVC +# include +# else +# include +# endif +#endif + 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: +#if ! BEAST_NO_INTRINSICS + bool sse42_; + + basic_parser_base() + : sse42_(beast::detail::get_cpu_info().sse42) + { + } +#endif + + // limit on the size of the obs-fold buffer + // + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values + // + static std::size_t constexpr max_obs_fold = 4096; + enum class state { nothing_yet = 0, - header, + start_line, + fields, body0, body, body_to_eof0, @@ -104,59 +123,6 @@ protected: 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 @@ -197,7 +163,37 @@ protected: bool is_print(char c) { - return static_cast(c-33) < 94; + return static_cast(c-32) < 95; + } + + template + static + FwdIt + trim_front(FwdIt it, FwdIt const& end) + { + while(it != end) + { + if(*it != ' ' && *it != '\t') + break; + ++it; + } + return it; + } + + template + static + RanIt + trim_back( + RanIt it, RanIt const& first) + { + while(it != first) + { + auto const c = it[-1]; + if(c != ' ' && c != '\t') + break; + --it; + } + return it; } static @@ -208,28 +204,214 @@ protected: std::size_t>(last - first)}; } - template - static - bool - strieq(string_view s1, - string_view s2) + //-------------------------------------------------------------------------- + + std::pair + find_fast( + char const* buf, + char const* buf_end, + char const* ranges, + size_t ranges_size) { - 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; + bool found = false; + + #if ! BEAST_NO_INTRINSICS + if(BOOST_LIKELY(sse42_)) + { + if(BOOST_LIKELY(buf_end - buf >= 16)) + { + __m128i ranges16 = _mm_loadu_si128((__m128i const*)ranges); + std::size_t left = (buf_end - buf) & ~15; + do + { + __m128i b16 = _mm_loadu_si128((__m128i const*)buf); + int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, + _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if(BOOST_UNLIKELY(r != 16)) + { + buf += r; + found = true; + break; + } + buf += 16; + left -= 16; + } + while(BOOST_LIKELY(left != 0)); + } + } + + #else + boost::ignore_unused(buf_end, ranges, ranges_size); + + #endif + return {buf, found}; } - template - bool - strieq(const char (&s1)[N], - string_view s2) + // VFALCO Can SIMD help this? + static + char const* + find_eol( + char const* it, char const* last, + error_code& ec) { - return strieq({s1, N-1}, s2); + for(;;) + { + if(it == last) + { + ec.assign(0, ec.category()); + return nullptr; + } + if(*it == '\r') + { + if(++it == last) + { + ec.assign(0, ec.category()); + return nullptr; + } + if(*it != '\n') + { + ec = error::bad_line_ending; + return nullptr; + } + ec.assign(0, ec.category()); + 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* p, char const* last) + { + for(;;) + { + if(p + 4 > last) + return nullptr; + if(p[3] != '\n') + { + if(p[3] == '\r') + ++p; + else + p += 4; + } + else if(p[2] != '\r') + { + p += 4; + } + else if(p[1] != '\n') + { + p += 2; + } + else if(p[0] != '\r') + { + p += 2; + } + else + { + return p + 4; + } + } + } + + //-------------------------------------------------------------------------- + + char const* + parse_token_to_eol( + char const* p, + char const* last, + char const*& token_last, + error_code& ec) + { + #if ! BEAST_NO_INTRINSICS + static char const ranges1[] = + "\0\010" // allow HT */ + "\012\037" // allow SP and up to but not including DEL + "\177\177" // allow chars w. MSB set + ; + bool found; + std::tie(p, found) = find_fast( + p, last, ranges1, sizeof(ranges1) - 1); + if(found) + goto found_control; + #else + /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ + while(BOOST_LIKELY(last - p >= 8)) + { + #define BEAST_PARSE_TOKEN_TO_EOL_REPEAT() \ + do \ + { \ + if(BOOST_UNLIKELY( \ + ! is_print(*p))) \ + goto non_printable; \ + ++p; \ + } \ + while(0); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + BEAST_PARSE_TOKEN_TO_EOL_REPEAT(); + #undef BEAST_PARSE_TOKEN_TO_EOL_REPEAT + continue; + non_printable: + if((BOOST_LIKELY((unsigned char)*p < '\040') && + BOOST_LIKELY(*p != '\011')) || + BOOST_UNLIKELY(*p == '\177')) + goto found_control; + ++p; + } + #endif + for(;; ++p) + { + if(p >= last) + { + ec = error::need_more; + return p; + } + if(BOOST_UNLIKELY(! is_print(*p))) + if((BOOST_LIKELY(static_cast< + unsigned char>(*p) < '\040') && + BOOST_LIKELY(*p != '\011')) || + BOOST_UNLIKELY(*p == '\177')) + goto found_control; + } + found_control: + if(BOOST_LIKELY(*p == '\r')) + { + if(++p >= last) + { + ec = error::need_more; + return last; + } + if(*p++ != '\n') + { + ec = error::bad_line_ending; + return last; + } + token_last = p - 2; + } + #if 0 + // VFALCO This allows `\n` by itself + // to terminate a line + else if(*p == '\n') + { + token_last = p; + ++p; + } + #endif + else + { + // invalid character + return nullptr; + } + return p; } template @@ -284,190 +466,361 @@ protected: } static - string_view - parse_method(char const*& it) + void + parse_method( + char const*& it, char const* last, + string_view& result, error_code& ec) { + // parse token SP auto const first = it; - while(detail::is_tchar(*it)) - ++it; - return {first, static_cast< - string_view::size_type>( - it - first)}; - } - - static - string_view - parse_target(char const*& it) - { - auto const first = it; - while(is_pathchar(*it)) - ++it; + for(;; ++it) + { + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(! detail::is_tchar(*it)) + break; + } + if(it + 1 > last) + { + ec = error::need_more; + return; + } if(*it != ' ') - return {}; - return {first, static_cast< - string_view::size_type>( - it - first)}; + { + ec = error::bad_method; + return; + } + if(it == first) + { + // cannot be empty + ec = error::bad_method; + return; + } + result = make_string(first, it++); } static - string_view - parse_name(char const*& it) + void + parse_target( + char const*& it, char const* last, + string_view& result, error_code& ec) { + // parse target SP auto const first = it; - while(to_field_char(*it)) - ++it; - return {first, static_cast< - string_view::size_type>( - it - first)}; + for(;; ++it) + { + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(! is_pathchar(*it)) + break; + } + if(it + 1 > last) + { + ec = error::need_more; + return; + } + if(*it != ' ') + { + ec = error::bad_target; + return; + } + if(it == first) + { + // cannot be empty + ec = error::bad_target; + return; + } + result = make_string(first, it++); } static - int - parse_version(char const*& it) + void + parse_version( + char const*& it, char const* last, + int& result, error_code& ec) { - 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(it + 8 > last) + { + ec = error::need_more; + return; + } + if(*it++ != 'H') + { + ec = error::bad_version; + return; + } + if(*it++ != 'T') + { + ec = error::bad_version; + return; + } + if(*it++ != 'T') + { + ec = error::bad_version; + return; + } + if(*it++ != 'P') + { + ec = error::bad_version; + return; + } + if(*it++ != '/') + { + ec = error::bad_version; + return; + } 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; + { + ec = error::bad_version; + return; + } + result = 10 * (*it++ - '0'); + if(*it++ != '.') + { + ec = error::bad_version; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_version; + return; + } + result += *it++ - '0'; + } + + static + void + parse_status( + char const*& it, char const* last, + unsigned short& result, error_code& ec) + { + // parse 3(digit) SP + if(it + 4 > last) + { + ec = error::need_more; + return; + } + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result = 100 * (*it++ - '0'); + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result += 10 * (*it++ - '0'); + if(! is_digit(*it)) + { + ec = error::bad_status; + return; + } + result += *it++ - '0'; + if(*it++ != ' ') + { + ec = error::bad_status; + return; + } } - static - string_view - parse_reason(char const*& it) + void + parse_reason( + char const*& it, char const* last, + string_view& result, error_code& ec) { auto const first = it; - while(*it != '\r') + char const* token_last; + auto p = parse_token_to_eol( + it, last, token_last, ec); + if(ec) + return; + if(! p) { - if(! is_text(*it)) - return {}; - ++it; + ec = error::bad_reason; + return; } - return {first, static_cast< - std::size_t>(it - first)}; + result = make_string(first, token_last); + it = p; } - // VFALCO Can SIMD help this? - static - char const* - find_eol( - char const* it, char const* last, - error_code& ec) + template + void + parse_field( + char const*& p, + char const* last, + string_view& name, + string_view& value, + static_string& buf, + error_code& ec) { -#if 0 - // SLOWER - it = reinterpret_cast( - std::memchr(it, '\r', last - it)); - if(! it) - { - ec.assign(0, ec.category()); - return nullptr; - } - if(it + 2 > last) - { - ec.assign(0, ec.category()); - return nullptr; - } - if(it[1] != '\n') - { - ec = error::bad_line_ending; - return nullptr; - } - ec.assign(0, ec.category()); - return it + 2; -#else - for(;;) - { - if(it == last) - { - ec.assign(0, ec.category()); - return nullptr; - } - if(*it == '\r') - { - if(++it == last) - { - ec.assign(0, ec.category()); - return nullptr; - } - if(*it != '\n') - { - ec = error::bad_line_ending; - return nullptr; - } - ec.assign(0, ec.category()); - return ++it; - } - // VFALCO Should we handle the legacy case - // for lines terminated with a single '\n'? - ++it; - } -#endif - } + /* header-field = field-name ":" OWS field-value OWS - // VFALCO Can SIMD help this? - static - char const* - find_eom(char const* p, char const* last) - { + 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 + + token = 1* + CHAR = + sep = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + */ + static char const* is_token = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\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\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\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"; + + // name + BOOST_ALIGNMENT(16) static const char ranges1[] = + "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\377"; /* 0x7b-0xff */ + auto first = p; + bool found; + std::tie(p, found) = find_fast( + p, last, ranges1, sizeof(ranges1)-1); + if(! found && p >= last) + { + ec = error::need_more; + return; + } for(;;) { - if(p + 4 > last) - return nullptr; - if(p[3] != '\n') + if(*p == ':') + break; + if(! is_token[static_cast< + unsigned char>(*p)]) { - if(p[3] == '\r') - ++p; - else - p += 4; + ec = error::bad_field; + return; } - else if(p[2] != '\r') + ++p; + if(p >= last) { - p += 4; + ec = error::need_more; + return; } - else if(p[1] != '\n') + } + if(p == first) + { + // empty name + ec = error::bad_field; + return; + } + name = make_string(first, p); + ++p; // eat ':' + char const* token_last; + for(;;) + { + // eat leading ' ' and '\t' + for(;;++p) { - p += 2; + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(! (*p == ' ' || *p == '\t')) + break; } - else if(p[0] != '\r') + // parse to CRLF + first = p; + p = parse_token_to_eol(p, last, token_last, ec); + if(ec) + return; + if(! p) { - p += 2; + ec = error::bad_value; + return; } - else + // Look 1 char past the CRLF to handle obs-fold. + if(p + 1 > last) { - return p + 4; + ec = error::need_more; + return; } + token_last = + trim_back(token_last, first); + if(*p != ' ' && *p != '\t') + { + value = make_string(first, token_last); + return; + } + ++p; + if(token_last != first) + break; + } + buf.resize(0); + buf.append(first, token_last); + BOOST_ASSERT(! buf.empty()); + try + { + for(;;) + { + // eat leading ' ' and '\t' + for(;;++p) + { + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(! (*p == ' ' || *p == '\t')) + break; + } + // parse to CRLF + first = p; + p = parse_token_to_eol(p, last, token_last, ec); + if(ec) + return; + // Look 1 char past the CRLF to handle obs-fold. + if(p + 1 > last) + { + ec = error::need_more; + return; + } + token_last = trim_back(token_last, first); + if(first != token_last) + { + buf.push_back(' '); + buf.append(first, token_last); + } + if(*p != ' ' && *p != '\t') + { + value = {buf.data(), buf.size()}; + return; + } + ++p; + } + } + catch(std::length_error const&) + { + ec = error::header_limit; + return; } } }; diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp index 8a8d1f62..cd7093d9 100644 --- a/include/beast/http/detail/rfc7230.hpp +++ b/include/beast/http/detail/rfc7230.hpp @@ -167,42 +167,6 @@ is_qpchar(char c) return tab[static_cast(c)]; } -// converts to lower case, -// returns 0 if not a valid token char -// -inline -char -to_field_char(char c) -{ - /* token = 1* - CHAR = - sep = "(" | ")" | "<" | ">" | "@" - | "," | ";" | ":" | "\" | <"> - | "/" | "[" | "]" | "?" | "=" - | "{" | "}" | SP | HT - */ - static char constexpr tab[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '!', 0, '#', '$', '%', '&', '\'', 0, 0, '*', '+', 0, '-', '.', 0, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0, - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, '|', 0, '~', 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - BOOST_STATIC_ASSERT(sizeof(tab) == 256); - return tab[static_cast(c)]; -} - // converts to lower case, // returns 0 if not a valid text char // diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp index 4893efaf..9c94b7c5 100644 --- a/include/beast/http/impl/basic_parser.ipp +++ b/include/beast/http/impl/basic_parser.ipp @@ -21,40 +21,6 @@ namespace beast { namespace http { -namespace detail { - -template -inline -FwdIt -skip_ows2(FwdIt it, FwdIt const& end) -{ - while(it != end) - { - if(*it != ' ' && *it != '\t') - break; - ++it; - } - return it; -} - -template -inline -RanIt -skip_ows_rev2( - RanIt it, RanIt const& first) -{ - while(it != first) - { - auto const c = it[-1]; - if(c != ' ' && c != '\t') - break; - --it; - } - return it; -} - -} // detail - template basic_parser:: basic_parser() @@ -177,6 +143,7 @@ put(boost::asio::const_buffers_1 const& buffer, auto n = buffer_size(*buffer.begin()); auto const p0 = p; auto const p1 = p0 + n; + ec.assign(0, ec.category()); loop: switch(state_) { @@ -186,16 +153,67 @@ loop: ec = error::need_more; return 0; } - state_ = state::header; + state_ = state::start_line; BEAST_FALLTHROUGH; - case state::header: - parse_header(p, n, ec); + case state::start_line: + { + maybe_need_more(p, n, ec); if(ec) goto done; + parse_start_line(p, p + std::min( + header_limit_, n), ec, is_request{}); + if(ec) + { + if(ec == error::need_more) + { + if(n >= header_limit_) + { + ec = error::header_limit; + goto done; + } + if(p + 3 <= p1) + skip_ = static_cast< + std::size_t>(p1 - p - 3); + } + goto done; + } + BOOST_ASSERT(! is_done()); + n = static_cast(p1 - p); + if(p >= p1) + { + ec = error::need_more; + goto done; + } + BEAST_FALLTHROUGH; + } + + case state::fields: + maybe_need_more(p, n, ec); + if(ec) + goto done; + parse_fields(p, p + std::min( + header_limit_, n), ec); + if(ec) + { + if(ec == error::need_more) + { + if(n >= header_limit_) + { + ec = error::header_limit; + goto done; + } + if(p + 3 <= p1) + skip_ = static_cast< + std::size_t>(p1 - p - 3); + } + goto done; + } + finish_header(ec, is_request{}); break; case state::body0: + BOOST_ASSERT(! skip_); impl().on_body(content_length(), ec); if(ec) goto done; @@ -203,12 +221,14 @@ loop: BEAST_FALLTHROUGH; case state::body: + BOOST_ASSERT(! skip_); parse_body(p, n, ec); if(ec) goto done; break; case state::body_to_eof0: + BOOST_ASSERT(! skip_); impl().on_body(content_length(), ec); if(ec) goto done; @@ -216,6 +236,7 @@ loop: BEAST_FALLTHROUGH; case state::body_to_eof: + BOOST_ASSERT(! skip_); parse_body_to_eof(p, n, ec); if(ec) goto done; @@ -259,7 +280,8 @@ basic_parser:: put_eof(error_code& ec) { BOOST_ASSERT(got_some()); - if(state_ == state::header) + if( state_ == state::start_line || + state_ == state::fields) { ec = error::partial_message; return; @@ -300,9 +322,12 @@ template inline void basic_parser:: -parse_header(char const*& p, - std::size_t n, error_code& ec) +maybe_need_more( + char const* p, std::size_t n, + error_code& ec) { + if(skip_ == 0) + return; if( n > header_limit_) n = header_limit_; if(n < skip_ + 4) @@ -320,73 +345,57 @@ parse_header(char const*& p, ec = error::header_limit; return; } - ec = http::error::need_more; + ec = error::need_more; return; } skip_ = 0; - - parse_header(p, term, ec, - std::integral_constant{}); - if(ec) - return; - - impl().on_header(ec); - if(ec) - return; - if(state_ == state::complete) - { - impl().on_complete(ec); - if(ec) - return; - } } template +inline void basic_parser:: -parse_header(char const*& p, char const* term, +parse_start_line( + char const*& in, char const* last, error_code& ec, std::true_type) { /* request-line = method SP request-target SP HTTP-version CRLF method = token */ - auto const method = parse_method(p); - if(method.empty()) - { - ec = error::bad_method; - return; - } - if(*p++ != ' ') - { - ec = error::bad_method; - return; - } + auto p = in; - auto const target = parse_target(p); - if(target.empty()) - { - ec = error::bad_target; + string_view method; + parse_method(p, last, method, ec); + if(ec) return; - } - if(*p++ != ' ') - { - ec = error::bad_target; - return; - } - auto const version = parse_version(p); - if(version < 0) + string_view target; + parse_target(p, last, target, ec); + if(ec) + return; + + int version; + parse_version(p, last, version, ec); + if(ec) + return; + if(version < 10 || version > 11) { ec = error::bad_version; return; } - if(! parse_crlf(p)) + if(p + 2 > last) + { + ec = error::need_more; + return; + } + if(p[0] != '\r' || p[1] != '\n') { ec = error::bad_version; return; } + p += 2; if(version >= 11) f_ |= flagHTTP11; @@ -396,11 +405,114 @@ parse_header(char const*& p, char const* term, if(ec) return; - parse_fields(p, term, ec); + in = p; + state_ = state::fields; +} + +template +inline +void +basic_parser:: +parse_start_line( + char const*& in, char const* last, + 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 ) +*/ + auto p = in; + + int version; + parse_version(p, last, version, ec); if(ec) return; - BOOST_ASSERT(p == term); + if(version < 10 || version > 11) + { + ec = error::bad_version; + return; + } + // SP + if(p + 1 > last) + { + ec = error::need_more; + return; + } + if(*p++ != ' ') + { + ec = error::bad_version; + return; + } + + parse_status(p, last, status_, ec); + if(ec) + return; + + // parse reason CRLF + string_view reason; + parse_reason(p, last, reason, ec); + if(ec) + return; + + if(version >= 11) + f_ |= flagHTTP11; + + impl().on_response( + status_, reason, version, ec); + if(ec) + return; + + in = p; + state_ = state::fields; +} + +template +void +basic_parser:: +parse_fields(char const*& in, + char const* last, error_code& ec) +{ + string_view name; + string_view value; + // https://stackoverflow.com/questions/686217/maximum-on-http-header-values + static_string buf; + auto p = in; + for(;;) + { + if(p + 2 > last) + { + ec = error::need_more; + return; + } + if(p[0] == '\r') + { + if(p[1] != '\n') + ec = error::bad_line_ending; + in = p + 2; + return; + } + parse_field(p, last, name, value, buf, ec); + if(ec) + return; + auto const f = string_to_field(name); + do_field(f, value, ec); + if(ec) + return; + impl().on_field(f, name, value, ec); + if(ec) + return; + in = p; + } +} + +template +inline +void +basic_parser:: +finish_header(error_code& ec, std::true_type) +{ // RFC 7230 section 3.3 // https://tools.ietf.org/html/rfc7230#section-3.3 @@ -430,62 +542,31 @@ parse_header(char const*& p, char const* term, len_ = 0; state_ = state::complete; } + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } } template +inline void basic_parser:: -parse_header(char const*& p, char const* term, - error_code& ec, std::false_type) +finish_header(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 ) -*/ - auto const version = parse_version(p); - if(version < 0 || *p != ' ') - { - ec = error::bad_version; - return; - } - ++p; - - auto const status = parse_status(p); - if(status < 0 || *p != ' ') - { - ec = error::bad_status; - return; - } - ++p; - - auto const reason = parse_reason(p); - if(! parse_crlf(p)) - { - ec = error::bad_reason; - return; - } - - if(version >= 11) - f_ |= flagHTTP11; - - impl().on_response( - status, reason, version, ec); - if(ec) - return; - - parse_fields(p, term, ec); - if(ec) - return; - BOOST_ASSERT(p == term); - // 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 + status_ / 100 == 1 || // 1xx e.g. Continue + status_ == 204 || // No Content + status_ == 304) // Not Modified { state_ = state::complete; return; @@ -514,6 +595,16 @@ parse_header(char const*& p, char const* term, f_ |= flagNeedEOF; state_ = state::body_to_eof0; } + + impl().on_header(ec); + if(ec) + return; + if(state_ == state::complete) + { + impl().on_complete(ec); + if(ec) + return; + } } template @@ -712,117 +803,6 @@ parse_chunk_body(char const*& p, state_ = state::chunk_header; } -template -void -basic_parser:: -parse_fields(char const*& p, - 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(p, last, ec); - if(ec) - return; - BOOST_ASSERT(term); - if(p == term - 2) - { - p = term; - break; - } - auto const name = parse_name(p); - if(name.empty()) - { - ec = error::bad_field; - return; - } - if(*p++ != ':') - { - ec = error::bad_field; - return; - } - if(*term != ' ' && - *term != '\t') - { - auto it2 = term - 2; - p = detail::skip_ows2(p, it2); - it2 = detail::skip_ows_rev2(it2, p); - auto const f = string_to_field(name); - auto const value = make_string(p, it2); - do_field(f, value, ec); - if(ec) - return; - impl().on_field(f, name, value, ec); - if(ec) - return; - p = term; - } - else - { - // obs-fold - for(;;) - { - auto const it2 = term - 2; - p = detail::skip_ows2(p, it2); - if(p != it2) - break; - p = term; - if(*p != ' ' && *p != '\t') - break; - term = find_eol(p, last, ec); - if(ec) - return; - } - // https://stackoverflow.com/questions/686217/maximum-on-http-header-values - static_string s; - try - { - if(p != term) - { - s.append(p, term - 2); - p = term; - for(;;) - { - if(*p != ' ' && *p != '\t') - break; - s.push_back(' '); - p = detail::skip_ows2(p, term - 2); - term = find_eol(p, last, ec); - if(ec) - return; - if(p != term - 2) - s.append(p, term - 2); - p = term; - } - } - } - catch(std::length_error const&) - { - ec = error::bad_obs_fold; - return; - } - auto const f = string_to_field(name); - string_view const value{s.data(), s.size()}; - do_field(f, value, ec); - if(ec) - return; - impl().on_field(f, name, value, ec); - if(ec) - return; - } - } -} - template void basic_parser:: @@ -842,19 +822,19 @@ do_field(field f, } for(auto const& s : list) { - if(strieq("close", s)) + if(iequals({"close", 5}, s)) { f_ |= flagConnectionClose; continue; } - if(strieq("keep-alive", s)) + if(iequals({"keep-alive", 10}, s)) { f_ |= flagConnectionKeepAlive; continue; } - if(strieq("upgrade", s)) + if(iequals({"upgrade", 7}, s)) { f_ |= flagConnectionUpgrade; continue; @@ -864,16 +844,6 @@ do_field(field f, return; } - for(auto p = value.begin(); - p != value.end(); ++p) - { - if(! is_text(*p)) - { - ec = error::bad_value; - return; - } - } - // Content-Length if(f == field::content_length) { @@ -933,7 +903,7 @@ do_field(field f, auto const p = std::find_if(v.begin(), v.end(), [&](typename token_list::value_type const& s) { - return strieq("chunked", s); + return iequals({"chunked", 7}, s); }); if(p == v.end()) return; diff --git a/test/benchmarks/parser.cpp b/test/benchmarks/parser.cpp index c5a0a72a..e607a90f 100644 --- a/test/benchmarks/parser.cpp +++ b/test/benchmarks/parser.cpp @@ -259,7 +259,7 @@ public: false, dynamic_body, fields>>( Repeat, cres_); }); -#if 1 +#if 0 timedTest(Trials, "nodejs_parser", [&] { diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp index d34dcee2..cba5448e 100644 --- a/test/http/basic_parser.cpp +++ b/test/http/basic_parser.cpp @@ -11,9 +11,12 @@ #include "test_parser.hpp" #include +#include #include #include #include +#include +#include #include namespace beast { @@ -140,12 +143,715 @@ public: } }; - template + //-------------------------------------------------------------------------- + + template + typename std::enable_if< + is_const_buffer_sequence::value>::type + parsegrind(ConstBufferSequence const& buffers, + Test const& test, bool skip = false) + { + auto const size = boost::asio::buffer_size(buffers); + for(std::size_t i = 1; i < size - 1; ++i) + { + Parser p; + p.eager(true); + p.skip(skip); + error_code ec; + consuming_buffers cb{buffers}; + auto n = p.put(buffer_prefix(i, cb), ec); + if(! BEAST_EXPECTS(! ec || + ec == error::need_more, ec.message())) + continue; + if(! BEAST_EXPECT(! p.is_done())) + continue; + cb.consume(n); + n = p.put(cb, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + if(! BEAST_EXPECT(n == boost::asio::buffer_size(cb))) + continue; + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + if(! BEAST_EXPECT(p.is_done())) + continue; + test(p); + } + for(std::size_t i = 1; i < size - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + consuming_buffers cb{buffers}; + cb.consume(i); + auto n = p.put(buffer_cat( + buffer_prefix(i, buffers), cb), ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + if(! BEAST_EXPECT(n == size)) + continue; + if(p.need_eof()) + { + p.put_eof(ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + continue; + } + test(p); + } + } + + template + void + parsegrind(string_view msg, Test const& test, bool skip = false) + { + parsegrind(boost::asio::const_buffers_1{ + msg.data(), msg.size()}, test, skip); + } + + template + typename std::enable_if< + is_const_buffer_sequence::value>::type + parsegrind(ConstBufferSequence const& buffers) + { + parsegrind(buffers, [](Parser const&){}); + } + + template + void + parsegrind(string_view msg) + { + parsegrind(msg, [](Parser const&){}); + } + + template + void + failgrind(string_view msg, error_code const& result) + { + for(std::size_t i = 1; i < msg.size() - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + consuming_buffers cb{ + boost::in_place_init, msg.data(), msg.size()}; + auto n = p.put(buffer_prefix(i, cb), ec); + if(ec == result) + { + pass(); + continue; + } + if(! BEAST_EXPECTS( + ec == error::need_more, ec.message())) + continue; + if(! BEAST_EXPECT(! p.is_done())) + continue; + cb.consume(n); + n = p.put(cb, ec); + if(! ec) + p.put_eof(ec); + BEAST_EXPECTS(ec == result, ec.message()); + } + for(std::size_t i = 1; i < msg.size() - 1; ++i) + { + Parser p; + p.eager(true); + error_code ec; + p.put(buffer_cat( + boost::asio::const_buffers_1{msg.data(), i}, + boost::asio::const_buffers_1{ + msg.data() + i, msg.size() - i}), ec); + if(! ec) + p.put_eof(ec); + BEAST_EXPECTS(ec == result, ec.message()); + } + } + + //-------------------------------------------------------------------------- + + void + testFlatten() + { + parsegrind>( + "GET / HTTP/1.1\r\n" + "\r\n" + ); + parsegrind>( + "POST / HTTP/1.1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + ); + parsegrind>( + "HTTP/1.1 403 Not Found\r\n" + "\r\n" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "\r\n" + "*****" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5;x\r\n*****\r\n" + "0\r\nMD5: 0xff30\r\n" + "\r\n" + ); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "\r\n" + "*****" + ); + } + + void + testObsFold() + { + auto const check = + [&](std::string const& s, string_view value) + { + std::string m = + "GET / HTTP/1.1\r\n" + "f: " + s + "\r\n" + "\r\n"; + parsegrind>(m, + [&](parser const& p) + { + BEAST_EXPECT(p.get()["f"] == value); + }); + }; + check("x", "x"); + check(" x", "x"); + check("\tx", "x"); + check(" \tx", "x"); + check("\t x", "x"); + check("x ", "x"); + check(" x\t", "x"); + check("\tx \t", "x"); + check(" \tx\t ", "x"); + check("\t x \t ", "x"); + check("\r\n x", "x"); + check(" \r\n x", "x"); + check(" \r\n\tx", "x"); + check(" \r\n\t x", "x"); + check(" \r\n \tx", "x"); + check(" \r\n \r\n \r\n x \t", "x"); + check("xy", "xy"); + check("\r\n x", "x"); + check("\r\n x", "x"); + check("\r\n xy", "xy"); + check("\r\n \r\n \r\n x", "x"); + check("\r\n \r\n \r\n xy", "xy"); + check("x\r\n y", "x y"); + check("x\r\n y\r\n z ", "x y z"); + } + + // Check that all callbacks are invoked + void + testCallbacks() + { + parsegrind>( + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 0); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n*\r\n" + "0\r\n\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 1); + BEAST_EXPECT(p.got_on_complete == 1); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1;x\r\n*\r\n" + "0\r\n\r\n", + [&](test_parser const& p) + { + BEAST_EXPECT(p.got_on_begin == 1); + BEAST_EXPECT(p.got_on_field == 2); + BEAST_EXPECT(p.got_on_header == 1); + BEAST_EXPECT(p.got_on_body == 1); + BEAST_EXPECT(p.got_on_chunk == 1); + BEAST_EXPECT(p.got_on_complete == 1); + }); + } + + void + testRequestLine() + { + using P = test_parser; + + parsegrind

("GET /x HTTP/1.0\r\n\r\n"); + parsegrind

("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz / HTTP/1.0\r\n\r\n"); + parsegrind

("GET / HTTP/1.0\r\n\r\n", expect_version{*this, 10}); + parsegrind

("G / HTTP/1.1\r\n\r\n", expect_version{*this, 11}); + // VFALCO TODO various forms of good request-target (uri) + + failgrind

("\tGET / HTTP/1.0\r\n" "\r\n", error::bad_method); + failgrind

("GET\x01 / HTTP/1.0\r\n" "\r\n", error::bad_method); + failgrind

("GET / HTTP/1.0\r\n" "\r\n", error::bad_target); + failgrind

("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_target); + failgrind

("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_target); + // VFALCO TODO various forms of bad request-target (uri) + failgrind

("GET / HTTP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / _TTP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / H_TP/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HT_P/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTT_/1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP_1.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/01.2\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/3.45\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/67.89\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/x.0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.x\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0 \r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1_0\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\n\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\n\r\r\n" "\r\n", error::bad_version); + failgrind

("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_version); + } + + void + testStatusLine() + { + using P = test_parser; + + parsegrind

("HTTP/1.0 000 OK\r\n" "\r\n", expect_status{*this, 0}); + parsegrind

("HTTP/1.1 012 OK\r\n" "\r\n", expect_status{*this, 12}); + parsegrind

("HTTP/1.0 345 OK\r\n" "\r\n", expect_status{*this, 345}); + parsegrind

("HTTP/1.0 678 OK\r\n" "\r\n", expect_status{*this, 678}); + parsegrind

("HTTP/1.0 999 OK\r\n" "\r\n", expect_status{*this, 999}); + parsegrind

("HTTP/1.0 200 \tX\r\n" "\r\n", expect_version{*this, 10}); + parsegrind

("HTTP/1.1 200 X\r\n" "\r\n", expect_version{*this, 11}); + parsegrind

("HTTP/1.0 200 \r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 X \r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 X\t\r\n" "\r\n"); + parsegrind

("HTTP/1.1 200 \x80\x81...\xfe\xff\r\n\r\n"); + parsegrind

("HTTP/1.1 200 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\r\n\r\n"); + + failgrind

("\rHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("\nHTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

(" HTTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("_TTP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("H_TP/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HT_P/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTT_/1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP_1.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/01.2 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/3.45 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/67.89 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/x.0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1.x 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1_0 200 OK\r\n" "\r\n", error::bad_version); + failgrind

("HTTP/1.0 200 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 0 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 12 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 3456 OK\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 200\r\n" "\r\n", error::bad_status); + failgrind

("HTTP/1.0 200 \n\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 \x01\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 \x7f\r\n" "\r\n", error::bad_reason); + failgrind

("HTTP/1.0 200 OK\n\r\n" "\r\n", error::bad_reason); + failgrind

("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"; + }; + + using P = test_parser; + + parsegrind

(m("f:\r\n")); + parsegrind

(m("f: \r\n")); + parsegrind

(m("f:\t\r\n")); + parsegrind

(m("f: \t\r\n")); + parsegrind

(m("f: v\r\n")); + parsegrind

(m("f:\tv\r\n")); + parsegrind

(m("f:\tv \r\n")); + parsegrind

(m("f:\tv\t\r\n")); + parsegrind

(m("f:\tv\t \r\n")); + parsegrind

(m("f:\r\n \r\n")); + parsegrind

(m("f:v\r\n")); + parsegrind

(m("f: v\r\n u\r\n")); + parsegrind

(m("!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz: v\r\n")); + parsegrind

(m("f: !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x80\x81...\xfe\xff\r\n")); + + failgrind

(m(" f: v\r\n"), error::bad_field); + failgrind

(m("\tf: v\r\n"), error::bad_field); + failgrind

(m("f : v\r\n"), error::bad_field); + failgrind

(m("f\t: v\r\n"), error::bad_field); + failgrind

(m("f: \n\r\n"), error::bad_value); + failgrind

(m("f: v\r \r\n"), error::bad_line_ending); + failgrind

(m("f: \r v\r\n"), error::bad_line_ending); + failgrind

( + "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 + + using P = test_parser; + + parsegrind

(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(",close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(" close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("\tclose\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\t\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn(" ,\t,,close,, ,\t,,\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("\r\n close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n \r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("any,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("any\r\n ,close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close\r\n ,any\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(cn("close,close\r\n"), expect_flags{*this, parse_flag::connection_close}); // weird but allowed + + parsegrind

(cn("keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\t ,x\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("\r\n keep-alive \t\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(cn("keep-alive\r\n \r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + + parsegrind

(cn("upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\t ,x\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("\r\n upgrade \t\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade \r\n \t \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(cn("upgrade\r\n \r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + + // VFALCO What's up with these? + //parsegrind

(cn("close,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive}); + parsegrind

(cn("upgrade,keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + parsegrind

(cn("upgrade,\r\n keep-alive\r\n"), expect_flags{*this, parse_flag::connection_upgrade | parse_flag::connection_keep_alive}); + //parsegrind

(cn("close,keep-alive,upgrade\r\n"), expect_flags{*this, parse_flag::connection_close | parse_flag::connection_keep_alive | parse_flag::connection_upgrade}); + + parsegrind

("GET / HTTP/1.1\r\n\r\n", expect_keepalive(*this, true)); + parsegrind

("GET / HTTP/1.0\r\n\r\n", expect_keepalive(*this, false)); + parsegrind

("GET / HTTP/1.0\r\n" + "Connection: keep-alive\r\n\r\n", expect_keepalive(*this, true)); + parsegrind

("GET / HTTP/1.1\r\n" + "Connection: close\r\n\r\n", expect_keepalive(*this, false)); + + parsegrind

(cn("x\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x ,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("x\t,y\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(",keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\tnone\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\t\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" ,\t,,keep,, ,\t,,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n keep\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(",closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\tcloset\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\t\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn(" ,\t,,closet,, ,\t,,\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n closet\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("closet\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("clog\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("key\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("uptown\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keeper\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("keep-alively\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("up\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("upgrader\r\n \r\n"), expect_flags{*this, 0}); + parsegrind

(cn("none\r\n"), expect_flags{*this, 0}); + parsegrind

(cn("\r\n none\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("ConnectioX: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Condor: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Connect: close\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Connections: close\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("Proxy-Connection: close\r\n"), expect_flags{*this, parse_flag::connection_close}); + parsegrind

(m("Proxy-Connection: keep-alive\r\n"), expect_flags{*this, parse_flag::connection_keep_alive}); + parsegrind

(m("Proxy-Connection: upgrade\r\n"), expect_flags{*this, parse_flag::connection_upgrade}); + parsegrind

(m("Proxy-ConnectioX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Proxy-Connections: 1\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Proxy-Connotes: see-also\r\n"), expect_flags{*this, 0}); + + failgrind

(cn("[\r\n"), error::bad_value); + failgrind

(cn("close[\r\n"), error::bad_value); + failgrind

(cn("close [\r\n"), error::bad_value); + failgrind

(cn("close, upgrade [\r\n"), error::bad_value); + failgrind

(cn("upgrade[]\r\n"), error::bad_value); + failgrind

(cn("keep\r\n -alive\r\n"), error::bad_value); + failgrind

(cn("keep-alive[\r\n"), error::bad_value); + failgrind

(cn("keep-alive []\r\n"), error::bad_value); + failgrind

(cn("no[ne]\r\n"), error::bad_value); + } + + void + testContentLengthField() + { + using P = test_parser; + 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"; }; + auto const check = + [&](std::string const& s, std::uint64_t v) + { + parsegrind

(c(s), + [&](P const& p) + { + BEAST_EXPECT(p.content_length()); + BEAST_EXPECT(p.content_length() && *p.content_length() == v); + }, true); + }; + + check("0\r\n", 0); + check("00\r\n", 0); + check("1\r\n", 1); + check("01\r\n", 1); + check("9\r\n", 9); + check("42 \r\n", 42); + check("42\t\r\n", 42); + check("42 \t \r\n", 42); + check("42\r\n \t \r\n", 42); + + parsegrind

(m("Content-LengtX: 0\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Content-Lengths: many\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Content: full\r\n"), expect_flags{*this, 0}); + + failgrind

(c("\r\n"), error::bad_content_length); + failgrind

(c("18446744073709551616\r\n"), error::bad_content_length); + failgrind

(c("0 0\r\n"), error::bad_content_length); + failgrind

(c("0 1\r\n"), error::bad_content_length); + failgrind

(c(",\r\n"), error::bad_content_length); + failgrind

(c("0,\r\n"), error::bad_content_length); + failgrind

(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"; }; + + using P = test_parser; + + parsegrind

(ce("chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked\t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked \t\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(" chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\tchunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked ,\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked, \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(", chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(" ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("chunked\r\n \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("\r\n ,chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce(",\r\n chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(ce("gzip, chunked \r\n"), expect_flags{*this, parse_flag::chunked}); + parsegrind

(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 + //parsegrind

(ce("custom;key=\",chunked\r\n"), expect_flags{*this, parse_flag::chunked}); + + parsegrind

(te("gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked, gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked\r\n , gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked,\r\n gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunked,\r\n ,gzip\r\n"), expect_flags{*this, 0}); + parsegrind

(te("bigchunked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("chunk\r\n ked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("bar\r\n ley chunked\r\n"), expect_flags{*this, 0}); + parsegrind

(te("barley\r\n chunked\r\n"), expect_flags{*this, 0}); + + parsegrind

(m("Transfer-EncodinX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Transfer-Encodings: 2\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Transfer-Encoded: false\r\n"), expect_flags{*this, 0}); + + failgrind>( + "HTTP/1.1 200 OK\r\n" + "Content-Length: 1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", error::bad_transfer_encoding); + } + + void + testUpgradeField() + { + auto const m = [](std::string const& s) + { return "GET / HTTP/1.1\r\n" + s + "\r\n"; }; + + using P = test_parser; + + parsegrind

(m("Upgrade:\r\n"), expect_flags{*this, parse_flag::upgrade}); + parsegrind

(m("Upgrade: \r\n"), expect_flags{*this, parse_flag::upgrade}); + parsegrind

(m("Upgrade: yes\r\n"), expect_flags{*this, parse_flag::upgrade}); + + parsegrind

(m("Up: yes\r\n"), expect_flags{*this, 0}); + parsegrind

(m("UpgradX: none\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Upgrades: 2\r\n"), expect_flags{*this, 0}); + parsegrind

(m("Upsample: 4x\r\n"), expect_flags{*this, 0}); + + parsegrind

( + "GET / HTTP/1.1\r\n" + "Connection: upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n", + [&](P const& p) + { + BEAST_EXPECT(p.is_upgrade()); + }); + } + + void + testLimits() + { + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.header_limit(10); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::header_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "HTTP/1.1 200 OK\r\n" + "\r\n" + "**"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + { + multi_buffer b; + ostream(b) << + "POST / HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "**\r\n" + "0\r\n\r\n"; + error_code ec; + test_parser p; + p.body_limit(1); + p.eager(true); + p.put(b.data(), ec); + BEAST_EXPECTS(ec == error::body_limit, ec.message()); + } + } + + //-------------------------------------------------------------------------- + static boost::asio::const_buffers_1 - buf(char const (&s)[N]) + buf(string_view s) { - return {s, N-1}; + return {s.data(), s.size()}; } template @@ -157,609 +863,59 @@ public: return p.put(buffers, ec); } - template - void - good(string_view s, - Pred const& pred, bool skipBody = false) - { - using boost::asio::buffer; - test_parser p; - p.eager(true); - if(skipBody) - p.skip(true); - error_code ec; - auto const n = p.put( - buffer(s.data(), s.size()), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - if(! BEAST_EXPECT(n == s.size())) - return; - if(p.need_eof()) - p.put_eof(ec); - if(BEAST_EXPECTS(! ec, ec.message())) - pred(p); - } - - template - void - good(string_view s) - { - good(s, - [](test_parser const&) - { - }); - } - - template - void - bad(string_view s, - error_code const& ev, bool skipBody = false) - { - using boost::asio::buffer; - test_parser p; - p.eager(true); - if(skipBody) - p.skip(true); - error_code ec; - p.put(buffer(s.data(), s.size()), ec); - if(! ec && ev) - p.put_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) - { - test_parser p; - p.eager(true); - error_code ec; - p.put(buffer(s.data(), i), ec); - BEAST_EXPECTS(ec == error::need_more, ec.message()); - ec.assign(0, ec.category()); - p.put(boost::asio::buffer(s.data(), s.size()), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_done()); - } - } - { - 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; - p.eager(true); - error_code ec; - p.put(b1, ec); - BEAST_EXPECTS(ec == error::need_more, ec.message()); - ec.assign(0, ec.category()); - p.put(buffer_cat(b1, b2), ec); - BEAST_EXPECTS(! ec, ec.message()); - p.put_eof(ec); - } - } - } - - // Check that all callbacks are invoked - void - testCallbacks() - { - using boost::asio::buffer; - { - test_parser p; - p.eager(true); - 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.put(buffer(s), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_done()); - 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; - p.eager(true); - 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.put(buffer(s), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_done()); - 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; - p.eager(true); - error_code ec; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1\r\n*\r\n" - "0\r\n\r\n"; - p.put(buffer(s), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_done()); - 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; - p.eager(true); - error_code ec; - std::string const s = - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1;x\r\n*\r\n" - "0\r\n\r\n"; - p.put(buffer(s), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(p.is_done()); - 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_target); - bad("GET \x01 HTTP/1.0\r\n" "\r\n", error::bad_target); - bad("GET /\x01 HTTP/1.0\r\n" "\r\n", error::bad_target); - // 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_version); - bad("GET / HTTP/1.0\r\r\n" "\r\n", error::bad_version); - } - - 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_version); - 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_reason); - } - - 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("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( + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "abcd"); + }); + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Server: test\r\n" + "Expect: Expires, MD5-Fingerprint\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" + ,[&](test_parser const& p) + { + BEAST_EXPECT(p.body == "*****--"); + }); + + parsegrind>( "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" "\r\n" "1", expect_body(*this, "1")); - good( + parsegrind>( "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_done()); - } + parsegrind>(buffer_cat( + buf("GET / HTTP/1.1\r\n" + "Content-Length: 10\r\n" + "\r\n"), + buf("12"), + buf("345"), + buf("67890"))); // request without Content-Length or // Transfer-Encoding: chunked has no body. @@ -863,176 +1019,28 @@ public: BEAST_EXPECT(p.is_done()); } - bad( + failgrind>( "GET / HTTP/1.1\r\n" "Content-Length: 1\r\n" "\r\n", error::partial_message); } -#if 0 - 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); - } -#endif - - void - testSplit() - { - multi_buffer b; - ostream(b) << - "POST / HTTP/1.1\r\n" - "Content-Length: 5\r\n" - "\r\n" - "*****"; - error_code ec; - test_parser p; - auto n = p.put(b.data(), 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_done()); - BEAST_EXPECT(p.is_header_done()); - BEAST_EXPECT(p.body.empty()); - b.consume(n); - n = feed(b.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_done()); - BEAST_EXPECT(p.body == "*****"); - } - - void - testLimits() - { - { - multi_buffer b; - ostream(b) << - "POST / HTTP/1.1\r\n" - "Content-Length: 2\r\n" - "\r\n" - "**"; - error_code ec; - test_parser p; - p.header_limit(10); - p.eager(true); - p.put(b.data(), ec); - BEAST_EXPECTS(ec == error::header_limit, ec.message()); - } - { - multi_buffer b; - ostream(b) << - "POST / HTTP/1.1\r\n" - "Content-Length: 2\r\n" - "\r\n" - "**"; - error_code ec; - test_parser p; - p.body_limit(1); - p.eager(true); - p.put(b.data(), ec); - BEAST_EXPECTS(ec == error::body_limit, ec.message()); - } - { - multi_buffer b; - ostream(b) << - "HTTP/1.1 200 OK\r\n" - "\r\n" - "**"; - error_code ec; - test_parser p; - p.body_limit(1); - p.eager(true); - p.put(b.data(), ec); - BEAST_EXPECTS(ec == error::body_limit, ec.message()); - } - { - multi_buffer b; - ostream(b) << - "POST / HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "2\r\n" - "**\r\n" - "0\r\n\r\n"; - error_code ec; - test_parser p; - p.body_limit(1); - p.eager(true); - p.put(b.data(), ec); - BEAST_EXPECTS(ec == error::body_limit, ec.message()); - } - } - - //-------------------------------------------------------------------------- - template - void - put(basic_parser& p, - string_view s) - { - error_code ec; - auto const bytes_used = p.put( - boost::asio::buffer(s.data(), s.size()), ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(bytes_used == s.size()); - } - // https://github.com/vinniefalco/Beast/issues/430 void testIssue430() { - { - test_parser p; - p.eager(true); - put(p, - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "Content-Type: application/octet-stream\r\n" - "\r\n" - "4\r\nabcd\r\n" - "0\r\n\r\n" - ); - } - { - test_parser p; - p.eager(true); - put(p, - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "Content-Type: application/octet-stream\r\n" - "\r\n" - "4\r\nabcd"); - put(p, - "\r\n" - "0\r\n\r\n" - ); - } + parsegrind>( + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: application/octet-stream\r\n" + "\r\n" + "4\r\nabcd\r\n" + "0\r\n\r\n"); } - //-------------------------------------------------------------------------- - // https://github.com/vinniefalco/Beast/issues/452 void testIssue452() @@ -1043,79 +1051,12 @@ public: string_view s = "GET / HTTP/1.1\r\n" "\r\n" - "die!" - ; + "die!"; p.put(boost::asio::buffer( s.data(), s.size()), ec); - pass(); - } - - //-------------------------------------------------------------------------- - - template - void - bufgrind(string_view s, Pred&& pred) - { - using boost::asio::buffer; - for(std::size_t n = 1; n < s.size() - 1; ++n) - { - Parser p; - p.eager(true); - error_code ec; - std::size_t used; - used = p.put(buffer(s.data(), n), ec); - if(ec == error::need_more) - continue; - if(! BEAST_EXPECTS(! ec, ec.message())) - continue; - if(! BEAST_EXPECT(used == n)) - continue; - used = p.put(buffer( - s.data() + n, s.size() - n), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - continue; - if(! BEAST_EXPECT(used == s.size() -n)) - continue; - if(! BEAST_EXPECT(p.is_done())) - continue; - pred(p); - } - } - - void - testBufGrind() - { - bufgrind>( - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "Content-Type: application/octet-stream\r\n" - "\r\n" - "4\r\nabcd\r\n" - "0\r\n\r\n" - ,[&](test_parser const& p) - { - BEAST_EXPECT(p.body == "abcd"); - }); - bufgrind>( - "HTTP/1.1 200 OK\r\n" - "Server: test\r\n" - "Trailer: Expires, MD5-Fingerprint\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" - ,[&](test_parser& p) - { - BEAST_EXPECT(p.body == "*****--"); - BEAST_EXPECT(p.fields["Expires"] == "never"); - BEAST_EXPECT(p.fields["MD5-Fingerprint"] == "-"); - }); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + BEAST_EXPECT(p.is_done()); } // https://github.com/vinniefalco/Beast/issues/496 @@ -1123,14 +1064,15 @@ public: testIssue496() { // The bug affected hex parsing with leading zeroes - bufgrind>( + using P = test_parser; + parsegrind

( "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "Content-Type: application/octet-stream\r\n" "\r\n" "0004\r\nabcd\r\n" "0\r\n\r\n" - ,[&](test_parser const& p) + ,[&](P const& p) { BEAST_EXPECT(p.body == "abcd"); }); @@ -1142,6 +1084,7 @@ public: run() override { testFlatten(); + testObsFold(); testCallbacks(); testRequestLine(); testStatusLine(); @@ -1150,13 +1093,11 @@ public: testContentLengthField(); testTransferEncodingField(); testUpgradeField(); - testBody(); - testSplit(); testLimits(); + testBody(); testIssue430(); testIssue452(); testIssue496(); - testBufGrind(); } }; diff --git a/test/http/test_parser.hpp b/test/http/test_parser.hpp index 3a89093c..7a17ee9f 100644 --- a/test/http/test_parser.hpp +++ b/test/http/test_parser.hpp @@ -32,13 +32,13 @@ public: 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_chunk = false; - bool got_on_complete = false; + int got_on_begin = 0; + int got_on_field = 0; + int got_on_header = 0; + int got_on_body = 0; + int got_content_length = 0; + int got_on_chunk = 0; + int got_on_complete = 0; std::unordered_map< std::string, std::string> fields; @@ -59,7 +59,7 @@ public: path = std::string( path_.data(), path_.size()); version = version_; - got_on_begin = true; + ++got_on_begin; if(fc_) fc_->fail(ec); else @@ -75,7 +75,7 @@ public: reason = std::string( reason_.data(), reason_.size()); version = version_; - got_on_begin = true; + ++got_on_begin; if(fc_) fc_->fail(ec); else @@ -86,7 +86,7 @@ public: on_field(field, string_view name, string_view value, error_code& ec) { - got_on_field = true; + ++got_on_field; if(fc_) fc_->fail(ec); else @@ -97,7 +97,7 @@ public: void on_header(error_code& ec) { - got_on_header = true; + ++got_on_header; if(fc_) fc_->fail(ec); else @@ -109,7 +109,7 @@ public: std::uint64_t> const& content_length_, error_code& ec) { - got_on_body = true; + ++got_on_body; got_content_length = static_cast(content_length_); if(fc_) @@ -134,7 +134,7 @@ public: on_chunk(std::uint64_t, string_view, error_code& ec) { - got_on_chunk = true; + ++got_on_chunk; if(fc_) fc_->fail(ec); else @@ -144,7 +144,7 @@ public: void on_complete(error_code& ec) { - got_on_complete = true; + ++got_on_complete; if(fc_) fc_->fail(ec); else