@@ -83,6 +83,7 @@ class PathMatcher {
8383 // will hold pointers to MethodData objects in this vector.
8484 std::vector<std::unique_ptr<MethodData>> methods_;
8585 bool fully_decode_reserved_expansion_;
86+ bool always_decode_;
8687
8788 private:
8889 friend class PathMatcherBuilder <Method>;
@@ -114,6 +115,12 @@ class PathMatcherBuilder {
114115 bool Register (const std::string& http_method, const std::string& path,
115116 const std::string& body_field_path, Method method);
116117
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+
117124 // When set to true, URL path parameters will be fully URI-decoded except in
118125 // cases of single segment matches in reserved expansion, where "%2F" will be
119126 // left encoded.
@@ -144,6 +151,7 @@ class PathMatcherBuilder {
144151 std::unordered_set<std::string> custom_verbs_;
145152 typedef typename PathMatcher<Method>::MethodData MethodData;
146153 std::vector<std::unique_ptr<MethodData>> methods_;
154+ bool always_decode_;
147155 bool fully_decode_reserved_expansion_;
148156
149157 friend class PathMatcher <Method>;
@@ -215,12 +223,16 @@ inline int hex_digit_to_int(char c) {
215223// If the next three characters are an escaped character then this function will
216224// also return what character is escaped.
217225bool 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) {
219228 if (i + 2 < src.size () && src[i] == ' %' ) {
220229 if (ascii_isxdigit (src[i + 1 ]) && ascii_isxdigit (src[i + 2 ])) {
221230 char c =
222231 (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)) {
224236 return false ;
225237 }
226238 *out = c;
@@ -234,13 +246,15 @@ bool GetEscapedChar(const std::string& src, size_t i,
234246// (as specified in RFC 6570) are not escaped if unescape_reserved_chars is
235247// false.
236248std::string UrlUnescapeString (const std::string& part,
237- bool unescape_reserved_chars) {
249+ bool unescape_reserved_chars,
250+ bool unescape_slash_char) {
238251 std::string unescaped;
239252 // Check whether we need to escape at all.
240253 bool needs_unescaping = false ;
241254 char ch = ' \0 ' ;
242255 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)) {
244258 needs_unescaping = true ;
245259 break ;
246260 }
@@ -256,7 +270,8 @@ std::string UrlUnescapeString(const std::string& part,
256270 char * p = begin;
257271
258272 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)) {
260275 *p++ = ch;
261276 i += 3 ;
262277 } else {
@@ -273,7 +288,8 @@ template <class VariableBinding>
273288void ExtractBindingsFromPath (const std::vector<HttpTemplate::Variable>& vars,
274289 const std::vector<std::string>& parts,
275290 std::vector<VariableBinding>* bindings,
276- const bool fully_decode_reserved_expansion) {
291+ bool fully_decode_reserved_expansion,
292+ bool decode_slash_character) {
277293 for (const auto & var : vars) {
278294 // Determine the subpath bound to the variable based on the
279295 // [start_segment, end_segment) segment range of the variable.
@@ -295,7 +311,8 @@ void ExtractBindingsFromPath(const std::vector<HttpTemplate::Variable>& vars,
295311 for (size_t i = var.start_segment ; i < end_segment; ++i) {
296312 // For multipart matches only unescape non-reserved characters.
297313 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);
299316 if (i < end_segment - 1 ) {
300317 binding.value += " /" ;
301318 }
@@ -328,7 +345,7 @@ void ExtractBindingsFromQueryParameters(
328345 // in the request, e.g. `book.author.name`.
329346 VariableBinding binding;
330347 split (name, ' .' , binding.field_path );
331- binding.value = UrlUnescapeString (param.substr (pos + 1 ), true );
348+ binding.value = UrlUnescapeString (param.substr (pos + 1 ), true , true );
332349 bindings->emplace_back (std::move (binding));
333350 }
334351 }
@@ -404,6 +421,7 @@ PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
404421 : root_ptr_(std::move(builder.root_ptr_)),
405422 custom_verbs_ (std::move(builder.custom_verbs_)),
406423 methods_(std::move(builder.methods_)),
424+ always_decode_(builder.always_decode_),
407425 fully_decode_reserved_expansion_(
408426 builder.fully_decode_reserved_expansion_) {}
409427
@@ -441,7 +459,8 @@ Method PathMatcher<Method>::Lookup(
441459 if (variable_bindings != nullptr ) {
442460 variable_bindings->clear ();
443461 ExtractBindingsFromPath (method_data->variables , parts, variable_bindings,
444- fully_decode_reserved_expansion_);
462+ fully_decode_reserved_expansion_ || always_decode_,
463+ always_decode_);
445464 ExtractBindingsFromQueryParameters (
446465 query_params, method_data->system_query_parameter_names ,
447466 variable_bindings);
@@ -479,6 +498,7 @@ Method PathMatcher<Method>::Lookup(const std::string& http_method,
479498template <class Method >
480499PathMatcherBuilder<Method>::PathMatcherBuilder()
481500 : root_ptr_(new PathMatcherNode()),
501+ always_decode_ (false ),
482502 fully_decode_reserved_expansion_(false ) {}
483503
484504template <class Method >
0 commit comments