@@ -32,6 +32,18 @@ namespace transcoding {
32
32
template <class Method >
33
33
class PathMatcherBuilder ; // required for PathMatcher constructor
34
34
35
+ enum class UrlUnescapeSpec {
36
+ // URL path parameters will not decode RFC 6570 reserved characters.
37
+ // This is the default behavior.
38
+ kAllCharactersExceptReserved = 0 ,
39
+ // URL path parameters will be fully URI-decoded except in
40
+ // cases of single segment matches in reserved expansion, where "%2F" will be
41
+ // left encoded.
42
+ kAllCharactersExceptSlash ,
43
+ // URL path parameters will be fully URI-decoded.
44
+ kAllCharacters ,
45
+ };
46
+
35
47
// The immutable, thread safe PathMatcher stores a mapping from a combination of
36
48
// a service (host) name and a HTTP path to your method (MethodInfo*). It is
37
49
// constructed with a PathMatcherBuilder and supports one operation: Lookup.
@@ -82,6 +94,7 @@ class PathMatcher {
82
94
// The info associated with each method. The path matcher nodes
83
95
// will hold pointers to MethodData objects in this vector.
84
96
std::vector<std::unique_ptr<MethodData>> methods_;
97
+ UrlUnescapeSpec unescape_spec_;
85
98
86
99
private:
87
100
friend class PathMatcherBuilder <Method>;
@@ -113,6 +126,11 @@ class PathMatcherBuilder {
113
126
bool Register (const std::string& http_method, const std::string& path,
114
127
const std::string& body_field_path, Method method);
115
128
129
+ // Change unescaping behavior, see UrlUnescapeSpec for available options.
130
+ void SetUrlUnescapeSpec (UrlUnescapeSpec unescape_spec) {
131
+ unescape_spec_ = unescape_spec;
132
+ }
133
+
116
134
// Returns a unique_ptr to a thread safe PathMatcher that contains all
117
135
// registered path-WrapperGraph pairs. Note the PathMatchBuilder instance
118
136
// will be moved so cannot use after invoking Build().
@@ -133,6 +151,8 @@ class PathMatcherBuilder {
133
151
std::unordered_set<std::string> custom_verbs_;
134
152
typedef typename PathMatcher<Method>::MethodData MethodData;
135
153
std::vector<std::unique_ptr<MethodData>> methods_;
154
+ UrlUnescapeSpec unescape_spec_ =
155
+ UrlUnescapeSpec::kAllCharactersExceptReserved ;
136
156
137
157
friend class PathMatcher <Method>;
138
158
};
@@ -203,13 +223,24 @@ inline int hex_digit_to_int(char c) {
203
223
// If the next three characters are an escaped character then this function will
204
224
// also return what character is escaped.
205
225
bool GetEscapedChar (const std::string& src, size_t i,
206
- bool unescape_reserved_chars , char * out) {
226
+ UrlUnescapeSpec unescape_spec , char * out) {
207
227
if (i + 2 < src.size () && src[i] == ' %' ) {
208
228
if (ascii_isxdigit (src[i + 1 ]) && ascii_isxdigit (src[i + 2 ])) {
209
229
char c =
210
230
(hex_digit_to_int (src[i + 1 ]) << 4 ) | hex_digit_to_int (src[i + 2 ]);
211
- if (!unescape_reserved_chars && IsReservedChar (c)) {
212
- return false ;
231
+ switch (unescape_spec) {
232
+ case UrlUnescapeSpec::kAllCharactersExceptReserved :
233
+ if (IsReservedChar (c)) {
234
+ return false ;
235
+ }
236
+ break ;
237
+ case UrlUnescapeSpec::kAllCharactersExceptSlash :
238
+ if (c == ' /' ) {
239
+ return false ;
240
+ }
241
+ break ;
242
+ case UrlUnescapeSpec::kAllCharacters :
243
+ break ;
213
244
}
214
245
*out = c;
215
246
return true ;
@@ -222,13 +253,13 @@ bool GetEscapedChar(const std::string& src, size_t i,
222
253
// (as specified in RFC 6570) are not escaped if unescape_reserved_chars is
223
254
// false.
224
255
std::string UrlUnescapeString (const std::string& part,
225
- bool unescape_reserved_chars ) {
256
+ UrlUnescapeSpec unescape_spec ) {
226
257
std::string unescaped;
227
258
// Check whether we need to escape at all.
228
259
bool needs_unescaping = false ;
229
260
char ch = ' \0 ' ;
230
261
for (size_t i = 0 ; i < part.size (); ++i) {
231
- if (GetEscapedChar (part, i, unescape_reserved_chars , &ch)) {
262
+ if (GetEscapedChar (part, i, unescape_spec , &ch)) {
232
263
needs_unescaping = true ;
233
264
break ;
234
265
}
@@ -244,7 +275,7 @@ std::string UrlUnescapeString(const std::string& part,
244
275
char * p = begin;
245
276
246
277
for (size_t i = 0 ; i < part.size ();) {
247
- if (GetEscapedChar (part, i, unescape_reserved_chars , &ch)) {
278
+ if (GetEscapedChar (part, i, unescape_spec , &ch)) {
248
279
*p++ = ch;
249
280
i += 3 ;
250
281
} else {
@@ -260,7 +291,8 @@ std::string UrlUnescapeString(const std::string& part,
260
291
template <class VariableBinding >
261
292
void ExtractBindingsFromPath (const std::vector<HttpTemplate::Variable>& vars,
262
293
const std::vector<std::string>& parts,
263
- std::vector<VariableBinding>* bindings) {
294
+ std::vector<VariableBinding>* bindings,
295
+ UrlUnescapeSpec unescape_spec) {
264
296
for (const auto & var : vars) {
265
297
// Determine the subpath bound to the variable based on the
266
298
// [start_segment, end_segment) segment range of the variable.
@@ -278,10 +310,13 @@ void ExtractBindingsFromPath(const std::vector<HttpTemplate::Variable>& vars,
278
310
// multi-part match by checking if it->second.end_segment is negative.
279
311
bool is_multipart =
280
312
(end_segment - var.start_segment ) > 1 || var.end_segment < 0 ;
313
+ const UrlUnescapeSpec var_unescape_spec =
314
+ is_multipart ? unescape_spec : UrlUnescapeSpec::kAllCharacters ;
315
+
281
316
// Joins parts with "/" to form a path string.
282
317
for (size_t i = var.start_segment ; i < end_segment; ++i) {
283
318
// For multipart matches only unescape non-reserved characters.
284
- binding.value += UrlUnescapeString (parts[i], !is_multipart );
319
+ binding.value += UrlUnescapeString (parts[i], var_unescape_spec );
285
320
if (i < end_segment - 1 ) {
286
321
binding.value += " /" ;
287
322
}
@@ -314,7 +349,8 @@ void ExtractBindingsFromQueryParameters(
314
349
// in the request, e.g. `book.author.name`.
315
350
VariableBinding binding;
316
351
split (name, ' .' , binding.field_path );
317
- binding.value = UrlUnescapeString (param.substr (pos + 1 ), true );
352
+ binding.value = UrlUnescapeString (param.substr (pos + 1 ),
353
+ UrlUnescapeSpec::kAllCharacters );
318
354
bindings->emplace_back (std::move (binding));
319
355
}
320
356
}
@@ -389,7 +425,8 @@ template <class Method>
389
425
PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
390
426
: root_ptr_(std::move(builder.root_ptr_)),
391
427
custom_verbs_ (std::move(builder.custom_verbs_)),
392
- methods_(std::move(builder.methods_)) {}
428
+ methods_(std::move(builder.methods_)),
429
+ unescape_spec_(builder.unescape_spec_) {}
393
430
394
431
// Lookup is a wrapper method for the recursive node Lookup. First, the wrapper
395
432
// splits the request path into slash-separated path parts. Next, the method
@@ -424,7 +461,8 @@ Method PathMatcher<Method>::Lookup(
424
461
MethodData* method_data = reinterpret_cast <MethodData*>(lookup_result.data );
425
462
if (variable_bindings != nullptr ) {
426
463
variable_bindings->clear ();
427
- ExtractBindingsFromPath (method_data->variables , parts, variable_bindings);
464
+ ExtractBindingsFromPath (method_data->variables , parts, variable_bindings,
465
+ unescape_spec_);
428
466
ExtractBindingsFromQueryParameters (
429
467
query_params, method_data->system_query_parameter_names ,
430
468
variable_bindings);
0 commit comments