@@ -94,7 +94,8 @@ class PathMatcher {
94
94
// The info associated with each method. The path matcher nodes
95
95
// will hold pointers to MethodData objects in this vector.
96
96
std::vector<std::unique_ptr<MethodData>> methods_;
97
- UrlUnescapeSpec unescape_spec_;
97
+ UrlUnescapeSpec path_unescape_spec_;
98
+ bool query_param_unescape_plus_;
98
99
99
100
private:
100
101
friend class PathMatcherBuilder <Method>;
@@ -127,8 +128,14 @@ class PathMatcherBuilder {
127
128
const std::string& body_field_path, Method method);
128
129
129
130
// Change unescaping behavior, see UrlUnescapeSpec for available options.
130
- void SetUrlUnescapeSpec (UrlUnescapeSpec unescape_spec) {
131
- unescape_spec_ = unescape_spec;
131
+ // This only applies to path, not query parameters.
132
+ void SetUrlUnescapeSpec (UrlUnescapeSpec path_unescape_spec) {
133
+ path_unescape_spec_ = path_unescape_spec;
134
+ }
135
+ // If true, unescape '+' in query parameters to space. Default is false.
136
+ // To support [HTML 2.0 or RFC1866](https://tools.ietf.org/html/rfc1866#section-8.2.1).
137
+ void SetQueryParamUnescapePlus (bool query_param_unescape_plus) {
138
+ query_param_unescape_plus_ = query_param_unescape_plus;
132
139
}
133
140
134
141
// Returns a unique_ptr to a thread safe PathMatcher that contains all
@@ -151,8 +158,9 @@ class PathMatcherBuilder {
151
158
std::unordered_set<std::string> custom_verbs_;
152
159
typedef typename PathMatcher<Method>::MethodData MethodData;
153
160
std::vector<std::unique_ptr<MethodData>> methods_;
154
- UrlUnescapeSpec unescape_spec_ =
161
+ UrlUnescapeSpec path_unescape_spec_ =
155
162
UrlUnescapeSpec::kAllCharactersExceptReserved ;
163
+ bool query_param_unescape_plus_ = false ;
156
164
157
165
friend class PathMatcher <Method>;
158
166
};
@@ -222,44 +230,53 @@ inline int hex_digit_to_int(char c) {
222
230
//
223
231
// If the next three characters are an escaped character then this function will
224
232
// also return what character is escaped.
225
- bool GetEscapedChar (const std::string& src, size_t i,
226
- UrlUnescapeSpec unescape_spec, char * out) {
233
+ //
234
+ // If unescape_plus is true, unescape '+' to space.
235
+ //
236
+ // return value: 0: not unescaped, >0: unescaped, number of used original characters.
237
+ //
238
+ int GetEscapedChar (const std::string& src, size_t i,
239
+ UrlUnescapeSpec unescape_spec, bool unescape_plus, char * out) {
240
+ if (unescape_plus && src[i] == ' +' ) {
241
+ *out = ' ' ;
242
+ return 1 ;
243
+ }
227
244
if (i + 2 < src.size () && src[i] == ' %' ) {
228
245
if (ascii_isxdigit (src[i + 1 ]) && ascii_isxdigit (src[i + 2 ])) {
229
246
char c =
230
247
(hex_digit_to_int (src[i + 1 ]) << 4 ) | hex_digit_to_int (src[i + 2 ]);
231
248
switch (unescape_spec) {
232
249
case UrlUnescapeSpec::kAllCharactersExceptReserved :
233
250
if (IsReservedChar (c)) {
234
- return false ;
251
+ return 0 ;
235
252
}
236
253
break ;
237
254
case UrlUnescapeSpec::kAllCharactersExceptSlash :
238
255
if (c == ' /' ) {
239
- return false ;
256
+ return 0 ;
240
257
}
241
258
break ;
242
259
case UrlUnescapeSpec::kAllCharacters :
243
260
break ;
244
261
}
245
262
*out = c;
246
- return true ;
263
+ return 3 ;
247
264
}
248
265
}
249
- return false ;
266
+ return 0 ;
250
267
}
251
268
252
269
// Unescapes string 'part' and returns the unescaped string. Reserved characters
253
270
// (as specified in RFC 6570) are not escaped if unescape_reserved_chars is
254
271
// false.
255
272
std::string UrlUnescapeString (const std::string& part,
256
- UrlUnescapeSpec unescape_spec) {
273
+ UrlUnescapeSpec unescape_spec, bool unescape_plus ) {
257
274
std::string unescaped;
258
275
// Check whether we need to escape at all.
259
276
bool needs_unescaping = false ;
260
277
char ch = ' \0 ' ;
261
278
for (size_t i = 0 ; i < part.size (); ++i) {
262
- if (GetEscapedChar (part, i, unescape_spec, &ch)) {
279
+ if (GetEscapedChar (part, i, unescape_spec, unescape_plus, &ch) > 0 ) {
263
280
needs_unescaping = true ;
264
281
break ;
265
282
}
@@ -275,9 +292,10 @@ std::string UrlUnescapeString(const std::string& part,
275
292
char * p = begin;
276
293
277
294
for (size_t i = 0 ; i < part.size ();) {
278
- if (GetEscapedChar (part, i, unescape_spec, &ch)) {
295
+ int skip = GetEscapedChar (part, i, unescape_spec, unescape_plus, &ch);
296
+ if (skip > 0 ) {
279
297
*p++ = ch;
280
- i += 3 ;
298
+ i += skip ;
281
299
} else {
282
300
*p++ = part[i];
283
301
i += 1 ;
@@ -291,8 +309,8 @@ std::string UrlUnescapeString(const std::string& part,
291
309
template <class VariableBinding >
292
310
void ExtractBindingsFromPath (const std::vector<HttpTemplate::Variable>& vars,
293
311
const std::vector<std::string>& parts,
294
- std::vector<VariableBinding>* bindings ,
295
- UrlUnescapeSpec unescape_spec ) {
312
+ UrlUnescapeSpec unescape_spec ,
313
+ std::vector<VariableBinding>* bindings ) {
296
314
for (const auto & var : vars) {
297
315
// Determine the subpath bound to the variable based on the
298
316
// [start_segment, end_segment) segment range of the variable.
@@ -316,7 +334,7 @@ void ExtractBindingsFromPath(const std::vector<HttpTemplate::Variable>& vars,
316
334
// Joins parts with "/" to form a path string.
317
335
for (size_t i = var.start_segment ; i < end_segment; ++i) {
318
336
// For multipart matches only unescape non-reserved characters.
319
- binding.value += UrlUnescapeString (parts[i], var_unescape_spec);
337
+ binding.value += UrlUnescapeString (parts[i], var_unescape_spec, false );
320
338
if (i < end_segment - 1 ) {
321
339
binding.value += " /" ;
322
340
}
@@ -329,6 +347,7 @@ template <class VariableBinding>
329
347
void ExtractBindingsFromQueryParameters (
330
348
const std::string& query_params,
331
349
const std::unordered_set<std::string>& system_params,
350
+ bool query_param_unescape_plus,
332
351
std::vector<VariableBinding>* bindings) {
333
352
// The bindings in URL the query parameters have the following form:
334
353
// <field_path1>=value1&<field_path2>=value2&...&<field_pathN>=valueN
@@ -350,7 +369,8 @@ void ExtractBindingsFromQueryParameters(
350
369
VariableBinding binding;
351
370
split (name, ' .' , binding.field_path );
352
371
binding.value = UrlUnescapeString (param.substr (pos + 1 ),
353
- UrlUnescapeSpec::kAllCharacters );
372
+ UrlUnescapeSpec::kAllCharacters ,
373
+ query_param_unescape_plus);
354
374
bindings->emplace_back (std::move (binding));
355
375
}
356
376
}
@@ -424,7 +444,8 @@ PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
424
444
: root_ptr_(std::move(builder.root_ptr_)),
425
445
custom_verbs_ (std::move(builder.custom_verbs_)),
426
446
methods_(std::move(builder.methods_)),
427
- unescape_spec_(builder.unescape_spec_) {}
447
+ path_unescape_spec_(builder.path_unescape_spec_),
448
+ query_param_unescape_plus_(builder.query_param_unescape_plus_) {}
428
449
429
450
// Lookup is a wrapper method for the recursive node Lookup. First, the wrapper
430
451
// splits the request path into slash-separated path parts. Next, the method
@@ -460,11 +481,11 @@ Method PathMatcher<Method>::Lookup(
460
481
MethodData* method_data = reinterpret_cast <MethodData*>(lookup_result.data );
461
482
if (variable_bindings != nullptr ) {
462
483
variable_bindings->clear ();
463
- ExtractBindingsFromPath (method_data->variables , parts, variable_bindings,
464
- unescape_spec_ );
484
+ ExtractBindingsFromPath (method_data->variables , parts,
485
+ path_unescape_spec_, variable_bindings );
465
486
ExtractBindingsFromQueryParameters (
466
487
query_params, method_data->system_query_parameter_names ,
467
- variable_bindings);
488
+ query_param_unescape_plus_, variable_bindings);
468
489
}
469
490
if (body_field_path != nullptr ) {
470
491
*body_field_path = method_data->body_field_path ;
0 commit comments