@@ -32,6 +32,18 @@ namespace transcoding {
3232template <class Method >
3333class PathMatcherBuilder ; // required for PathMatcher constructor
3434
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+
3547// The immutable, thread safe PathMatcher stores a mapping from a combination of
3648// a service (host) name and a HTTP path to your method (MethodInfo*). It is
3749// constructed with a PathMatcherBuilder and supports one operation: Lookup.
@@ -82,6 +94,7 @@ class PathMatcher {
8294 // The info associated with each method. The path matcher nodes
8395 // will hold pointers to MethodData objects in this vector.
8496 std::vector<std::unique_ptr<MethodData>> methods_;
97+ UrlUnescapeSpec unescape_spec_;
8598
8699 private:
87100 friend class PathMatcherBuilder <Method>;
@@ -113,6 +126,11 @@ class PathMatcherBuilder {
113126 bool Register (const std::string& http_method, const std::string& path,
114127 const std::string& body_field_path, Method method);
115128
129+ // Change unescaping behavior, see UrlUnescapeSpec for available options.
130+ void SetUrlUnescapeSpec (UrlUnescapeSpec unescape_spec) {
131+ unescape_spec_ = unescape_spec;
132+ }
133+
116134 // Returns a unique_ptr to a thread safe PathMatcher that contains all
117135 // registered path-WrapperGraph pairs. Note the PathMatchBuilder instance
118136 // will be moved so cannot use after invoking Build().
@@ -133,6 +151,8 @@ class PathMatcherBuilder {
133151 std::unordered_set<std::string> custom_verbs_;
134152 typedef typename PathMatcher<Method>::MethodData MethodData;
135153 std::vector<std::unique_ptr<MethodData>> methods_;
154+ UrlUnescapeSpec unescape_spec_ =
155+ UrlUnescapeSpec::kAllCharactersExceptReserved ;
136156
137157 friend class PathMatcher <Method>;
138158};
@@ -203,13 +223,24 @@ inline int hex_digit_to_int(char c) {
203223// If the next three characters are an escaped character then this function will
204224// also return what character is escaped.
205225bool GetEscapedChar (const std::string& src, size_t i,
206- bool unescape_reserved_chars , char * out) {
226+ UrlUnescapeSpec unescape_spec , char * out) {
207227 if (i + 2 < src.size () && src[i] == ' %' ) {
208228 if (ascii_isxdigit (src[i + 1 ]) && ascii_isxdigit (src[i + 2 ])) {
209229 char c =
210230 (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 ;
213244 }
214245 *out = c;
215246 return true ;
@@ -222,13 +253,13 @@ bool GetEscapedChar(const std::string& src, size_t i,
222253// (as specified in RFC 6570) are not escaped if unescape_reserved_chars is
223254// false.
224255std::string UrlUnescapeString (const std::string& part,
225- bool unescape_reserved_chars ) {
256+ UrlUnescapeSpec unescape_spec ) {
226257 std::string unescaped;
227258 // Check whether we need to escape at all.
228259 bool needs_unescaping = false ;
229260 char ch = ' \0 ' ;
230261 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)) {
232263 needs_unescaping = true ;
233264 break ;
234265 }
@@ -244,7 +275,7 @@ std::string UrlUnescapeString(const std::string& part,
244275 char * p = begin;
245276
246277 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)) {
248279 *p++ = ch;
249280 i += 3 ;
250281 } else {
@@ -260,7 +291,8 @@ std::string UrlUnescapeString(const std::string& part,
260291template <class VariableBinding >
261292void ExtractBindingsFromPath (const std::vector<HttpTemplate::Variable>& vars,
262293 const std::vector<std::string>& parts,
263- std::vector<VariableBinding>* bindings) {
294+ std::vector<VariableBinding>* bindings,
295+ UrlUnescapeSpec unescape_spec) {
264296 for (const auto & var : vars) {
265297 // Determine the subpath bound to the variable based on the
266298 // [start_segment, end_segment) segment range of the variable.
@@ -278,10 +310,13 @@ void ExtractBindingsFromPath(const std::vector<HttpTemplate::Variable>& vars,
278310 // multi-part match by checking if it->second.end_segment is negative.
279311 bool is_multipart =
280312 (end_segment - var.start_segment ) > 1 || var.end_segment < 0 ;
313+ const UrlUnescapeSpec var_unescape_spec =
314+ is_multipart ? unescape_spec : UrlUnescapeSpec::kAllCharacters ;
315+
281316 // Joins parts with "/" to form a path string.
282317 for (size_t i = var.start_segment ; i < end_segment; ++i) {
283318 // 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 );
285320 if (i < end_segment - 1 ) {
286321 binding.value += " /" ;
287322 }
@@ -314,7 +349,8 @@ void ExtractBindingsFromQueryParameters(
314349 // in the request, e.g. `book.author.name`.
315350 VariableBinding binding;
316351 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 );
318354 bindings->emplace_back (std::move (binding));
319355 }
320356 }
@@ -389,7 +425,8 @@ template <class Method>
389425PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
390426 : root_ptr_(std::move(builder.root_ptr_)),
391427 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_) {}
393430
394431// Lookup is a wrapper method for the recursive node Lookup. First, the wrapper
395432// splits the request path into slash-separated path parts. Next, the method
@@ -424,7 +461,8 @@ Method PathMatcher<Method>::Lookup(
424461 MethodData* method_data = reinterpret_cast <MethodData*>(lookup_result.data );
425462 if (variable_bindings != nullptr ) {
426463 variable_bindings->clear ();
427- ExtractBindingsFromPath (method_data->variables , parts, variable_bindings);
464+ ExtractBindingsFromPath (method_data->variables , parts, variable_bindings,
465+ unescape_spec_);
428466 ExtractBindingsFromQueryParameters (
429467 query_params, method_data->system_query_parameter_names ,
430468 variable_bindings);
0 commit comments