RESTinio
Loading...
Searching...
No Matches
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
20namespace restinio
21{
22
23namespace impl
24{
25
26inline const char *
27modified_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.
43{
44 public:
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
67
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
147 find_parameter_with_check( string_view_t key ) const
148 {
149 auto it = find_parameter( key );
150
151 if( m_parameters.end() == it )
152 {
153 throw exception_t{
154 fmt::format(
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.
173template < typename Value_Type >
174Value_Type
175get( const query_string_params_t & params, string_view_t key )
176{
177 return std::get< Value_Type >( params[ key ] );
178}
179
181{
182
183namespace 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{
199 string_view_t where,
200 string_view_t::size_type start_from ) noexcept
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{
219 string_view_t where,
220 string_view_t::size_type start_from ) noexcept
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 */
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 */
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 */
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
301query = *( pchar / "/" / "?" )
302pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
303unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
304reserved = gen-delims / sub-delims
305gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
306sub-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 */
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.
341 std::string m_description;
342
343public:
344 parse_query_failure_t( std::string description )
345 : m_description{ std::move(description) }
346 {}
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 */
396template< typename Parse_Traits >
397[[nodiscard]]
400 //! Query part of the request target.
401 string_view_t original_query_string )
402{
403 std::unique_ptr< char[] > data_buffer;
404 query_string_params_t::parameters_container_t parameters;
405
406 if( !original_query_string.empty() )
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(),
413 original_query_string.data(),
414 original_query_string.size() );
415
416 // Work with created buffer:
417 string_view_t work_query_string{
418 data_buffer.get(),
419 original_query_string.size()
420 };
421 string_view_t::size_type pos{ 0 };
422 const string_view_t::size_type end_pos = work_query_string.size();
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.
435 return make_unexpected( parse_query_failure_t{
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 =
446 utils::try_inplace_unescape_percent_encoding< Parse_Traits >(
447 &data_buffer[ pos ],
448 end_pos - pos );
449 if( !tag_unescape_result )
450 return make_unexpected( parse_query_failure_t{
451 std::move(tag_unescape_result.error())
452 } );
453
454 const string_view_t tag = work_query_string.substr(
455 pos, *tag_unescape_result );
456
457 return query_string_params_t{ std::move( data_buffer ), tag };
458 }
459 }
460
461 const auto eq_pos_next = eq_pos + 1u;
462 auto separator_pos = Parse_Traits::find_next_separator(
463 work_query_string, eq_pos_next );
464 if( string_view_t::npos == separator_pos )
465 separator_pos = work_query_string.size();
466
467 // Handle next pair of parameters found.
468 auto key_unescape_result =
469 utils::try_inplace_unescape_percent_encoding< Parse_Traits >(
470 &data_buffer[ pos ],
471 eq_pos - pos );
472 if( !key_unescape_result )
473 return make_unexpected( parse_query_failure_t{
474 std::move(key_unescape_result.error())
475 } );
476
477 auto value_unescape_result =
478 utils::try_inplace_unescape_percent_encoding< Parse_Traits >(
479 &data_buffer[ eq_pos_next ],
480 separator_pos - eq_pos_next );
481 if( !value_unescape_result )
482 return make_unexpected( parse_query_failure_t{
483 std::move(value_unescape_result.error())
484 } );
485
486 parameters.emplace_back(
487 string_view_t{ &data_buffer[ pos ], *key_unescape_result },
488 string_view_t{ &data_buffer[ eq_pos_next ], *value_unescape_result } );
489
490 pos = separator_pos + 1u;
491 }
492 }
493
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*/
517template< typename Parse_Traits = parse_query_traits::restinio_defaults >
518[[nodiscard]]
521 //! Query part of the request target.
522 string_view_t original_query_string )
523{
524 auto r = try_parse_query< Parse_Traits >( original_query_string );
525 if( !r )
526 throw exception_t{ std::move(r.error().giveout_description()) };
527
528 return std::move(*r);
529}
530
531} /* namespace restinio */
Exception class for all exceptions thrown by RESTinio.
Definition exception.hpp:26
Type that indicates a failure of an attempt of query-string parsing.
std::string giveout_description() noexcept
Get out the value of the description of the failure.
const std::string & description() const noexcept
Get a reference to the description of the failure.
std::string m_description
Description of a failure.
parse_query_failure_t(std::string description)
parse_query_failure_t(utils::unescape_percent_encoding_failure_t &&failure)
std::unique_ptr< char[] > m_data_buffer
Shared buffer for string_view of named parameterts names.
query_string_params_t(query_string_params_t &&)=default
auto tag() const noexcept
Get the tag (web beacon) part.
bool empty() const noexcept
Is there any parameters?
auto size() const noexcept
Get the size of parameters.
parameters_container_t::const_iterator begin() const noexcept
query_string_params_t & operator=(query_string_params_t &&)=default
string_view_t operator[](string_view_t key) const
Get parameter.
parameters_container_t m_parameters
std::vector< std::pair< string_view_t, string_view_t > > parameters_container_t
std::optional< string_view_t > get_param(string_view_t key) const noexcept
Get the value of a parameter if it exists.
query_string_params_t(std::unique_ptr< char[] > data_buffer, parameters_container_t parameters)
Constructor for the case when query string empty of contains a set of key-value pairs.
parameters_container_t::const_iterator find_parameter(string_view_t key) const noexcept
query_string_params_t & operator=(const query_string_params_t &)=delete
parameters_container_t::const_reference find_parameter_with_check(string_view_t key) const
parameters_container_t::const_iterator end() const noexcept
bool has(string_view_t key) const noexcept
Check parameter.
query_string_params_t(const query_string_params_t &)=delete
std::optional< string_view_t > m_tag
Tag (or web beacon) part.
Type that indicates a failure of unescaping of percent-encoded symbols.
std::string giveout_description() noexcept
Get out the value of the description of the failure.
#define RESTINIO_FMT_FORMAT_STRING(s)
const char * modified_memchr(int chr, const char *from, const char *to)
query_string_params_t parse_query(string_view_t original_query_string)
Parse query key-value parts.
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.
Value_Type get(const query_string_params_t &params, string_view_t key)
Cast query string parameter to a given type.
Helper class to be reused in implementation of query-string parsing traits.
static string_view_t::size_type find_next_separator(string_view_t where, string_view_t::size_type start_from) noexcept
Helper class to be reused in implementation of query-string parsing traits.
static string_view_t::size_type find_next_separator(string_view_t where, string_view_t::size_type start_from) noexcept
Traits for parsing a query string in JavaScript-compatible mode.
Traits for parsing a query string in a very relaxed mode.
Traits for the default RESTinio parser for query string.
Traits for parsing a query string in application/x-www-form-urlencoded mode.
The traits for escaping and unexcaping symbols in JavaScript-compatible mode.
Traits for escaping and unescaping symbols in a query string in very relaxed mode.
The default traits for escaping and unexcaping symbols in a query string.
Traits for escaping and unexcaping symbols in a query string in correspondence with application/x-www...