@@ -83,6 +83,7 @@ class PathMatcher {
83
83
// will hold pointers to MethodData objects in this vector.
84
84
std::vector<std::unique_ptr<MethodData>> methods_;
85
85
bool fully_decode_reserved_expansion_;
86
+ bool always_decode_;
86
87
87
88
private:
88
89
friend class PathMatcherBuilder <Method>;
@@ -114,6 +115,12 @@ class PathMatcherBuilder {
114
115
bool Register (const std::string& http_method, const std::string& path,
115
116
const std::string& body_field_path, Method method);
116
117
118
+ // When set to true, URL path parameters will be fully URI-decoded.
119
+ //
120
+ // The default behavior is to not decode RFC 6570 reserved characters in multi
121
+ // segment matches.
122
+ void SetAlwaysDecode (bool value) { always_decode_ = value; }
123
+
117
124
// When set to true, URL path parameters will be fully URI-decoded except in
118
125
// cases of single segment matches in reserved expansion, where "%2F" will be
119
126
// left encoded.
@@ -144,6 +151,7 @@ class PathMatcherBuilder {
144
151
std::unordered_set<std::string> custom_verbs_;
145
152
typedef typename PathMatcher<Method>::MethodData MethodData;
146
153
std::vector<std::unique_ptr<MethodData>> methods_;
154
+ bool always_decode_;
147
155
bool fully_decode_reserved_expansion_;
148
156
149
157
friend class PathMatcher <Method>;
@@ -215,12 +223,16 @@ inline int hex_digit_to_int(char c) {
215
223
// If the next three characters are an escaped character then this function will
216
224
// also return what character is escaped.
217
225
bool GetEscapedChar (const std::string& src, size_t i,
218
- bool unescape_reserved_chars, char * out) {
226
+ bool unescape_reserved_chars, bool unescape_slash_char,
227
+ char * out) {
219
228
if (i + 2 < src.size () && src[i] == ' %' ) {
220
229
if (ascii_isxdigit (src[i + 1 ]) && ascii_isxdigit (src[i + 2 ])) {
221
230
char c =
222
231
(hex_digit_to_int (src[i + 1 ]) << 4 ) | hex_digit_to_int (src[i + 2 ]);
223
- if (!unescape_reserved_chars && IsReservedChar (c)) {
232
+ if (!unescape_slash_char && c == ' /' ) {
233
+ return false ;
234
+ }
235
+ if (!unescape_reserved_chars && c != ' /' && IsReservedChar (c)) {
224
236
return false ;
225
237
}
226
238
*out = c;
@@ -234,13 +246,15 @@ bool GetEscapedChar(const std::string& src, size_t i,
234
246
// (as specified in RFC 6570) are not escaped if unescape_reserved_chars is
235
247
// false.
236
248
std::string UrlUnescapeString (const std::string& part,
237
- bool unescape_reserved_chars) {
249
+ bool unescape_reserved_chars,
250
+ bool unescape_slash_char) {
238
251
std::string unescaped;
239
252
// Check whether we need to escape at all.
240
253
bool needs_unescaping = false ;
241
254
char ch = ' \0 ' ;
242
255
for (size_t i = 0 ; i < part.size (); ++i) {
243
- if (GetEscapedChar (part, i, unescape_reserved_chars, &ch)) {
256
+ if (GetEscapedChar (part, i, unescape_reserved_chars, unescape_slash_char,
257
+ &ch)) {
244
258
needs_unescaping = true ;
245
259
break ;
246
260
}
@@ -256,7 +270,8 @@ std::string UrlUnescapeString(const std::string& part,
256
270
char * p = begin;
257
271
258
272
for (size_t i = 0 ; i < part.size ();) {
259
- if (GetEscapedChar (part, i, unescape_reserved_chars, &ch)) {
273
+ if (GetEscapedChar (part, i, unescape_reserved_chars, unescape_slash_char,
274
+ &ch)) {
260
275
*p++ = ch;
261
276
i += 3 ;
262
277
} else {
@@ -273,7 +288,8 @@ template <class VariableBinding>
273
288
void ExtractBindingsFromPath (const std::vector<HttpTemplate::Variable>& vars,
274
289
const std::vector<std::string>& parts,
275
290
std::vector<VariableBinding>* bindings,
276
- const bool fully_decode_reserved_expansion) {
291
+ bool fully_decode_reserved_expansion,
292
+ bool decode_slash_character) {
277
293
for (const auto & var : vars) {
278
294
// Determine the subpath bound to the variable based on the
279
295
// [start_segment, end_segment) segment range of the variable.
@@ -295,7 +311,8 @@ void ExtractBindingsFromPath(const std::vector<HttpTemplate::Variable>& vars,
295
311
for (size_t i = var.start_segment ; i < end_segment; ++i) {
296
312
// For multipart matches only unescape non-reserved characters.
297
313
binding.value += UrlUnescapeString (
298
- parts[i], fully_decode_reserved_expansion || !is_multipart);
314
+ parts[i], fully_decode_reserved_expansion || !is_multipart,
315
+ decode_slash_character || !is_multipart);
299
316
if (i < end_segment - 1 ) {
300
317
binding.value += " /" ;
301
318
}
@@ -328,7 +345,7 @@ void ExtractBindingsFromQueryParameters(
328
345
// in the request, e.g. `book.author.name`.
329
346
VariableBinding binding;
330
347
split (name, ' .' , binding.field_path );
331
- binding.value = UrlUnescapeString (param.substr (pos + 1 ), true );
348
+ binding.value = UrlUnescapeString (param.substr (pos + 1 ), true , true );
332
349
bindings->emplace_back (std::move (binding));
333
350
}
334
351
}
@@ -404,6 +421,7 @@ PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
404
421
: root_ptr_(std::move(builder.root_ptr_)),
405
422
custom_verbs_ (std::move(builder.custom_verbs_)),
406
423
methods_(std::move(builder.methods_)),
424
+ always_decode_(builder.always_decode_),
407
425
fully_decode_reserved_expansion_(
408
426
builder.fully_decode_reserved_expansion_) {}
409
427
@@ -441,7 +459,8 @@ Method PathMatcher<Method>::Lookup(
441
459
if (variable_bindings != nullptr ) {
442
460
variable_bindings->clear ();
443
461
ExtractBindingsFromPath (method_data->variables , parts, variable_bindings,
444
- fully_decode_reserved_expansion_);
462
+ fully_decode_reserved_expansion_ || always_decode_,
463
+ always_decode_);
445
464
ExtractBindingsFromQueryParameters (
446
465
query_params, method_data->system_query_parameter_names ,
447
466
variable_bindings);
@@ -479,6 +498,7 @@ Method PathMatcher<Method>::Lookup(const std::string& http_method,
479
498
template <class Method >
480
499
PathMatcherBuilder<Method>::PathMatcherBuilder()
481
500
: root_ptr_(new PathMatcherNode()),
501
+ always_decode_ (false ),
482
502
fully_decode_reserved_expansion_(false ) {}
483
503
484
504
template <class Method >
0 commit comments