RESTinio
uri_helpers.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  escape functions.
7 */
8 
9 #pragma once
10 
11 #include <restinio/impl/include_fmtlib.hpp>
12 
13 #include <restinio/exception.hpp>
14 #include <restinio/utils/percent_encoding.hpp>
15 
16 #include <optional>
17 #include <string>
18 #include <unordered_map>
19 
20 namespace restinio
21 {
22 
23 namespace impl
24 {
25 
26 inline const char *
27 modified_memchr( int chr , const char * from, const char * to )
28 {
29  const char * result = static_cast< const char * >(
30  std::memchr( from, chr, static_cast<std::size_t>(to - from) ) );
31 
32  return result ? result : to;
33 }
34 
35 } /* namespace impl */
36 
37 //
38 // query_string_params_t
39 //
40 
41 //! Parameters container for query strings parameters.
42 class query_string_params_t final
43 {
44  public:
45  using parameters_container_t = std::vector< std::pair< string_view_t, string_view_t > >;
46 
47  //! Constructor for the case when query string empty of
48  //! contains a set of key-value pairs.
50  std::unique_ptr< char[] > data_buffer,
51  parameters_container_t parameters )
54  {}
55 
56  //! Constructor for the case when query string contains only tag
57  //! (web beacon).
59  std::unique_ptr< char[] > data_buffer,
60  std::optional< string_view_t > tag )
62  , m_tag{ tag }
63  {}
64 
65  query_string_params_t( query_string_params_t && ) = default;
66  query_string_params_t & operator = ( query_string_params_t && ) = default;
67 
68  query_string_params_t( const query_string_params_t & ) = delete;
69  query_string_params_t & operator = ( const query_string_params_t & ) = delete;
70 
71  //! Get parameter.
73  operator [] ( string_view_t key ) const
74  {
75  return find_parameter_with_check( key ).second;
76  }
77 
78  //! Check parameter.
79  bool
80  has( string_view_t key ) const noexcept
81  {
82  return m_parameters.end() != find_parameter( key );
83  }
84 
85  //! Get the value of a parameter if it exists.
86  //! @since v.0.4.4
88  get_param( string_view_t key ) const noexcept
89  {
90  const auto it = find_parameter( key );
91 
92  return m_parameters.end() != it ?
93  std::optional< string_view_t >{ it->second } :
94  std::optional< string_view_t >{ std::nullopt };
95  }
96 
97  //! Get the size of parameters.
98  auto size() const noexcept { return m_parameters.size(); }
99 
100  //! Is there any parameters?
101  //! @since v.0.4.8
102  bool empty() const noexcept { return m_parameters.empty(); }
103 
104  //! @name Iterate parameters.
105  //! @{
107  begin() const noexcept
108  {
109  return m_parameters.begin();
110  }
111 
113  end() const noexcept
114  {
115  return m_parameters.end();
116  }
117  //! @}
118 
119  //! Get the tag (web beacon) part.
120  /*!
121  A value of "tag" (also known as web beacon) is available only
122  if URI looks like that:
123  \verbatim
124  http://example.com/resource?value
125  \endverbatim
126  In that case tag will contain `value`. For URI with different
127  formats tag() will return empty optional.
128 
129  @since v.0.4.9
130  */
131  auto tag() const noexcept { return m_tag; }
132 
133  private:
135  find_parameter( string_view_t key ) const noexcept
136  {
137  return
138  std::find_if(
139  m_parameters.begin(),
140  m_parameters.end(),
141  [&]( const auto p ){
142  return key == p.first;
143  } );
144  }
145 
148  {
149  auto it = find_parameter( key );
150 
151  if( m_parameters.end() == it )
152  {
153  throw exception_t{
154  fmt::format(
155  RESTINIO_FMT_FORMAT_STRING(
156  "unable to find parameter \"{}\"" ),
157  std::string{ key.data(), key.size() } ) };
158  }
159 
160  return *it;
161  }
162 
163  //! Shared buffer for string_view of named parameterts names.
166 
167  //! Tag (or web beacon) part.
168  /*! @since v.0.4.9 */
170 };
171 
172 //! Cast query string parameter to a given type.
173 template < typename Value_Type >
174 Value_Type
175 get( const query_string_params_t & params, string_view_t key )
176 {
177  return std::get< Value_Type >( params[ key ] );
178 }
179 
181 {
182 
183 namespace details
184 {
185 
186 /*!
187  * @brief Helper class to be reused in implementation of query-string
188  * parsing traits.
189  *
190  * Implements `find_next_separator` method that recongnizes `&` and
191  * `;` as `name=value` separators.
192  *
193  * @since v.0.6.5
194  */
196 {
197  static string_view_t::size_type
201  {
202  return where.find_first_of( "&;", start_from );
203  }
204 };
205 
206 /*!
207  * @brief Helper class to be reused in implementation of query-string
208  * parsing traits.
209  *
210  * Implements `find_next_separator` method that recongnizes only `&`
211  * `name=value` separator.
212  *
213  * @since v.0.6.5
214  */
216 {
217  static string_view_t::size_type
221  {
222  return where.find_first_of( '&', start_from );
223  }
224 };
225 
226 } /* namespace details */
227 
228 /*!
229  * @brief Traits for the default RESTinio parser for query string.
230  *
231  * The default RESTinio parser prohibit usage of unexcaped asterisk.
232  *
233  * @note
234  * This traits type is used by default. It means that a call:
235  * @code
236  * auto result = restinio::parse_query<restinio::parse_query_traits::restinio_defaults>("name=value");
237  * @endcode
238  * is equivalent to:
239  * @code
240  * auto result = restinio::parse_query("name=value");
241  * @endcode
242  *
243  * @since v.0.4.9.1
244  */
248 {};
249 
250 /*!
251  * @brief Traits for parsing a query string in JavaScript-compatible mode.
252  *
253  * In that mode several non-percent-encoded characters are allowed:
254  * `-`, `.`, `~`, `_`, `*`, `!`, `'`, `(`, `)`
255  *
256  * Usage example:
257  * @code
258  * auto result = restinio::parse_query<restinio::parse_query_traits::javascript_compatible>("name=A*");
259  * @endcode
260  *
261  * @since v.0.4.9.1
262  */
266 {};
267 
268 /*!
269  * @brief Traits for parsing a query string in
270  * application/x-www-form-urlencoded mode.
271  *
272  * In that mode:
273  *
274  * - `name=value` pairs can be concatenated only by `&`;
275  * - the following characters can only be used unescaped: `*` (0x2A), `-`
276  * (0x2D), `.` (0x2E), `_` (0x5F), `0`..`9` (0x30..0x39), `A`..`Z`
277  * (0x41..0x5A), `a`..`z` (0x61..0x7A);
278  * - space character (0x20) should be replaced by + (0x2B);
279  * - *all other characters should be represented as percent-encoded*.
280  *
281  * Reference for more details: https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
282  *
283  * Usage example:
284  * @code
285  * auto result = restinio::parse_query<restinio::parse_query_traits::x_www_form_urlencoded>("name=A*");
286  * @endcode
287  *
288  * @since v.0.6.5
289  */
293 {};
294 
295 /*!
296  * @brief Traits for parsing a query string in a very relaxed mode.
297  *
298  * In that mode all characters described in that rule from
299  * [RCF3986](https://tools.ietf.org/html/rfc3986) can be used as unexcaped:
300 @verbatim
301 query = *( pchar / "/" / "?" )
302 pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
303 unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
304 reserved = gen-delims / sub-delims
305 gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
306 sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
307  / "*" / "+" / "," / ";" / "="
308 @endverbatim
309  *
310  * Additionaly this traits allows to use unexcaped space character.
311  *
312  * Note that despite that fact that symbols like `#`, `+`, `=` and `&` can be
313  * used in non-percent-encoded form they play special role and are interpreted
314  * special way. So such symbols should be percent-encoded if they are used as
315  * part of name or value in query string.
316  *
317  * Only ampersand (`&`) can be used as `name=value` pairs separator.
318  *
319  * Usage example:
320  * @code
321  * auto result = restinio::parse_query<restinio::parse_query_traits::relaxed>("a=(&b=)&c=[&d=]&e=!&f=,&g=;");
322  * @endcode
323  *
324  * @since v.0.6.5
325  */
326 struct relaxed
329 {};
330 
331 } /* namespace parse_query_traits */
332 
333 /*!
334  * @brief Type that indicates a failure of an attempt of query-string parsing.
335  *
336  * @since v.0.6.5
337  */
339 {
340  //! Description of a failure.
342 
343 public:
344  parse_query_failure_t( std::string description )
346  {}
348  utils::unescape_percent_encoding_failure_t && failure )
350  {}
351 
352  //! Get a reference to the description of the failure.
353  [[nodiscard]]
354  const std::string &
355  description() const noexcept { return m_description; }
356 
357  //! Get out the value of the description of the failure.
358  /*!
359  * This method is intended for cases when this description should be move
360  * elsewhere (to another object like parse_query_failure_t or to some
361  * exception-like object).
362  */
363  [[nodiscard]]
364  std::string
365  giveout_description() noexcept { return m_description; }
366 };
367 
368 /*!
369  * @brief Helper function for parsing query string.
370  *
371  * Unlike parse_query() function the try_parse_query() doesn't throw if
372  * some unsupported character sequence is found.
373  *
374  * @note
375  * Parsing traits should be specified explicitly.
376  *
377  * Usage example:
378  * @code
379  * auto result = restinio::try_parse_query<
380  * restinio::parse_query_traits::javascript_compatible>("name=A*&flags=!");
381  * if(!result) {
382  * std::cerr << "Unable to parse query-string: " << result.error().description() << std::endl;
383  * }
384  * else {
385  * const restinio::query_string_params_t & params = *result;
386  * ...
387  * }
388  * @endcode
389  *
390  * @attention
391  * This function is not noexcept and can throw on other types of
392  * failures (like unability to allocate a memory).
393  *
394  * @since v.0.6.5
395  */
396 template< typename Parse_Traits >
397 [[nodiscard]]
400  //! Query part of the request target.
402 {
403  std::unique_ptr< char[] > data_buffer;
405 
407  {
408  // Because query string is not empty a new buffer should be
409  // allocated and query string should be copied to it.
410  data_buffer.reset( new char[ original_query_string.size() ] );
411  std::memcpy(
412  data_buffer.get(),
415 
416  // Work with created buffer:
418  data_buffer.get(),
420  };
423 
424  while( pos < end_pos )
425  {
426  const auto eq_pos = work_query_string.find_first_of( '=', pos );
427 
428  if( string_view_t::npos == eq_pos )
429  {
430  // Since v.0.4.9 we should check the presence of tag (web beacon)
431  // in query string.
432  // Tag can be the only item in query string.
433  if( pos != 0u )
434  // The query string has illegal format.
436  fmt::format(
438  "invalid format of key-value pairs in query_string, "
439  "no '=' symbol starting from position {}" ),
440  pos )
441  } );
442  else
443  {
444  // Query string contains only tag (web beacon).
445  auto tag_unescape_result =
447  &data_buffer[ pos ],
448  end_pos - pos );
449  if( !tag_unescape_result )
452  } );
453 
456 
458  }
459  }
460 
461  const auto eq_pos_next = eq_pos + 1u;
466 
467  // Handle next pair of parameters found.
468  auto key_unescape_result =
470  &data_buffer[ pos ],
471  eq_pos - pos );
472  if( !key_unescape_result )
475  } );
476 
477  auto value_unescape_result =
481  if( !value_unescape_result )
484  } );
485 
489 
490  pos = separator_pos + 1u;
491  }
492  }
493 
494  return query_string_params_t{
495  std::move( data_buffer ),
496  std::move( parameters )
497  };
498 }
499 
500 //! Parse query key-value parts.
501 /*!
502  Since v.0.4.9 this function correctly handles the following cases:
503 
504  - presence of tag (web beacon) in URI. For example, when URI looks like
505  `http://example.com/resource?tag`. In that case value of the tag (web
506  beacon) can be obtained via query_string_params_t::tag() method.
507  References: [web beacon](https://en.wikipedia.org/wiki/Web_beacon) and
508  [query-string-tracking](https://en.wikipedia.org/wiki/Query_string#Tracking);
509  - usage of `;` instead of `&` as parameter separator.
510 
511  Since v.0.4.9.1 this function can be parametrized by parser traits. For
512  example:
513  @code
514  auto result = restinio::parse_query<restinio::parse_query_traits::javascript_compatible>("name=A*");
515  @endcode
516 */
517 template< typename Parse_Traits = parse_query_traits::restinio_defaults >
518 [[nodiscard]]
519 query_string_params_t
521  //! Query part of the request target.
522  string_view_t original_query_string )
523 {
525  if( !r )
527 
528  return std::move(*r);
529 }
530 
531 } /* namespace restinio */
static string_view_t::size_type find_next_separator(string_view_t where, string_view_t::size_type start_from) noexcept
auto tag() const noexcept
Get the tag (web beacon) part.
Traits for parsing a query string in a very relaxed mode.
parameters_container_t::const_iterator find_parameter(string_view_t key) const noexcept
const std::string & description() const noexcept
Get a reference to the description of the failure.
Helper class to be reused in implementation of query-string parsing traits.
parameters_container_t::const_reference find_parameter_with_check(string_view_t key) const
query_string_params_t & operator=(const query_string_params_t &)=delete
std::unique_ptr< char[] > m_data_buffer
Shared buffer for string_view of named parameterts names.
parse_query_failure_t(std::string description)
std::string m_description
Description of a failure.
static string_view_t::size_type find_next_separator(string_view_t where, string_view_t::size_type start_from) noexcept
parameters_container_t m_parameters
query_string_params_t(const query_string_params_t &)=delete
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, std::optional< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in std::optional<Value_Type> if parameter exis...
Definition: value_or.hpp:64
parameters_container_t::const_iterator begin() const noexcept
Traits for parsing a query string in application/x-www-form-urlencoded mode.
auto size() const noexcept
Get the size of parameters.
Definition: uri_helpers.hpp:98
bool empty() const noexcept
Is there any parameters?
std::optional< string_view_t > get_param(string_view_t key) const noexcept
Get the value of a parameter if it exists.
Definition: uri_helpers.hpp:88
query_string_params_t parse_query(string_view_t original_query_string)
Parse query key-value parts.
bool has(string_view_t key) const noexcept
Check parameter.
Definition: uri_helpers.hpp:80
const char * modified_memchr(int chr, const char *from, const char *to)
Definition: uri_helpers.hpp:27
Traits for the default RESTinio parser for query string.
Value_Type get(const query_string_params_t &params, string_view_t key)
Cast query string parameter to a given type.
query_string_params_t(query_string_params_t &&)=default
string_view_t operator[](string_view_t key) const
Get parameter.
Definition: uri_helpers.hpp:73
query_string_params_t(std::unique_ptr< char[] > data_buffer, std::optional< string_view_t > tag)
Constructor for the case when query string contains only tag (web beacon).
Definition: uri_helpers.hpp:58
parse_query_failure_t(utils::unescape_percent_encoding_failure_t &&failure)
Traits for parsing a query string in JavaScript-compatible mode.
Type that indicates a failure of an attempt of query-string parsing.
query_string_params_t & operator=(query_string_params_t &&)=default
std::optional< string_view_t > m_tag
Tag (or web beacon) part.
parameters_container_t::const_iterator end() const noexcept
std::string giveout_description() noexcept
Get out the value of the description of the failure.
Helper class to be reused in implementation of query-string parsing traits.
expected_t< query_string_params_t, parse_query_failure_t > try_parse_query(string_view_t original_query_string)
Helper function for parsing query string.