diff --git a/CHANGELOG b/CHANGELOG index 18cf19df..a467786b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,16 @@ 1.0.0-b6 -* Add HTTP field value parsers * Use SFINAE on return values * Use beast::error_code instead of nested types * Tidy up use of GENERATING_DOCS +* Remove obsolete RFC2616 functions +* Add HTTP field value parser containers: + - ext_list + - param_list + - token_list + +API Changes: + +* ci_equal is moved to beast::http namespace, in rfc7230.hpp -------------------------------------------------------------------------------- diff --git a/TODO.txt b/TODO.txt index 31043032..2efbbb77 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,7 +2,6 @@ Boost.Http * Use enum instead of bool in isRequest -* move version to a subclass of message Docs: * Include Example program listings in the docs @@ -33,21 +32,15 @@ HTTP: * Define Parser concept in HTTP - Need parse version of read() so caller can set parser options like maximum size of headers, maximum body size, etc -* trim public interface of rfc2616.h to essentials only * add bool should_close(message_v1 const&) to replace the use of eof return value from write and async_write -* http type_check, e.g. is_WritableBody * More fine grained parser errors * HTTP parser size limit with test (configurable?) * HTTP parser trailers with test * Decode chunk encoding parameters * URL parser, strong URL character checking in HTTP parser -* Update for rfc7230 -* Consider rename to MessageBody concept * Fix prepare() calling content_length() without init() -* Use construct,destroy allocator routines in basic_headers * Complete allocator testing in basic_streambuf, basic_headers -* Add tests for writer using the resume function / coros * Custom HTTP error codes for various situations * Branch prediction hints in parser * Check basic_parser_v1 against rfc7230 for leading message whitespace diff --git a/include/beast/core/detail/ci_char_traits.hpp b/include/beast/core/detail/ci_char_traits.hpp index 8484f273..0a388b7a 100644 --- a/include/beast/core/detail/ci_char_traits.hpp +++ b/include/beast/core/detail/ci_char_traits.hpp @@ -8,76 +8,96 @@ #ifndef BEAST_DETAIL_CI_CHAR_TRAITS_HPP #define BEAST_DETAIL_CI_CHAR_TRAITS_HPP +#include #include -#include -#include -#include -#include -#include -#include +#include +#include namespace beast { namespace detail { -/** Case-insensitive function object for performing less than comparisons. */ +inline +char +tolower(char c) +{ + static std::array constexpr tab = {{ + 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, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 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)]); +} + +template +inline +boost::string_ref +string_helper(const char (&s)[N]) +{ + return boost::string_ref{s, N-1}; +} + +template +inline +T const& +string_helper(T const& t) +{ + return t; +} + +// Case-insensitive less struct ci_less { static bool const is_transparent = true; + template bool - operator()(boost::string_ref const& lhs, - boost::string_ref const& rhs) const noexcept + operator()(S1 const& lhs, S2 const& rhs) const noexcept { using std::begin; using std::end; + auto const s1 = string_helper(lhs); + auto const s2 = string_helper(rhs); return std::lexicographical_compare( - begin(lhs), end(lhs), begin(rhs), end(rhs), + begin(s1), end(s1), begin(s2), end(s2), [](char lhs, char rhs) { - return std::tolower(lhs) < std::tolower(rhs); + return tolower(lhs) < tolower(rhs); } ); } }; -inline +// Case-insensitive equal +struct ci_equal_pred +{ + bool + operator()(char c1, char c2) const noexcept + { + return tolower(c1) == tolower(c2); + } +}; + +// Case-insensitive equal +template bool -ci_equal(std::pair lhs, - std::pair rhs) +ci_equal(S1 const& lhs, S2 const& rhs) { - if(lhs.second != rhs.second) - return false; - return std::equal (lhs.first, lhs.first + lhs.second, - rhs.first, - [] (char lhs, char rhs) - { - return std::tolower(lhs) == std::tolower(rhs); - } - ); -} - -template -inline -std::pair -view(const char (&s)[N]) -{ - return {s, N-1}; -} - -inline -std::pair -view(std::string const& s) -{ - return {s.data(), s.size()}; -} - -/** Returns `true` if strings are case-insensitive equal. */ -template -inline -bool -ci_equal(String1 const& lhs, String2 const& rhs) -{ - return ci_equal(view(lhs), view(rhs)); + return boost::range::equal( + string_helper(lhs), string_helper(rhs), + ci_equal_pred{}); } } // detail diff --git a/include/beast/http.hpp b/include/beast/http.hpp index 1d0a2678..f3ffefa5 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/beast/http/basic_headers.hpp b/include/beast/http/basic_headers.hpp index 38b3d3ee..ee6b3b0f 100644 --- a/include/beast/http/basic_headers.hpp +++ b/include/beast/http/basic_headers.hpp @@ -246,8 +246,7 @@ public: Field names are stored as-is, but comparison are case-insensitive. The container preserves the order of insertion of fields with different names. For fields with the same name, the implementation - concatenates values inserted with duplicate names as per the - rules in rfc2616 section 4.2. + concatenates values inserted with duplicate names as per rfc7230. @note Meets the requirements of @b `FieldSequence`. */ @@ -393,8 +392,7 @@ public: */ // VFALCO TODO Consider allowing rvalue references for std::move? void - insert(boost::string_ref const& name, - boost::string_ref const& value); + insert(boost::string_ref const& name, boost::string_ref value); /** Insert a field value. @@ -416,8 +414,7 @@ public: specified value is inserted as if by `insert(field, value)`. */ void - replace(boost::string_ref const& name, - boost::string_ref const& value); + replace(boost::string_ref const& name, boost::string_ref value); /** Replace a field value. diff --git a/include/beast/http/detail/rfc7230.hpp b/include/beast/http/detail/rfc7230.hpp new file mode 100644 index 00000000..64272fdb --- /dev/null +++ b/include/beast/http/detail/rfc7230.hpp @@ -0,0 +1,261 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_DETAIL_RFC7230_HPP +#define BEAST_HTTP_DETAIL_RFC7230_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +inline +bool +is_tchar(char c) +{ + /* + tchar = "!" | "#" | "$" | "%" | "&" | + "'" | "*" | "+" | "-" | "." | + "^" | "_" | "`" | "|" | "~" | + DIGIT | ALPHA + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 + }}; + return tab[static_cast(c)]; +} + +inline +bool +is_qdchar(char c) +{ + /* + qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 0, 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, 0, 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)]; +} + +inline +bool +is_qpchar(char c) +{ + /* + quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + obs-text = %x80-FF + */ + static std::array constexpr tab = {{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 144 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 160 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 176 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 192 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 208 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 224 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 240 + }}; + return tab[static_cast(c)]; +} + +template +void +skip_ows(FwdIt& it, FwdIt const& end) +{ + while(it != end) + { + auto const c = *it; + if(c != ' ' && c != '\t') + break; + ++it; + } +} + +inline +boost::string_ref +trim(boost::string_ref const& s) +{ + auto first = s.begin(); + auto last = s.end(); + skip_ows(first, last); + while(first != last) + { + auto const c = *std::prev(last); + if(c != ' ' && c != '\t') + break; + --last; + } + if(first == last) + return {}; + return {&*first, + static_cast(last - first)}; +} + +struct param_iter +{ + using iter_type = boost::string_ref::const_iterator; + + iter_type it; + iter_type begin; + iter_type end; + std::pair v; + + bool + empty() const + { + return begin == it; + } + + template + void + increment(); +}; + +template +void +param_iter:: +increment() +{ +/* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + + quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + qdtext = HTAB / SP / "!" / %x23-5B ; '#'-'[' / %x5D-7E ; ']'-'~' / obs-text + quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + obs-text = %x80-FF + + Example: + chunked;a=b;i=j,gzip;windowBits=12 + x,y +*/ + auto const err = + [&] + { + it = begin; + }; + v.first.clear(); + v.second.clear(); + detail::skip_ows(it, end); + begin = it; + if(it == end) + return err(); + if(*it != ';') + return err(); + ++it; + detail::skip_ows(it, end); + if(it == end) + return err(); + // param + if(! detail::is_tchar(*it)) + return err(); + auto const p0 = it; + for(;;) + { + ++it; + if(it == end) + return err(); + if(! detail::is_tchar(*it)) + break; + } + auto const p1 = it; + detail::skip_ows(it, end); + if(it == end) + return err(); + if(*it != '=') + return err(); + ++it; + detail::skip_ows(it, end); + if(it == end) + return err(); + if(*it == '"') + { + // quoted-string + auto const p2 = it; + ++it; + for(;;) + { + if(it == end) + return err(); + auto c = *it++; + if(c == '"') + break; + if(detail::is_qdchar(c)) + continue; + if(c != '\\') + return err(); + if(it == end) + return err(); + c = *it++; + if(! detail::is_qpchar(c)) + return err(); + } + v.first = { &*p0, static_cast(p1 - p0) }; + v.second = { &*p2, static_cast(it - p2) }; + } + else + { + // token + if(! detail::is_tchar(*it)) + return err(); + auto const p2 = it; + for(;;) + { + it++; + if(it == end) + break; + if(! detail::is_tchar(*it)) + break; + } + v.first = { &*p0, static_cast(p1 - p0) }; + v.second = { &*p2, static_cast(it - p2) }; + } +} + +} // detail +} // http +} // beast + +#endif + diff --git a/include/beast/http/impl/basic_headers.ipp b/include/beast/http/impl/basic_headers.ipp index 31f9913a..27b9d416 100644 --- a/include/beast/http/impl/basic_headers.ipp +++ b/include/beast/http/impl/basic_headers.ipp @@ -8,6 +8,8 @@ #ifndef BEAST_HTTP_IMPL_BASIC_HEADERS_IPP #define BEAST_HTTP_IMPL_BASIC_HEADERS_IPP +#include + namespace beast { namespace http { @@ -257,12 +259,13 @@ template void basic_headers:: insert(boost::string_ref const& name, - boost::string_ref const& value) + boost::string_ref value) { + value = detail::trim(value); typename set_t::insert_commit_data d; auto const result = set_.insert_check(name, less{}, d); - if (result.second) + if(result.second) { auto const p = alloc_traits::allocate( this->member(), 1); @@ -284,8 +287,9 @@ template void basic_headers:: replace(boost::string_ref const& name, - boost::string_ref const& value) + boost::string_ref value) { + value = detail::trim(value); erase(name); insert(name, value); } diff --git a/include/beast/http/impl/message_v1.ipp b/include/beast/http/impl/message_v1.ipp index 16a85a58..d0b69e4e 100644 --- a/include/beast/http/impl/message_v1.ipp +++ b/include/beast/http/impl/message_v1.ipp @@ -8,7 +8,7 @@ #ifndef BEAST_HTTP_IMPL_MESSAGE_V1_IPP #define BEAST_HTTP_IMPL_MESSAGE_V1_IPP -#include +#include #include #include #include @@ -22,13 +22,11 @@ is_keep_alive(message_v1 const& msg) { if(msg.version >= 11) { - if(rfc2616::token_in_list( - msg.headers["Connection"], "close")) + if(token_list{msg.headers["Connection"]}.exists("close")) return false; return true; } - if(rfc2616::token_in_list( - msg.headers["Connection"], "keep-alive")) + if(token_list{msg.headers["Connection"]}.exists("keep-alive")) return true; return false; } @@ -39,8 +37,7 @@ is_upgrade(message_v1 const& msg) { if(msg.version < 11) return false; - if(rfc2616::token_in_list( - msg.headers["Connection"], "upgrade")) + if(token_list{msg.headers["Connection"]}.exists("upgrade")) return true; return false; } @@ -129,8 +126,7 @@ prepare(message_v1& msg, throw std::invalid_argument( "prepare called with Content-Length field set"); - if(rfc2616::token_in_list( - msg.headers["Transfer-Encoding"], "chunked")) + if(token_list{msg.headers["Transfer-Encoding"]}.exists("chunked")) throw std::invalid_argument( "prepare called with Transfer-Encoding: chunked set"); @@ -175,8 +171,8 @@ prepare(message_v1& msg, } // rfc7230 6.7. - if(msg.version < 11 && rfc2616::token_in_list( - msg.headers["Connection"], "upgrade")) + if(msg.version < 11 && token_list{ + msg.headers["Connection"]}.exists("upgrade")) throw std::invalid_argument( "invalid version for Connection: upgrade"); } diff --git a/include/beast/http/impl/rfc7230.ipp b/include/beast/http/impl/rfc7230.ipp new file mode 100644 index 00000000..feffa23e --- /dev/null +++ b/include/beast/http/impl/rfc7230.ipp @@ -0,0 +1,548 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_HTTP_IMPL_RFC7230_IPP +#define BEAST_HTTP_IMPL_RFC7230_IPP + +#include +#include +#include + +namespace beast { +namespace http { + +class param_list::const_iterator +{ + using iter_type = boost::string_ref::const_iterator; + + std::string s_; + detail::param_iter pi_; + +public: + using value_type = param_list::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + const_iterator() = default; + + bool + operator==(const_iterator const& other) const + { + return + other.pi_.it == pi_.it && + other.pi_.end == pi_.end && + other.pi_.begin == pi_.begin; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return pi_.v; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class param_list; + + const_iterator(iter_type begin, iter_type end) + { + pi_.it = begin; + pi_.begin = begin; + pi_.end = end; + increment(); + } + + template + static + std::string + unquote(boost::string_ref const& sr); + + template + void + increment(); +}; + +inline +auto +param_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +param_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +param_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +param_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template +std::string +param_list::const_iterator:: +unquote(boost::string_ref const& sr) +{ + std::string s; + s.reserve(sr.size()); + auto it = sr.begin() + 1; + auto end = sr.end() - 1; + while(it != end) + { + if(*it == '\\') + ++it; + s.push_back(*it); + ++it; + } + return s; +} + +template +void +param_list::const_iterator:: +increment() +{ + s_.clear(); + pi_.increment(); + if(pi_.empty()) + { + pi_.it = pi_.end; + pi_.begin = pi_.end; + } + else if(pi_.v.second.front() == '"') + { + s_ = unquote(pi_.v.second); + pi_.v.second = boost::string_ref{ + s_.data(), s_.size()}; + } +} + +//------------------------------------------------------------------------------ + +class ext_list::const_iterator +{ + ext_list::value_type v_; + iter_type it_; + iter_type begin_; + iter_type end_; + +public: + using value_type = ext_list::value_type; + using pointer = value_type const*; + using reference = 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.it_ == it_ && + other.begin_ == begin_ && + other.end_ == end_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return v_; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class ext_list; + + const_iterator(iter_type begin, iter_type end) + { + it_ = begin; + begin_ = begin; + end_ = end; + increment(); + } + + template + void + increment(); +}; + +inline +auto +ext_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +ext_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +ext_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +ext_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template +auto +ext_list:: +find(T const& s) -> + const_iterator +{ + return std::find_if(begin(), end(), + [&s](value_type const& v) + { + return beast::detail::ci_equal(s, v.first); + }); +} + +template +bool +ext_list:: +exists(T const& s) +{ + return find(s) != end(); +} + +template +void +ext_list::const_iterator:: +increment() +{ + /* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + + chunked;a=b;i=j,gzip;windowBits=12 + x,y + ,,,,,chameleon + */ + auto const err = + [&] + { + it_ = end_; + begin_ = end_; + }; + auto need_comma = it_ != begin_; + v_.first = {}; + begin_ = it_; + for(;;) + { + detail::skip_ows(it_, end_); + if(it_ == end_) + return err(); + auto const c = *it_; + if(detail::is_tchar(c)) + { + if(need_comma) + return err(); + auto const p0 = it_; + for(;;) + { + ++it_; + if(it_ == end_) + break; + if(! detail::is_tchar(*it_)) + break; + } + v_.first = boost::string_ref{&*p0, + static_cast(it_ - p0)}; + detail::param_iter pi; + pi.it = it_; + pi.begin = it_; + pi.end = end_; + for(;;) + { + pi.increment(); + if(pi.empty()) + break; + } + v_.second = param_list{boost::string_ref{&*it_, + static_cast(pi.it - it_)}}; + it_ = pi.it; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +//------------------------------------------------------------------------------ + +class token_list::const_iterator +{ + token_list::value_type v_; + iter_type it_; + iter_type begin_; + iter_type end_; + +public: + using value_type = token_list::value_type; + using pointer = value_type const*; + using reference = 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.it_ == it_ && + other.begin_ == begin_ && + other.end_ == end_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return v_; + } + + pointer + operator->() const + { + return &*(*this); + } + + const_iterator& + operator++() + { + increment(); + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + friend class token_list; + + const_iterator(iter_type begin, iter_type end) + { + it_ = begin; + begin_ = begin; + end_ = end; + increment(); + } + + template + void + increment(); +}; + +inline +auto +token_list:: +begin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +token_list:: +end() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +inline +auto +token_list:: +cbegin() const -> + const_iterator +{ + return const_iterator{s_.begin(), s_.end()}; +} + +inline +auto +token_list:: +cend() const -> + const_iterator +{ + return const_iterator{s_.end(), s_.end()}; +} + +template +void +token_list::const_iterator:: +increment() +{ + /* + token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] ) + */ + auto const err = + [&] + { + it_ = end_; + begin_ = end_; + }; + auto need_comma = it_ != begin_; + v_ = {}; + begin_ = it_; + for(;;) + { + detail::skip_ows(it_, end_); + if(it_ == end_) + return err(); + auto const c = *it_; + if(detail::is_tchar(c)) + { + if(need_comma) + return err(); + auto const p0 = it_; + for(;;) + { + ++it_; + if(it_ == end_) + break; + if(! detail::is_tchar(*it_)) + break; + } + v_ = boost::string_ref{&*p0, + static_cast(it_ - p0)}; + return; + } + if(c != ',') + return err(); + need_comma = false; + ++it_; + } +} + +template +bool +token_list:: +exists(T const& s) +{ + return std::find_if(begin(), end(), + [&s](value_type const& v) + { + return beast::detail::ci_equal(s, v); + } + ) != end(); +} + +} // http +} // beast + +#endif + diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index bb08c038..05f2a908 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -97,10 +97,10 @@ struct write_preparation message_v1 const& msg_) : msg(msg_) , w(msg) - , chunked(rfc2616::token_in_list( - msg.headers["Transfer-Encoding"], "chunked")) - , close(rfc2616::token_in_list( - msg.headers["Connection"], "close") || + , chunked(token_list{ + msg.headers["Transfer-Encoding"]}.exists("chunked")) + , close(token_list{ + msg.headers["Connection"]}.exists("close") || (msg.version < 11 && ! msg.headers.exists( "Content-Length"))) { diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp index 46f8dee8..c99c2156 100644 --- a/include/beast/http/parser_v1.hpp +++ b/include/beast/http/parser_v1.hpp @@ -124,8 +124,6 @@ private: { if(! value_.empty()) { - rfc2616::trim_right_in_place(value_); - // VFALCO could std::move m_.headers.insert(field_, value_); field_.clear(); value_.clear(); diff --git a/include/beast/http/rfc2616.hpp b/include/beast/http/rfc2616.hpp deleted file mode 100644 index e3c4b844..00000000 --- a/include/beast/http/rfc2616.hpp +++ /dev/null @@ -1,464 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -#ifndef BEAST_HTTP_RFC2616_HPP -#define BEAST_HTTP_RFC2616_HPP - -#include -#include -#include -#include -#include -#include -#include -#include // for std::tie, remove ASAP -#include -#include - -namespace beast { - -#if ! GENERATING_DOCS - -/** Routines for performing RFC2616 compliance. - RFC2616: - Hypertext Transfer Protocol -- HTTP/1.1 - http://www.w3.org/Protocols/rfc2616/rfc2616 -*/ -namespace rfc2616 { - -namespace detail { - -struct ci_equal_pred -{ - bool operator()(char c1, char c2) - { - // VFALCO TODO Use a table lookup here - return std::tolower(c1) == std::tolower(c2); - } -}; - -} // detail - -/** Returns `true` if `c` is linear white space. - - This excludes the CRLF sequence allowed for line continuations. -*/ -inline -bool -is_lws(char c) -{ - return c == ' ' || c == '\t'; -} - -/** Returns `true` if `c` is any whitespace character. */ -inline -bool -is_white(char c) -{ - switch (c) - { - case ' ': case '\f': case '\n': - case '\r': case '\t': case '\v': - return true; - }; - return false; -} - -/** Returns `true` if `c` is a control character. */ -inline -bool -is_control(char c) -{ - return c <= 31 || c >= 127; -} - -/** Returns `true` if `c` is a separator. */ -inline -bool -is_separator(char c) -{ - // VFALCO Could use a static table - switch (c) - { - case '(': case ')': case '<': case '>': case '@': - case ',': case ';': case ':': case '\\': case '"': - case '{': case '}': case ' ': case '\t': - return true; - }; - return false; -} - -/** Returns `true` if `c` is a character. */ -inline -bool -is_char(char c) -{ - return c >= 0 && c <= 127; -} - -template -FwdIter -trim_left (FwdIter first, FwdIter last) -{ - return std::find_if_not (first, last, - is_white); -} - -template -FwdIter -trim_right (FwdIter first, FwdIter last) -{ - if (first == last) - return last; - do - { - --last; - if (! is_white (*last)) - return ++last; - } - while (last != first); - return first; -} - -template -void -trim_right_in_place (std::basic_string < - CharT, Traits, Allocator>& s) -{ - s.resize (std::distance (s.begin(), - trim_right (s.begin(), s.end()))); -} - -template -std::pair -trim (FwdIter first, FwdIter last) -{ - first = trim_left (first, last); - last = trim_right (first, last); - return std::make_pair (first, last); -} - -template -String -trim (String const& s) -{ - using std::begin; - using std::end; - auto first = begin(s); - auto last = end(s); - std::tie (first, last) = trim (first, last); - return { first, last }; -} - -template -String -trim_right (String const& s) -{ - using std::begin; - using std::end; - auto first (begin(s)); - auto last (end(s)); - last = trim_right (first, last); - return { first, last }; -} - -inline -std::string -trim (std::string const& s) -{ - return trim (s); -} - -/** Parse a character sequence of values separated by commas. - Double quotes and escape sequences will be converted. Excess white - space, commas, double quotes, and empty elements are not copied. - Format: - #(token|quoted-string) - Reference: - http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2 -*/ -template ::value_type>>, - class Char> -Result -split(FwdIt first, FwdIt last, Char delim) -{ - Result result; - using string = typename Result::value_type; - FwdIt iter = first; - string e; - while (iter != last) - { - if (*iter == '"') - { - // quoted-string - ++iter; - while (iter != last) - { - if (*iter == '"') - { - ++iter; - break; - } - - if (*iter == '\\') - { - // quoted-pair - ++iter; - if (iter != last) - e.append (1, *iter++); - } - else - { - // qdtext - e.append (1, *iter++); - } - } - if (! e.empty()) - { - result.emplace_back(std::move(e)); - e.clear(); - } - } - else if (*iter == delim) - { - e = trim_right (e); - if (! e.empty()) - { - result.emplace_back(std::move(e)); - e.clear(); - } - ++iter; - } - else if (is_lws (*iter)) - { - ++iter; - } - else - { - e.append (1, *iter++); - } - } - - if (! e.empty()) - { - e = trim_right (e); - if (! e.empty()) - result.emplace_back(std::move(e)); - } - return result; -} - -template ::value_type>>> -Result -split_commas(FwdIt first, FwdIt last) -{ - return split(first, last, ','); -} - -template > -Result -split_commas(boost::string_ref const& s) -{ - return split_commas(s.begin(), s.end()); -} - -//------------------------------------------------------------------------------ - -/** Iterates through a comma separated list. - - Meets the requirements of ForwardIterator. - - List defined in rfc2616 2.1. - - @note Values returned may contain backslash escapes. -*/ -class list_iterator -{ - using iter_type = boost::string_ref::const_iterator; - - iter_type it_; - iter_type end_; - boost::string_ref value_; - -public: - using value_type = boost::string_ref; - using pointer = value_type const*; - using reference = value_type const&; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::forward_iterator_tag; - - list_iterator(iter_type begin, iter_type end) - : it_(begin) - , end_(end) - { - if(it_ != end_) - increment(); - } - - bool - operator==(list_iterator const& other) const - { - return other.it_ == it_ && other.end_ == end_ - && other.value_.size() == value_.size(); - } - - bool - operator!=(list_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return value_; - } - - pointer - operator->() const - { - return &*(*this); - } - - list_iterator& - operator++() - { - increment(); - return *this; - } - - list_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - -private: - template - void - increment(); -}; - -template -void -list_iterator::increment() -{ - value_.clear(); - while(it_ != end_) - { - if(*it_ == '"') - { - // quoted-string - ++it_; - if(it_ == end_) - return; - if(*it_ != '"') - { - auto start = it_; - for(;;) - { - ++it_; - if(it_ == end_) - { - value_ = boost::string_ref( - &*start, std::distance(start, it_)); - return; - } - if(*it_ == '"') - { - value_ = boost::string_ref( - &*start, std::distance(start, it_)); - ++it_; - return; - } - } - } - ++it_; - } - else if(*it_ == ',') - { - it_++; - continue; - } - else if(is_lws(*it_)) - { - ++it_; - continue; - } - else - { - auto start = it_; - for(;;) - { - ++it_; - if(it_ == end_ || - *it_ == ',' || - is_lws(*it_)) - { - value_ = boost::string_ref( - &*start, std::distance(start, it_)); - return; - } - } - } - } -} - -/** Returns true if two strings are equal. - - A case-insensitive comparison is used. -*/ -inline -bool -ci_equal(boost::string_ref s1, boost::string_ref s2) -{ - return boost::range::equal(s1, s2, - detail::ci_equal_pred{}); -} - -/** Returns a range representing the list. */ -inline -boost::iterator_range -make_list(boost::string_ref const& field) -{ - return boost::iterator_range{ - list_iterator{field.begin(), field.end()}, - list_iterator{field.end(), field.end()}}; -} - -/** Returns true if the specified token exists in the list. - - A case-insensitive comparison is used. -*/ -template -bool -token_in_list(boost::string_ref const& value, - boost::string_ref const& token) -{ - for(auto const& item : make_list(value)) - if(ci_equal(item, token)) - return true; - return false; -} - -} // rfc2616 - -#endif - -} // beast - -#endif - diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp index 3f69354a..e429efba 100644 --- a/include/beast/http/rfc7230.hpp +++ b/include/beast/http/rfc7230.hpp @@ -8,16 +8,238 @@ #ifndef BEAST_HTTP_RFC7230_HPP #define BEAST_HTTP_RFC7230_HPP -#include -#include +#include namespace beast { -namespace rfc7230 { +namespace http { +/** A list of parameters in a HTTP extension field value. + This container allows iteration of the parameter list + in a HTTP extension. The parameter list is a series + of "name = value" pairs with each pair starting with + a semicolon. -} // rfc7230 + BNF: + @code + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + @endcode + + 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. +*/ +class param_list +{ + boost::string_ref s_; + +public: + /** The type of each element in the list. + + The first string in the pair is the name of the + parameter, and the second string in the pair is its value. + */ + using value_type = + std::pair; + + /// A constant iterator to the list +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + /// Default constructor. + param_list() = default; + + /** Construct a list. + + @param s A string containing the list contents. The string + must remain valid for the lifetime of the container. + */ + explicit + param_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; +}; + +//------------------------------------------------------------------------------ + +/** A list of extensions in a comma separated HTTP field value. + + This container allows iteration of the extensions in a HTTP + field value. The extension list is a comma separated list of + token parameter list pairs. + + BNF: + @code + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + @endcode + + 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. +*/ +class ext_list +{ + using iter_type = boost::string_ref::const_iterator; + + boost::string_ref s_; + +public: + /** The type of each element in the list. + + The first element of the pair is the extension token, and the + second element of the pair is an iterable container holding the + extension's name/value parameters. + */ + using value_type = std::pair; + + /// A constant iterator to the list +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + /** Construct a list. + + @param s A string containing the list contents. The string + must remain valid for the lifetime of the container. + */ + explicit + ext_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; + + /** Find a token in the list. + + @param s The token to find. A case-insensitive comparison is used. + + @return An iterator to the matching token, or `end()` if no + token exists. + */ + template + const_iterator + find(T const& s); + + /** Return `true` if a token is present in the list. + + @param s The token to find. A case-insensitive comparison is used. + */ + template + bool + exists(T const& s); +}; + +//------------------------------------------------------------------------------ + +/** A list of tokens in a comma separated HTTP field value. + + This container allows iteration of the extensions in a HTTP + field value. The extension list is a comma separated list of + token parameter list pairs. + + BNF: + @code + token-list = *( "," OWS ) token *( OWS "," [ OWS ext ] ) + @endcode + + 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. +*/ +class token_list +{ + using iter_type = boost::string_ref::const_iterator; + + boost::string_ref s_; + +public: + /** The type of each element in the token list. + + The first element of the pair is the extension token, and the + second element of the pair is an iterable container holding the + extension's name/value parameters. + */ + using value_type = boost::string_ref; + + /// A constant iterator to the list +#if GENERATING_DOCS + using const_iterator = implementation_defined; +#else + class const_iterator; +#endif + + /** Construct a list. + + @param s A string containing the list contents. The string + must remain valid for the lifetime of the container. + */ + explicit + token_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; + + /** Return `true` if a token is present in the list. + + @param s The token to find. A case-insensitive comparison is used. + */ + template + bool + exists(T const& s); +}; + +} // http } // beast +#include + #endif diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 569f43b6..c331cdd3 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include @@ -951,8 +951,7 @@ build_response(http::request_v1 const& req) return err("Missing Host"); if(! req.headers.exists("Sec-WebSocket-Key")) return err("Missing Sec-WebSocket-Key"); - if(! rfc2616::token_in_list( - req.headers["Upgrade"], "websocket")) + if(! http::token_list{req.headers["Upgrade"]}.exists("websocket")) return err("Missing websocket Upgrade token"); { auto const version = @@ -1005,8 +1004,7 @@ do_response(http::response_v1 const& res, return fail(); if(! is_upgrade(res)) return fail(); - if(! rfc2616::ci_equal( - res.headers["Upgrade"], "websocket")) + if(! http::token_list{res.headers["Upgrade"]}.exists("websocket")) return fail(); if(! res.headers.exists("Sec-WebSocket-Accept")) return fail(); diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index a26927a2..abe22409 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -120,7 +120,7 @@ decorate(Decorator&& d) This setting only affects the behavior of HTTP requests that implicitly or explicitly ask for a keepalive. For HTTP requests that indicate the connection should be closed, the connection is - closed as per rfc2616. + closed as per rfc7230. The default setting is to close connections after a failed upgrade request. diff --git a/test/Jamfile b/test/Jamfile index 02fb70a1..9cd423b4 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -54,7 +54,6 @@ unit-test http-tests : http/read.cpp http/reason.cpp http/resume_context.cpp - http/rfc2616.cpp http/rfc7230.cpp http/status.cpp http/streambuf_body.cpp diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 6b2b8c37..af46ab20 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -20,7 +20,6 @@ add_executable (http-tests read.cpp reason.cpp resume_context.cpp - rfc2616.cpp rfc7230.cpp status.cpp streambuf_body.cpp diff --git a/test/http/basic_parser_v1.cpp b/test/http/basic_parser_v1.cpp index bef3d4cd..b5ef62fd 100644 --- a/test/http/basic_parser_v1.cpp +++ b/test/http/basic_parser_v1.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -135,6 +135,13 @@ public: { }; + static + std::string + str(boost::string_ref const& s) + { + return std::string{s.data(), s.size()}; + } + template class test_parser : public basic_parser_v1> @@ -146,8 +153,7 @@ public: { if(! value_.empty()) { - rfc2616::trim_right_in_place(value_); - fields.emplace(field_, value_); + fields.emplace(field_, str(detail::trim(value_))); field_.clear(); value_.clear(); } diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp index d197958f..4cbf2b57 100644 --- a/test/http/nodejs_parser.hpp +++ b/test/http/nodejs_parser.hpp @@ -11,7 +11,7 @@ #include "nodejs-parser/http_parser.h" #include -#include +#include #include #include #include @@ -611,7 +611,7 @@ nodejs_basic_parser::check_header() { if (! value_.empty()) { - rfc2616::trim_right_in_place(value_); + //detail::trim(value_); call_on_field(field_, value_, has_on_field{}); field_.clear(); diff --git a/test/http/rfc2616.cpp b/test/http/rfc2616.cpp deleted file mode 100644 index 346e838d..00000000 --- a/test/http/rfc2616.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// - -// Test that header file is self-contained. -#include - -#include -#include -#include - -namespace beast { -namespace rfc2616 { -namespace test { - -class rfc2616_test : public beast::unit_test::suite -{ -public: - void - checkSplit(std::string const& s, - std::vector const& expected) - { - auto const parsed = split_commas(s.begin(), s.end()); - expect (parsed == expected); - } - - void testSplit() - { - checkSplit("", {}); - checkSplit(" ", {}); - checkSplit(" ", {}); - checkSplit("\t", {}); - checkSplit(" \t ", {}); - checkSplit(",", {}); - checkSplit(",,", {}); - checkSplit(" ,", {}); - checkSplit(" , ,", {}); - checkSplit("x", {"x"}); - checkSplit(" x", {"x"}); - checkSplit(" \t x", {"x"}); - checkSplit("x ", {"x"}); - checkSplit("x \t", {"x"}); - checkSplit(" \t x \t ", {"x"}); - checkSplit("\"\"", {}); - checkSplit(" \"\"", {}); - checkSplit("\"\" ", {}); - checkSplit("\"x\"", {"x"}); - checkSplit("\" \"", {" "}); - checkSplit("\" x\"", {" x"}); - checkSplit("\"x \"", {"x "}); - checkSplit("\" x \"", {" x "}); - checkSplit("\"\tx \"", {"\tx "}); - checkSplit("x,y", {"x", "y"}); - checkSplit("x ,\ty ", {"x", "y"}); - checkSplit("x, y, z", {"x","y","z"}); - checkSplit("x, \"y\", z", {"x","y","z"}); - checkSplit(",,x,,\"y\",,", {"x","y"}); - } - - void - checkIter(std::string const& s, - std::vector const& expected) - { - std::vector got; - for(auto const& v : make_list(s)) - got.emplace_back(v); - expect(got == expected); - } - - void - testIter() - { - checkIter("x", {"x"}); - checkIter(" x", {"x"}); - checkIter("x\t", {"x"}); - checkIter("\tx ", {"x"}); - checkIter(",x", {"x"}); - checkIter("x,", {"x"}); - checkIter(",x,", {"x"}); - checkIter(" , x\t,\t", {"x"}); - checkIter("x,y", {"x", "y"}); - checkIter("x, ,y ", {"x", "y"}); - checkIter("\"x\"", {"x"}); - } - - void - testList() - { - expect(token_in_list("x", "x")); - expect(token_in_list("x,y", "x")); - expect(token_in_list("x,y", "y")); - expect(token_in_list("x, y ", "y")); - expect(token_in_list("x", "X")); - expect(token_in_list("Y", "y")); - expect(token_in_list("close, keepalive", "close")); - expect(token_in_list("close, keepalive", "keepalive")); - } - - void - run() - { - testSplit(); - testIter(); - testList(); - } -}; - -BEAST_DEFINE_TESTSUITE(rfc2616,http,beast); - -} // test -} // rfc2616 -} // beast diff --git a/test/http/rfc7230.cpp b/test/http/rfc7230.cpp index 93a1a81a..c63fd8ae 100644 --- a/test/http/rfc7230.cpp +++ b/test/http/rfc7230.cpp @@ -7,3 +7,240 @@ // Test that header file is self-contained. #include + +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace test { + +class rfc7230_test : public beast::unit_test::suite +{ +public: + static + std::string + fmt(std::string const& s) + { + return '\'' + s + '\''; + } + + static + std::string + str(boost::string_ref const& s) + { + return std::string(s.data(), s.size()); + } + + static + std::string + str(param_list const& c) + { + std::string s; + for(auto const& p : c) + { + s.push_back(';'); + s.append(str(p.first)); + s.push_back('='); + s.append(str(p.second)); + } + return s; + } + + void + testParamList() + { + auto const ce = + [&](std::string const& s) + { + auto const got = str(param_list{s}); + expect(got == s, fmt(got)); + }; + auto const cs = + [&](std::string const& s, std::string const& good) + { + ce(good); + auto const got = str(param_list{s}); + ce(got); + expect(got == good, fmt(got)); + }; + auto const cq = + [&](std::string const& s, std::string const& good) + { + auto const got = str(param_list{s}); + expect(got == good, fmt(got)); + }; + + ce(""); + cs(" ;\t i =\t 1 \t", ";i=1"); + cq("\t; \t xyz=1 ; ijk=\"q\\\"t\"", ";xyz=1;ijk=q\"t"); + + // invalid strings + cs(";", ""); + cs(";,", ""); + cs(";xy", ""); + cs(";xy", ""); + cs(";xy ", ""); + cs(";xy,", ""); + + cq(";x=,", ""); + cq(";xy=\"", ""); + cq(";xy=\"\x7f", ""); + cq(";xy=\"\\", ""); + cq(";xy=\"\\\x01\"", ""); + } + + static + std::string + str(ext_list const& ex) + { + std::string s; + for(auto const& e : ex) + { + if(! s.empty()) + s += ','; + s.append(str(e.first)); + s += str(e.second); + } + return s; + } + + void + testExtList() + { + auto const ce = + [&](std::string const& s) + { + auto const got = str(ext_list{s}); + expect(got == s, fmt(got)); + }; + auto const cs = + [&](std::string const& s, std::string const& good) + { + ce(good); + auto const got = str(ext_list{s}); + ce(got); + expect(got == good, fmt(got)); + }; + auto const cq = + [&](std::string const& s, std::string const& good) + { + auto const got = str(ext_list{s}); + expect(got == good, fmt(got)); + }; + /* + ext-list = *( "," OWS ) ext *( OWS "," [ OWS ext ] ) + ext = token param-list + param-list = *( OWS ";" OWS param ) + param = token OWS "=" OWS ( token / quoted-string ) + */ + ce(""); + cs(",", ""); + cs(", ", ""); + cs(",\t", ""); + cs(", \t", ""); + cs(" ", ""); + cs(" ,", ""); + cs("\t,", ""); + cs("\t , \t", ""); + cs(",,", ""); + cs(" , \t,, \t,", ""); + + ce("a"); + ce("ab"); + ce("a,b"); + cs(" a ", "a"); + cs("\t a, b\t , c\t", "a,b,c"); + + cs("a; \t i\t=\t \t1\t ", "a;i=1"); + ce("a;i=1;j=2;k=3"); + ce("a;i=1;j=2;k=3,b;i=4;j=5;k=6"); + + cq("ab;x=\" \"", "ab;x= "); + cq("ab;x=\"\\\"\"", "ab;x=\""); + + expect(ext_list{"a,b;i=1,c;j=2;k=3"}.exists("A")); + expect(ext_list{"a,b;i=1,c;j=2;k=3"}.exists("b")); + expect(! ext_list{"a,b;i=1,c;j=2;k=3"}.exists("d")); + + // invalid strings + cs("i j", "i"); + cs(";", ""); + } + + static + std::string + str(token_list const& c) + { + bool first = true; + std::string s; + for(auto const& p : c) + { + if(! first) + s.push_back(','); + s.append(str(p)); + first = false; + } + return s; + } + + void + testTokenList() + { + auto const ce = + [&](std::string const& s) + { + auto const got = str(token_list{s}); + expect(got == s, fmt(got)); + }; + auto const cs = + [&](std::string const& s, std::string const& good) + { + ce(good); + auto const got = str(token_list{s}); + ce(got); + expect(got == good, fmt(got)); + }; + + cs("", ""); + cs(" ", ""); + cs(" ", ""); + cs("\t", ""); + cs(" \t ", ""); + cs(",", ""); + cs(",,", ""); + cs(" ,", ""); + cs(" , ,", ""); + cs(" x", "x"); + cs(" \t x", "x"); + cs("x ", "x"); + cs("x \t", "x"); + cs(" \t x \t ", "x"); + ce("x,y"); + cs("x ,\ty ", "x,y"); + cs("x, y, z", "x,y,z"); + + expect(token_list{"a,b,c"}.exists("A")); + expect(token_list{"a,b,c"}.exists("b")); + expect(! token_list{"a,b,c"}.exists("d")); + + // invalid + cs("x y", "x"); + } + + void + run() + { + testParamList(); + testExtList(); + testTokenList(); + } +}; + +BEAST_DEFINE_TESTSUITE(rfc7230,http,beast); + +} // test +} // http +} // beast