/* * RESTinio */ /*! * @file * @brief Utilities for parsing values of http-fields * * @since v.0.6.1 */ #pragma once #include #include #include #include namespace restinio { namespace http_field_parsers { using namespace restinio::easy_parser; namespace meta = restinio::utils::metaprogramming; namespace qvalue_details { //! A type to hold a qvalue. using underlying_uint_t = std::uint_least16_t; //! The maximal allowed value for a qvalue. constexpr underlying_uint_t maximum = 1000u; //! The minimal allowed value for a qvalue. constexpr underlying_uint_t zero = 0u; // A part of workaround for problem with static constexpr members // detected after release of v.0.6.1. enum class extremum_min_t { v }; enum class extremum_max_t { v }; //! A helper wrapper to indicate that value is already checked and //! shouldn't be checked again. class trusted { const underlying_uint_t m_value; public : explicit constexpr trusted( underlying_uint_t value ) noexcept : m_value{ value } {} constexpr auto get() const noexcept { return m_value; } }; //! A helper wrapper to indicate that value hasn't been checked yet //! and should be checked in the constructor of qvalue. class untrusted { underlying_uint_t m_value; public : explicit untrusted( underlying_uint_t value ) : m_value{ value } { if( m_value > maximum ) throw exception_t( "invalid value for " "http_field_parser::rfc::qvalue_t" ); } auto get() const noexcept { return m_value; } }; } /* namespace qvalue_details */ // // qvalue_t // /*! * @brief A class for holding the parsed value of qvalue from RFC7231. * * An important note: qvalue in RFC7231 is defined as a number with * fractional point. This number can have no more that three digits * after a point. And the non-factional part can only be 0 or 1. If * non-frational part is 1 then fractional part can only be 0. It means * that qvalue can be in the range [0.000, 1.000]. * * To simplify work with qvalue RESTinio holds qvalue as a small integer * in the range [0, 1000] where 0 corresponds to 0.000 and 1000 corresponds * to 1.000. In means, for example, that value 0.05 will be represented and 50, * and the value 0.001 will be represented as 1, and the value 0.901 will * be represented as 901. * * The integer representation of qvalue can be obtained via as_uint() * method. * * Method as_string() returns std::string with number with fractional * part inside. It means: * @code * assert("1.000" == qvalue_t{qvalue_t::maximum}.as_string()); * assert("0.901" == qvalue_t{qvalue_t::untrusted{901}}.as_string()); * @endcode * Such representation is also used in `operator<<` for std::ostream. * * Instances of qvalue_t can be compared, operations `==`, `!=`, `<` and `<=` * are supported. * * There are two ways to construct a new qvalue object. * * The first and recommended way is intended for cases when the source * value for a new qvalue object isn't known at the compile-time and * produced somehow at the run-time. It's the usage of qvalue_t::untrusted * wrapper: * @code * qvalue_t weight{qvalue_t::untrusted{config.default_weigh() * 10}}; * @endcode * In that case the value of `untrusted` will be checked in * qvalue_t::untrusted's constructor and an exception will be thrown if that * value isn't in [0, 1000] range. * * The second way is intended to be used with compile-time constants: * @code * qvalue_t weight{qvalue_t::trusted{250}}; * @endcode * In the case of `trusted` the value is not checked in qvalue_t's * constructor. Therefore that way should be used with additional care. * * @since v.0.6.1 */ class qvalue_t { public : //! The type of underlying small integer. using underlying_uint_t = qvalue_details::underlying_uint_t; //! The type that doesn't check a value to be used for qvalue. using trusted = qvalue_details::trusted; //! The type that checks a value to be used for qvalue. using untrusted = qvalue_details::untrusted; //! The indicator that tells that new qvalue object should have //! the maximal allowed value. static constexpr qvalue_details::extremum_max_t maximum = qvalue_details::extremum_max_t::v; //! The indicator that tells that new qvalue object should have //! the minimal allowed value. static constexpr qvalue_details::extremum_min_t zero = qvalue_details::extremum_min_t::v; private : // Note: with the terminal 0-symbol. using underlying_char_array_t = std::array; underlying_uint_t m_value{}; underlying_char_array_t make_char_array() const noexcept { underlying_char_array_t result; if( qvalue_details::maximum == m_value ) { std::strcpy( &result[0], "1.000" ); } else { result[0] = '0'; result[1] = '.'; result[2] = '0' + static_cast(m_value / 100u); const auto d2 = m_value % 100u; result[3] = '0' + static_cast(d2 / 10u); const auto d3 = d2 % 10u; result[4] = '0' + static_cast(d3); result[5] = 0; } return result; } public : constexpr qvalue_t() = default; qvalue_t( untrusted val ) noexcept : m_value{ val.get() } {} constexpr qvalue_t( trusted val ) noexcept : m_value{ val.get() } {} constexpr qvalue_t( qvalue_details::extremum_min_t ) noexcept : m_value{ qvalue_details::zero } {} constexpr qvalue_t( qvalue_details::extremum_max_t ) noexcept : m_value{ qvalue_details::maximum } {} constexpr auto as_uint() const noexcept { return m_value; } auto as_string() const { return std::string{ &make_char_array().front() }; } friend std::ostream & operator<<( std::ostream & to, const qvalue_t & what ) { return (to << &what.make_char_array().front()); } }; RESTINIO_NODISCARD inline bool operator==( const qvalue_t & a, const qvalue_t & b ) noexcept { return a.as_uint() == b.as_uint(); } RESTINIO_NODISCARD inline bool operator!=( const qvalue_t & a, const qvalue_t & b ) noexcept { return a.as_uint() != b.as_uint(); } RESTINIO_NODISCARD inline bool operator<( const qvalue_t & a, const qvalue_t & b ) noexcept { return a.as_uint() < b.as_uint(); } RESTINIO_NODISCARD inline bool operator<=( const qvalue_t & a, const qvalue_t & b ) noexcept { return a.as_uint() <= b.as_uint(); } namespace impl { using namespace restinio::easy_parser::impl; // // is_alpha // /*! * @brief Is a character an ALPHA? * * See: https://tools.ietf.org/html/rfc5234#appendix-B.1 * * @since v.0.6.1 */ RESTINIO_NODISCARD inline constexpr bool is_alpha( const char ch ) noexcept { return (ch >= '\x41' && ch <= '\x5A') || (ch >= '\x61' && ch <= '\x7A'); } // // is_alpha_predicate_t // /*! * @brief A preducate for symbol_producer_template that checks that * a symbol is an alpha. * * @since v.0.6.2 */ struct is_alpha_predicate_t { RESTINIO_NODISCARD bool operator()( const char actual ) const noexcept { return is_alpha(actual); } }; // // is_alphanum_predicate_t // /*! * @brief A preducate for symbol_producer_template that checks that * a symbol is an alpha or numeric. * * @since v.0.6.2 */ struct is_alphanum_predicate_t { RESTINIO_NODISCARD bool operator()( const char actual ) const noexcept { return is_alpha(actual) || is_digit(actual); } }; // // is_vchar // /*! * @brief Is a character a VCHAR? * * See: https://tools.ietf.org/html/rfc5234#appendix-B.1 * * @since v.0.6.1 */ RESTINIO_NODISCARD inline constexpr bool is_vchar( const char ch ) noexcept { return (ch >= '\x21' && ch <= '\x7E'); } // // is_vchar_predicate_t // /*! * @brief A preducate for symbol_producer_template that checks that * a symbol is a VCHAR. * * @since v.0.6.2 */ struct is_vchar_predicate_t { RESTINIO_NODISCARD bool operator()( const char actual ) const noexcept { return is_vchar(actual); } }; // // is_obs_text // /*! * @brief Is a character an obs_text? * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.1 */ RESTINIO_NODISCARD inline constexpr bool is_obs_text( const char ch ) noexcept { constexpr unsigned short left = 0x80u; constexpr unsigned short right = 0xFFu; const unsigned short t = static_cast( static_cast(ch)); return (t >= left && t <= right); } // // is_qdtext // /*! * @brief Is a character a qdtext? * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.1 */ RESTINIO_NODISCARD inline constexpr bool is_qdtext( const char ch ) noexcept { return ch == SP || ch == HTAB || ch == '!' || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E') || is_obs_text( ch ); } // // is_ctext // /*! * @brief Is a character a ctext? * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.4 */ RESTINIO_NODISCARD inline constexpr bool is_ctext( const char ch ) noexcept { return ch == SP || ch == HTAB || (ch >= '\x21' && ch <= '\x27') || (ch >= '\x2A' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E') || is_obs_text( ch ); } // // is_ctext_predicate_t // /*! * @brief A preducate for symbol_producer_template that checks that * a symbol is a ctext. * * @since v.0.6.4 */ struct is_ctext_predicate_t { RESTINIO_NODISCARD bool operator()( const char actual ) const noexcept { return is_ctext(actual); } }; // // is_token_char_predicate_t // /*! * @brief A predicate for symbol_producer_template that checks that * a symbol can be used inside a token. */ struct is_token_char_predicate_t { RESTINIO_NODISCARD static constexpr bool is_token_char( const char ch ) noexcept { return is_alpha(ch) || is_digit(ch) || ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' || ch == '+' || ch == '-' || ch == '.' || ch == '^' || ch == '_' || ch == '`' || ch == '|' || ch == '~'; } RESTINIO_NODISCARD bool operator()( const char actual ) const noexcept { return is_token_char(actual); } }; // // ows_producer_t // /*! * @brief A producer for OWS. * * If an OWS found in the input stream it produces non-empty * optional_t with SP as the value. * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.1 */ class ows_producer_t : public producer_tag< restinio::optional_t > { public : RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) const noexcept { std::size_t extracted_spaces{}; character_t ch; for( ch = from.getch(); !ch.m_eof && is_space(ch.m_ch); ch = from.getch() ) { ++extracted_spaces; } if( !ch.m_eof ) // The first non-space char should be returned back. from.putback(); if( extracted_spaces > 0u ) return result_type{ ' ' }; return result_type{ nullopt }; } }; // // token_producer_t // /*! * @brief A producer for token. * * If a token is found in the input stream it produces std::string * with the value of that token. * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.1 */ class token_producer_t : public producer_tag< std::string > { RESTINIO_NODISCARD static optional_t< parse_error_t > try_parse_value( source_t & from, std::string & accumulator ) { error_reason_t reason = error_reason_t::pattern_not_found; do { const auto ch = from.getch(); if( ch.m_eof ) { reason = error_reason_t::unexpected_eof; break; } if( !is_token_char(ch.m_ch) ) { from.putback(); reason = error_reason_t::unexpected_character; break; } accumulator += ch.m_ch; } while( true ); if( accumulator.empty() ) { return parse_error_t{ from.current_position(), reason }; } return nullopt; } RESTINIO_NODISCARD static constexpr bool is_token_char( const char ch ) noexcept { return is_token_char_predicate_t::is_token_char( ch ); } public : RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) const { std::string value; const auto try_result = try_parse_value( from, value ); if( !try_result ) return { std::move(value) }; else return make_unexpected( *try_result ); } }; // // quoted_string_producer_t // /*! * @brief A producer for quoted_string. * * If a quoted_string is found in the input stream it produces std::string * with the value of that token. * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.1 */ class quoted_string_producer_t : public producer_tag< std::string > { RESTINIO_NODISCARD static optional_t< parse_error_t > try_parse_value( source_t & from, std::string & accumulator ) { error_reason_t reason = error_reason_t::pattern_not_found; bool second_quote_extracted{ false }; do { const auto ch = from.getch(); if( ch.m_eof ) { reason = error_reason_t::unexpected_eof; break; } if( '"' == ch.m_ch ) second_quote_extracted = true; else if( '\\' == ch.m_ch ) { const auto next = from.getch(); if( next.m_eof ) { reason = error_reason_t::unexpected_eof; break; } else if( SP == next.m_ch || HTAB == next.m_ch || is_vchar( next.m_ch ) || is_obs_text( next.m_ch ) ) { accumulator += next.m_ch; } else { reason = error_reason_t::unexpected_character; from.putback(); break; } } else if( is_qdtext( ch.m_ch ) ) accumulator += ch.m_ch; else { reason = error_reason_t::unexpected_character; from.putback(); break; } } while( !second_quote_extracted ); if( !second_quote_extracted ) return parse_error_t{ from.current_position(), reason }; else return nullopt; } public : RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) const { source_t::content_consumer_t consumer{ from }; const auto ch = from.getch(); if( !ch.m_eof ) { if( '"' == ch.m_ch ) { std::string value; const auto try_result = try_parse_value( from, value ); if( !try_result ) { consumer.commit(); return { std::move(value) }; } else return make_unexpected( *try_result ); } else { return make_unexpected( parse_error_t{ consumer.started_at(), error_reason_t::unexpected_character } ); } } else return make_unexpected( parse_error_t{ consumer.started_at(), error_reason_t::unexpected_eof } ); } }; // // quoted_pair_producer_t // /*! * @brief A producer for quoted_pair. * * If a quoted_pair is found in the input stream it produces char * with the value of that token. * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.4 */ class quoted_pair_producer_t : public producer_tag< char > { public : RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) const { source_t::content_consumer_t consumer{ from }; error_reason_t reason = error_reason_t::unexpected_eof; const auto ch = from.getch(); if( !ch.m_eof ) { if( '\\' == ch.m_ch ) { const auto next = from.getch(); if( !next.m_eof ) { if( SP == next.m_ch || HTAB == next.m_ch || is_vchar( next.m_ch ) || is_obs_text( next.m_ch ) ) { consumer.commit(); return next.m_ch; } reason = error_reason_t::unexpected_character; } } else reason = error_reason_t::unexpected_character; } return make_unexpected( parse_error_t{ from.current_position(), reason } ); } }; // // comment_producer_t // /*! * @brief A producer for comment. * * If a comment is found in the input stream it produces std::string * with the value of that token. * * Comment is defined that way: @verbatim comment = "(" *( ctext / quoted-pair / comment ) ")" ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) @endverbatim * * See: https://tools.ietf.org/html/rfc7230 * * @since v.0.6.4 */ class comment_producer_t : public producer_tag< std::string > { public : RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) const; // NOTE: implementation below. }; } /* namespace impl */ // // alpha_symbol_producer // /*! * @brief A factory for producer of ALPHA symbols. * * Usage example: * @code produce( repeat(1, 20, alpha_symbol_p() >> to_container()); * @endcode * * @since v.0.6.2 */ RESTINIO_NODISCARD inline auto alpha_symbol_p() { return restinio::easy_parser::impl::symbol_producer_template_t< impl::is_alpha_predicate_t >{}; } // // alphanum_symbol_producer // /*! * @brief A factory for producer of symbol that an ALPHA or DIGIT. * * Usage example: * @code produce( repeat(1, 20, alphanum_symbol_p() >> to_container()); * @endcode * * @since v.0.6.2 */ RESTINIO_NODISCARD inline auto alphanum_symbol_p() { return restinio::easy_parser::impl::symbol_producer_template_t< impl::is_alphanum_predicate_t >{}; } // // vchar_symbol_producer // /*! * @brief A factory for producer of VCHAR symbols. * * Usage example: * @code produce( repeat(1, 20, vchar_symbol_p() >> to_container()); * @endcode * * @since v.0.6.2 */ RESTINIO_NODISCARD inline auto vchar_symbol_p() { return restinio::easy_parser::impl::symbol_producer_template_t< impl::is_vchar_predicate_t >{}; } // // ctext_symbol_producer // /*! * @brief A factory for producer of ctext symbols. * * Usage example: * @code produce( repeat(1, 20, ctext_symbol_p() >> to_container()); * @endcode * * @since v.0.6.4 */ RESTINIO_NODISCARD inline auto ctext_symbol_p() { return restinio::easy_parser::impl::symbol_producer_template_t< impl::is_ctext_predicate_t >{}; } // // comment_producer // /*! * @brief A factory for producer of comment token. * * Usage example: * @code produce( alternatives( token_p() >> as_result(), comment_p() >> as_result() ) ); * @endcode * * @since v.0.6.4 */ RESTINIO_NODISCARD inline auto comment_p() { return impl::comment_producer_t{}; } // // ows_producer // /*! * @brief A factory function to create an ows_producer. * * Usage example: * @code * produce( * ows_p() >> skip(), * symbol('v'), * symbol('='), * ows_p() >> skip(), * token_p() >> as_result() * ); * @endcode * * @note * Factory function ows() can be used instead of expression `ows_p() >> skip()`. * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto ows_p() noexcept { return impl::ows_producer_t{}; } // // ows // /*! * @brief A factory function to create an OWS clause. * * This clause handles an optional sequence of spaces in the input stream and * skips the value of that sequence. * * Usage example: * @code * produce( * ows(), * symbol('v'), * symbol('='), * ows(), * token_p() >> as_result() * ); * @endcode * This expression corresponds the following rule: @verbatim T := OWS 'v' '=' OWS token @endverbatim * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto ows() noexcept { return ows_p() >> skip(); } // // token_symbol_producer // /*! * @brief A factory for producer of symbols than can be used in tokens. * * Usage example: * @code produce( repeat(1, 20, token_symbol_p() >> to_container()); * @endcode * * @since v.0.6.9 */ RESTINIO_NODISCARD inline auto token_symbol_p() noexcept { return restinio::easy_parser::impl::symbol_producer_template_t< impl::is_token_char_predicate_t >{}; } // // token_producer // /*! * @brief A factory function to create a token_producer. * * Usage example: * @code * using parameter = std::pair; * produce( * ows(), * token_p() >> ¶meter::first, * symbol('='), * ows(), * token_p() >> ¶meter::second * ); * @endcode * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto token_p() noexcept { return impl::token_producer_t{}; } // // quoted_string_producer // /*! * @brief A factory function to create a quoted_string_producer. * * Usage example: * @code * using parameter = std::pair; * produce( * ows(), * token_p() >> ¶meter::first, * symbol('='), * ows(), * alternatives( * token_p() >> ¶meter::second, * quoted_string_p() >> ¶meter::second * ) * ); * @endcode * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto quoted_string_p() noexcept { return impl::quoted_string_producer_t{}; } // // quoted_pair_producer // /*! * @brief A factory function to create a quoted_pair_producer. * * Usage example: * @code * produce( * repeat(1, N, * alternatives( * ctext_symbol_p() >> to_container(), * quoted_pair_p() >> to_container() * ) * ); * @endcode * * @since v.0.6.4 */ RESTINIO_NODISCARD inline auto quoted_pair_p() noexcept { return impl::quoted_pair_producer_t{}; } // // expected_token_p // /*! * @brief A factory function to create a producer that expect a * token with specific value. * * If the expected token is successfully parsed then boolean value * is produced. * * Usage example: * @code * enum class compression { zlib, bz2, xz }; * produce( * expected_token_p("compression") >> skip(), * ows(), * symbol('='), * ows(), * alternatives( * expected_token_p("zlib") >> just_result(compression::zlib), * expected_token_p("bz2") >> just_result(compression::bz2), * expected_token_p("xz") >> just_result(compression::xz) * ) * ); * @endcode * * @since v.0.6.9 */ RESTINIO_NODISCARD inline auto expected_token_p( string_view_t token ) { return produce< bool >( exact_p( token ) >> as_result(), not_clause( token_symbol_p() >> skip() ) ); } // // expected_caseless_token_p // /*! * @brief A factory function to create a producer that expect a * token with specific value. * * This processer uses case-insensitive comparison. * * If the expected token is successfully parsed then boolean value * is produced. * * Usage example: * @code * enum class compression { zlib, bz2, xz }; * produce( * caseless_expected_token_p("Compression") >> skip(), * ows(), * symbol('='), * ows(), * alternatives( * caseless_expected_token_p("ZLib") >> just_result(compression::zlib), * caseless_expected_token_p("BZ2") >> just_result(compression::bz2), * caseless_expected_token_p("xz") >> just_result(compression::xz) * ) * ); * @endcode * * @since v.0.6.9 */ RESTINIO_NODISCARD inline auto expected_caseless_token_p( string_view_t token ) { return produce< bool >( caseless_exact_p( token ) >> as_result(), not_clause( token_symbol_p() >> skip() ) ); } namespace impl { // // comment_producer_t implementation // RESTINIO_NODISCARD inline expected_t< comment_producer_t::result_type, parse_error_t > comment_producer_t::try_parse( source_t & from ) const { return produce< std::string >( sequence( symbol('('), repeat(0, N, alternatives( ctext_symbol_p() >> to_container(), quoted_pair_p() >> to_container(), comment_p() >> custom_consumer( []( std::string & dest, std::string && what ) { dest += what; } ) ) ), symbol(')') ) ).try_parse( from ); } // // qvalue_producer_t // /*! * @brief An implementation of producer of qvalue. * * Handles the following rule: @verbatim qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] ) @endverbatim * and produces an instance of qvalue_t. * * See: https://tools.ietf.org/html/rfc7231 * * @since v.0.6.1 */ class qvalue_producer_t : public producer_tag< qvalue_t > { // This type has to be used as type parameter for produce(). struct zero_initialized_unit_t { qvalue_t::underlying_uint_t m_value{0u}; }; //! A helper class to be used to accumulate actual integer //! while when the next digit is extracted from the input stream. class digit_consumer_t : public consumer_tag { const qvalue_t::underlying_uint_t m_multiplier; public : constexpr digit_consumer_t( qvalue_t::underlying_uint_t m ) : m_multiplier{ m } {} void consume( zero_initialized_unit_t & dest, char && digit ) { dest.m_value += m_multiplier * static_cast< qvalue_t::underlying_uint_t >(digit - '0'); } }; public : RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) const noexcept { const auto parse_result = produce< zero_initialized_unit_t >( alternatives( sequence( symbol('0'), maybe( symbol('.'), maybe( digit_p() >> digit_consumer_t{100}, maybe( digit_p() >> digit_consumer_t{10}, maybe( digit_p() >> digit_consumer_t{1} ) ) ) ) ), sequence( symbol_p('1') >> digit_consumer_t{1000}, maybe( symbol('.'), maybe( symbol('0'), maybe( symbol('0'), maybe( symbol('0') ) ) ) ) ) ) ).try_parse( from ); if( parse_result ) return qvalue_t{ qvalue_t::trusted{ parse_result->m_value } }; else return make_unexpected( parse_result.error() ); } }; } /* namespace impl */ // // qvalue_producer // /*! * @brief A factory function to create a qvalue_producer. * * Usage example: * @code * produce( * alternatives(symbol('r'), symbol('R')), * symbol('='), * qvalue_p() >> as_result() * ); * @endcode * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto qvalue_p() noexcept { return impl::qvalue_producer_t{}; } // // weight_producer // /*! * @brief A factory function to create a producer for weight parameter. * * Returns a producer that handles the following rules: @verbatim weight = OWS ';' OWS ('q' / 'Q') '=' qvalue qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] ) @endverbatim * * See: https://tools.ietf.org/html/rfc7231 * * That producer produces a value of type qvalue_t. * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto weight_p() noexcept { return produce< qvalue_t >( ows(), symbol(';'), ows(), caseless_symbol('q'), symbol('='), qvalue_p() >> as_result() ); } namespace impl { // // non_empty_comma_separated_list_producer_t // /*! * @brief A template for a producer that handles non-empty list of * comma-separated values. * * That producer handles the following rule: @verbatim 1#element => *( "," OWS ) element *( OWS "," [ OWS element ] ) @endverbatim * * See: https://tools.ietf.org/html/rfc7230 * (section "7. ABNF List Extension: #rule") * * @tparam Container the type of container to be produced. * * @tparam Element_Producer the type of the producer of a single item. * * @since v.0.6.1 */ template< typename Container, typename Element_Producer > class non_empty_comma_separated_list_producer_t : public producer_tag< Container > { static_assert( impl::is_producer_v, "Element_Producer should be a value producer type" ); Element_Producer m_element; public : using typename producer_tag< Container >::result_type; non_empty_comma_separated_list_producer_t( Element_Producer && element ) : m_element{ std::move(element) } {} RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) { Container tmp_value; const auto appender = to_container(); const auto process_result = sequence( repeat( 0, N, symbol(','), ows() ), m_element >> appender, repeat( 0, N, ows(), symbol(','), maybe( ows(), m_element >> appender ) ) ).try_process( from, tmp_value ); if( !process_result ) return { std::move(tmp_value) }; else return make_unexpected( *process_result ); } }; // // maybe_empty_comma_separated_list_producer_t // /*! * @brief A template for a producer that handles possibly empty list of * comma-separated values. * * That producer handles the following rule: @verbatim #element => [ ( "," / element ) *( OWS "," [ OWS element ] ) ] @endverbatim * * See: https://tools.ietf.org/html/rfc7230 * (section "7. ABNF List Extension: #rule") * * @tparam Container the type of container to be produced. * * @tparam Element_Producer the type of the producer of a single item. * * @since v.0.6.1 */ template< typename Container, typename Element_Producer > class maybe_empty_comma_separated_list_producer_t : public producer_tag< Container > { static_assert( impl::is_producer_v, "Element_Producer should be a value producer type" ); Element_Producer m_element; public : using typename producer_tag< Container >::result_type; maybe_empty_comma_separated_list_producer_t( Element_Producer && element ) : m_element{ std::move(element) } {} RESTINIO_NODISCARD expected_t< result_type, parse_error_t > try_parse( source_t & from ) { Container tmp_value; const auto appender = to_container(); const auto process_result = maybe( alternatives( symbol(','), m_element >> appender ), repeat( 0, N, ows(), symbol(','), maybe( ows(), m_element >> appender ) ) ).try_process( from, tmp_value ); if( !process_result ) return { std::move(tmp_value) }; else return make_unexpected( *process_result ); } }; } /* namespace impl */ // // non_empty_comma_separated_list_producer // /*! * @brief A factory for a producer that handles non-empty list of * comma-separated values. * * That producer handles the following rule: @verbatim 1#element => *( "," OWS ) element *( OWS "," [ OWS element ] ) @endverbatim * * See: https://tools.ietf.org/html/rfc7230 * (section "7. ABNF List Extension: #rule") * * Usage example: * @code auto parse = produce< byte_ranges_data >( make_bytes_prefix_parser(), non_empty_comma_separated_list_p< std::vector< byte_range > >( make_byte_range_parser() ) >> &byte_ranges_data::ranges ); * @endcode * * @tparam Container the type of container to be produced. * * @tparam Element_Producer the type of the producer of a single item. * * @since v.0.6.1 */ template< typename Container, typename Element_Producer > RESTINIO_NODISCARD auto non_empty_comma_separated_list_p( Element_Producer element ) { static_assert( impl::is_producer_v, "Element_Producer should be a value producer type" ); return impl::non_empty_comma_separated_list_producer_t< Container, Element_Producer >{ std::move(element) }; } // // maybe_empty_comma_separated_list_producer // /*! * @brief A factory for a producer that handles possibly empty list of * comma-separated values. * * That producer handles the following rule: @verbatim #element => [ ( "," / element ) *( OWS "," [ OWS element ] ) ] @endverbatim * * See: https://tools.ietf.org/html/rfc7230 * (section "7. ABNF List Extension: #rule") * * Usage example: * @code auto parse = produce< byte_ranges_data >( make_bytes_prefix_parser(), maybe_empty_comma_separated_list_p< std::vector< byte_range > >( make_byte_range_parser() ) >> &byte_ranges_data::ranges ); * @endcode * * @tparam Container the type of container to be produced. * * @tparam Element_Producer the type of the producer of a single item. * * @since v.0.6.1 */ template< typename Container, typename Element_Producer > RESTINIO_NODISCARD auto maybe_empty_comma_separated_list_p( Element_Producer element ) { static_assert( impl::is_producer_v, "Element_Producer should be a value producer type" ); return impl::maybe_empty_comma_separated_list_producer_t< Container, Element_Producer >{ std::move(element) }; } // // parameter_with_mandatory_value_t // /*! * @brief A type that describes a parameter with mandatory value. * * @since v.0.6.1 */ using parameter_with_mandatory_value_t = std::pair< std::string, std::string >; // // parameter_with_mandatory_value_container_t // /*! * @brief A type of container for parameters with mandatory values. * * @since v.0.6.1 */ using parameter_with_mandatory_value_container_t = std::vector< parameter_with_mandatory_value_t >; // // not_found_t // /*! * @brief An empty type to be used as indicator of negative search result. * * @since v.0.6.1 */ struct not_found_t {}; // // find_first // /*! * @brief A helper function to find the first occurence of a parameter * with the specified value. * * @note * The caseless (case-insentive) search is used. It means that * search with value "charset" will returns items "CharSet", "charset", * "CHARSET" and so on. * * Usage example: * @code * const auto charset = find_first(content_type_params, "charset"); * if(charset) { * ... // Handle the value of charset parameter. * } * @endcode * * @since v.0.6.1 */ RESTINIO_NODISCARD inline expected_t< string_view_t, not_found_t > find_first( const parameter_with_mandatory_value_container_t & where, string_view_t what ) { const auto it = std::find_if( where.begin(), where.end(), [&what]( const auto & pair ) { return restinio::impl::is_equal_caseless( pair.first, what ); } ); if( it != where.end() ) return string_view_t{ it->second }; else return make_unexpected( not_found_t{} ); } namespace impl { namespace params_with_value_producer_details { // // make_parser // /*! * @brief Helper function that creates an instance of producer * of parameter_with_mandatory_value_container. * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto make_parser() { return produce< parameter_with_mandatory_value_container_t >( repeat( 0, N, produce< parameter_with_mandatory_value_t >( ows(), symbol(';'), ows(), token_p() >> to_lower() >> ¶meter_with_mandatory_value_t::first, symbol('='), alternatives( token_p() >> ¶meter_with_mandatory_value_t::second, quoted_string_p() >> ¶meter_with_mandatory_value_t::second ) ) >> to_container() ) ); } } /* namespace params_with_value_producer_details */ // // params_with_value_producer_t // /*! * @brief A type of producer that produces instances of * parameter_with_mandatory_value_container. * * @since v.0.6.1 */ class params_with_value_producer_t : public producer_tag< parameter_with_mandatory_value_container_t > { using actual_producer_t = std::decay_t< decltype(params_with_value_producer_details::make_parser()) >; actual_producer_t m_producer{ params_with_value_producer_details::make_parser() }; public : params_with_value_producer_t() = default; RESTINIO_NODISCARD auto try_parse( source_t & from ) { return m_producer.try_parse( from ); } }; } /* namespace impl */ // // params_with_value_producer // /*! * @brief A factory of producer of parameter_with_mandatory_value_container. * * Creates a produces that handles the following rule: @verbatim T := *( OWS ';' OWS token '=' OWS (token / quoted_string)) @endverbatim * * Usage example: * @code * struct my_field { * std::string value; * parameter_with_mandatory_value_container_t params; * }; * produce( * token_p() >> to_lower() >> &my_field::value, * params_with_value_p() >> &my_field::params * ); * @endcode * * @note * Parameters names are converted to lower case. Parameters' values * are not changed and stored as they are. * * @since v.0.6.1 */ RESTINIO_NODISCARD inline impl::params_with_value_producer_t params_with_value_p() { return {}; } // // parameter_with_optional_value_t // /*! * @brief A type that describes a parameter with optional value. * * @since v.0.6.1 */ using parameter_with_optional_value_t = std::pair< std::string, restinio::optional_t >; // // parameter_with_optional_value_container_t // /*! * @brief A type of container for parameters with optional values. * * @since v.0.6.1 */ using parameter_with_optional_value_container_t = std::vector< parameter_with_optional_value_t >; // // find_first // /*! * @brief A helper function to find the first occurence of a parameter * with the specified value. * * @note * The caseless (case-insentive) search is used. It means that * search with value "charset" will returns items "CharSet", "charset", * "CHARSET" and so on. * * Usage example: * @code * const auto max_age = find_first(cache_control_params, "max-age"); * if(max_age) { * if(*max_age) { * ... // Handle the value of max-age parameter. * } * else { * ... // Handle the case where max-age specified but without a value. * } * } * @endcode * * @since v.0.6.1 */ RESTINIO_NODISCARD inline expected_t< restinio::optional_t, not_found_t > find_first( const parameter_with_optional_value_container_t & where, string_view_t what ) { const auto it = std::find_if( where.begin(), where.end(), [&what]( const auto & pair ) { return restinio::impl::is_equal_caseless( pair.first, what ); } ); if( it != where.end() ) { const auto opt = it->second; if( opt ) return string_view_t{ *opt }; else return restinio::optional_t< string_view_t >{ nullopt }; } else return make_unexpected( not_found_t{} ); } namespace impl { namespace params_with_opt_value_producer_details { // // make_parser // /*! * @brief Helper function that creates an instance of producer * of parameter_with_optional_value_container. * * @since v.0.6.1 */ RESTINIO_NODISCARD inline auto make_parser() { return produce< parameter_with_optional_value_container_t >( repeat( 0, N, produce< parameter_with_optional_value_t >( ows(), symbol(';'), ows(), token_p() >> to_lower() >> ¶meter_with_optional_value_t::first, maybe( symbol('='), alternatives( token_p() >> ¶meter_with_optional_value_t::second, quoted_string_p() >> ¶meter_with_optional_value_t::second ) ) ) >> to_container() ) ); } } /* namespace params_with_opt_value_producer_details */ // // params_with_opt_value_producer_t // /*! * @brief A type of producer that produces instances of * parameter_with_optional_value_container. * * @since v.0.6.1 */ class params_with_opt_value_producer_t : public producer_tag< parameter_with_optional_value_container_t > { using actual_producer_t = std::decay_t< decltype(params_with_opt_value_producer_details::make_parser()) >; actual_producer_t m_producer{ params_with_opt_value_producer_details::make_parser() }; public : params_with_opt_value_producer_t() = default; RESTINIO_NODISCARD auto try_parse( source_t & from ) { return m_producer.try_parse( from ); } }; } /* namespace impl */ // // params_with_opt_value_producer // /*! * @brief A factory of producer of parameter_with_optional_value_container. * * Creates a produces that handles the following rule: @verbatim T := *( OWS ';' OWS token ['=' OWS (token / quoted_string)] ) @endverbatim * * Usage example: * @code * struct my_field { * std::string value; * parameter_with_optional_value_container_t params; * }; * produce( * token_p() >> to_lower() >> &my_field::value, * params_with_opt_value_p() >> &my_field::params * ); * @endcode * * @note * Parameters names are converted to lower case. Parameters' values * are not changed and stored as they are. * * @since v.0.6.1 */ RESTINIO_NODISCARD inline impl::params_with_opt_value_producer_t params_with_opt_value_p() { return {}; } } /* namespace http_field_parser */ } /* namespace restinio */