RESTinio
percent_encoding.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  Percent encoding routine.
7 */
8 
9 #pragma once
10 
11 #include <string>
12 
13 #include <fmt/format.h>
14 
15 #include <restinio/string_view.hpp>
16 #include <restinio/exception.hpp>
17 
18 namespace restinio
19 {
20 
21 namespace utils
22 {
23 
24 /*!
25  * @brief The default traits for escaping and unexcaping symbols in
26  * a query string.
27  *
28  * Unescaped asterisk is not allowed.
29  *
30  * @since v.0.4.9.1
31  */
33 {
34  static constexpr bool
35  ordinary_char( char c ) noexcept
36  {
37  return
38  ( '0' <= c && c <= '9' ) ||
39  ( 'a' <= c && c <= 'z' ) ||
40  ( 'A' <= c && c <= 'Z' ) ||
41  '-' == c ||
42  '.' == c ||
43  '~' == c ||
44  '_' == c;
45  }
46 };
47 
48 /*!
49  * @brief The traits for escaping and unexcaping symbols in
50  * JavaScript-compatible mode.
51  *
52  * Unescaped asterisk is allowed.
53  *
54  * @since v.0.4.9.1
55  */
57 {
58  static constexpr bool
59  ordinary_char( char c ) noexcept
60  {
61  return
62  ( '0' <= c && c <= '9' ) ||
63  ( 'a' <= c && c <= 'z' ) ||
64  ( 'A' <= c && c <= 'Z' ) ||
65  '-' == c ||
66  '.' == c ||
67  '~' == c ||
68  '_' == c ||
69  '*' == c;
70  }
71 };
72 
73 namespace impl
74 {
75 
76 inline bool
77 is_hexdigit( char c )
78 {
79  return
80  ( '0' <= c && c <= '9' ) ||
81  ( 'a' <= c && c <= 'f' ) ||
82  ( 'A' <= c && c <= 'F' );
83 };
84 
85 inline char
86 extract_escaped_char( char c1, char c2 )
87 {
88  char result;
89 
90  if( '0' <= c1 && c1 <= '9' )
91  result = c1 - '0';
92  else
93  {
94  c1 |= 0x20;
95  result = 10 + c1 - 'a';
96  }
97 
98  result <<= 4;
99 
100  if( '0' <= c2 && c2 <= '9' )
101  result += c2 - '0';
102  else
103  {
104  c2 |= 0x20;
105  result += 10 + c2 - 'a';
106  }
107 
108  return result;
109 };
110 
111 } /* namespace impl */
112 
113 //! Percent encoding.
114 //! \{
115 template< typename Traits = restinio_default_unescape_traits >
116 std::string
118 {
119  std::string result;
120  const auto escaped_chars_count = static_cast<std::size_t>(
121  std::count_if(
122  data.begin(),
123  data.end(),
124  []( auto c ){ return !Traits::ordinary_char(c); } ));
125 
126  if( 0 == escaped_chars_count )
127  {
128  // No escaped chars.
129  result.assign( data.data(), data.size() );
130  }
131  else
132  {
133  // Having escaped chars.
135  for( auto c : data )
136  {
137  if( Traits::ordinary_char( c ) )
138  result += c;
139  else
140  {
141  result += fmt::format( "%{:02X}", c );
142  }
143  }
144  }
145 
146  return result;
147 }
148 
149 template< typename Traits = restinio_default_unescape_traits >
150 std::string
152 {
153  std::string result;
154  result.reserve( data.size() );
155 
157  const char * d = data.data();
158 
159  while( 0 < chars_to_handle )
160  {
161  char c = *d;
162  if( Traits::ordinary_char( c ) )
163  {
164  result += c;
165  --chars_to_handle;
166  ++d;
167  }
168  else if( '+' == c )
169  {
170  result += ' ';
171  --chars_to_handle;
172  ++d;
173  }
174  else if( '%' != c )
175  {
176  throw exception_t{
177  fmt::format(
178  "invalid non-escaped char with code {:#02X} at pos: {}",
179  c,
180  d - data.data() ) };
181  }
182  else if( chars_to_handle >= 3 &&
183  impl::is_hexdigit( d[ 1 ] ) &&
184  impl::is_hexdigit( d[ 2 ] ) )
185  {
186  result += impl::extract_escaped_char( d[ 1 ], d[ 2 ] );
187  chars_to_handle -= 3;
188  d += 3;
189  }
190  else
191  {
192  throw exception_t{
193  fmt::format(
194  "invalid escape sequence at pos {}", d - data.data() ) };
195  }
196  }
197  return result;
198 }
199 
200 template< typename Traits = restinio_default_unescape_traits >
201 std::size_t
203 {
206  const char * d = data;
207  char * dest = data;
208 
209  while( 0 < chars_to_handle )
210  {
211  char c = *d;
212  if( Traits::ordinary_char( c ) )
213  {
214  // Skip.
215  *dest++ = c;
216  --chars_to_handle;
217  ++d;
218  }
219  else if( '+' == c )
220  {
221  // Replace with space.
222  *dest++ = ' ';
223  --chars_to_handle;
224  ++d;
225  }
226  else if( '%' != c )
227  {
228  throw exception_t{
229  fmt::format(
230  "invalid non-escaped char with code {:#02X} at pos: {}",
231  c,
232  d - data ) };
233  }
234  else if( chars_to_handle >= 3 &&
235  impl::is_hexdigit( d[ 1 ] ) &&
236  impl::is_hexdigit( d[ 2 ] ) )
237  {
238  *dest++ = impl::extract_escaped_char( d[ 1 ], d[ 2 ] );
239  chars_to_handle -= 3;
240  d += 3;
241  result_size -= 2; // 3 chars => 1 char.
242  }
243  else
244  {
245  throw exception_t{
246  fmt::format(
247  "invalid escape sequence at pos {}", d - data ) };
248  }
249  }
250 
251  return result_size;
252 }
253 
254 //! \}
255 
256 } /* namespace utils */
257 
258 } /* namespace restinio */
static constexpr bool ordinary_char(char c) noexcept
string_view_t from_string< string_view_t >(string_view_t s)
Get a value from string_view.
std::size_t inplace_unescape_percent_encoding(char *data, std::size_t size)
The traits for escaping and unexcaping symbols in JavaScript-compatible mode.
The default traits for escaping and unexcaping symbols in a query string.
std::string unescape_percent_encoding(const string_view_t data)
char extract_escaped_char(char c1, char c2)
std::string escape_percent_encoding(const string_view_t data)
Percent encoding.
static constexpr bool ordinary_char(char c) noexcept
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64