Skip to content

Commit 87fd3fb

Browse files
author
Ruslan Nigmatullin
committed
CR
1 parent cfada35 commit 87fd3fb

File tree

2 files changed

+46
-45
lines changed

2 files changed

+46
-45
lines changed

src/include/grpc_transcoding/path_matcher.h

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ namespace transcoding {
3232
template <class Method>
3333
class 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,8 +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_;
85-
bool fully_decode_reserved_expansion_;
86-
bool always_decode_;
97+
UrlUnescapeSpec unescape_spec_;
8798

8899
private:
89100
friend class PathMatcherBuilder<Method>;
@@ -115,20 +126,9 @@ class PathMatcherBuilder {
115126
bool Register(const std::string& http_method, const std::string& path,
116127
const std::string& body_field_path, Method method);
117128

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-
124-
// When set to true, URL path parameters will be fully URI-decoded except in
125-
// cases of single segment matches in reserved expansion, where "%2F" will be
126-
// left encoded.
127-
//
128-
// The default behavior is to not decode RFC 6570 reserved characters in multi
129-
// segment matches.
130-
void SetFullyDecodeReservedExpansion(bool value) {
131-
fully_decode_reserved_expansion_ = value;
129+
// Change unescaping behavior, see UrlUnescapeSpec for available options.
130+
void SetUrlUnescapeSpec(UrlUnescapeSpec unescape_spec) {
131+
unescape_spec_ = unescape_spec;
132132
}
133133

134134
// Returns a unique_ptr to a thread safe PathMatcher that contains all
@@ -151,8 +151,7 @@ class PathMatcherBuilder {
151151
std::unordered_set<std::string> custom_verbs_;
152152
typedef typename PathMatcher<Method>::MethodData MethodData;
153153
std::vector<std::unique_ptr<MethodData>> methods_;
154-
bool always_decode_;
155-
bool fully_decode_reserved_expansion_;
154+
UrlUnescapeSpec unescape_spec_;
156155

157156
friend class PathMatcher<Method>;
158157
};
@@ -421,9 +420,7 @@ PathMatcher<Method>::PathMatcher(PathMatcherBuilder<Method>&& builder)
421420
: root_ptr_(std::move(builder.root_ptr_)),
422421
custom_verbs_(std::move(builder.custom_verbs_)),
423422
methods_(std::move(builder.methods_)),
424-
always_decode_(builder.always_decode_),
425-
fully_decode_reserved_expansion_(
426-
builder.fully_decode_reserved_expansion_) {}
423+
unescape_spec_(builder.unescape_spec_) {}
427424

428425
// Lookup is a wrapper method for the recursive node Lookup. First, the wrapper
429426
// splits the request path into slash-separated path parts. Next, the method
@@ -458,9 +455,11 @@ Method PathMatcher<Method>::Lookup(
458455
MethodData* method_data = reinterpret_cast<MethodData*>(lookup_result.data);
459456
if (variable_bindings != nullptr) {
460457
variable_bindings->clear();
461-
ExtractBindingsFromPath(method_data->variables, parts, variable_bindings,
462-
fully_decode_reserved_expansion_ || always_decode_,
463-
always_decode_);
458+
ExtractBindingsFromPath(
459+
method_data->variables, parts, variable_bindings,
460+
unescape_spec_ == UrlUnescapeSpec::kAllCharacters ||
461+
unescape_spec_ == UrlUnescapeSpec::kAllCharactersExceptSlash,
462+
unescape_spec_ == UrlUnescapeSpec::kAllCharacters);
464463
ExtractBindingsFromQueryParameters(
465464
query_params, method_data->system_query_parameter_names,
466465
variable_bindings);
@@ -498,8 +497,7 @@ Method PathMatcher<Method>::Lookup(const std::string& http_method,
498497
template <class Method>
499498
PathMatcherBuilder<Method>::PathMatcherBuilder()
500499
: root_ptr_(new PathMatcherNode()),
501-
always_decode_(false),
502-
fully_decode_reserved_expansion_(false) {}
500+
unescape_spec_(UrlUnescapeSpec::kAllCharactersExceptReserved) {}
503501

504502
template <class Method>
505503
PathMatcherPtr<Method> PathMatcherBuilder<Method>::Build() {

test/path_matcher_test.cc

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,10 @@ class PathMatcherTest : public ::testing::Test {
116116

117117
MethodInfo* AddGetPath(std::string path) { return AddPath("GET", path); }
118118

119-
void SetFullyDecodeReservedExpansion(bool value) {
120-
builder_.SetFullyDecodeReservedExpansion(value);
119+
void SetUrlUnescapeSpec(UrlUnescapeSpec unescape_spec) {
120+
builder_.SetUrlUnescapeSpec(unescape_spec);
121121
}
122122

123-
void SetAlwaysDecode(bool value) { builder_.SetAlwaysDecode(value); }
124-
125123
void Build() { matcher_ = builder_.Build(); }
126124

127125
MethodInfo* LookupWithBodyFieldPath(std::string method, std::string path,
@@ -383,31 +381,36 @@ TEST_F(PathMatcherTest, PercentEscapesNotUnescapedForMultiSegment2) {
383381
bindings);
384382
}
385383

386-
TEST_F(PathMatcherTest, OnlyUnreservedCharsAreUnescapedForMultiSegmentMatch) {
384+
TEST_F(
385+
PathMatcherTest,
386+
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchUnescapeAllExceptReservedImplicit) {
387387
// All %XX are reserved characters, they should be intact.
388388
MultiSegmentMatchWithReservedCharactersBase(
389389
"%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
390390
}
391391

392-
TEST_F(PathMatcherTest,
393-
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchAlwaysDecode) {
394-
SetFullyDecodeReservedExpansion(true);
395-
// All %XX are reserved characters, they should be decoded.
396-
MultiSegmentMatchWithReservedCharactersBase("!#$&'()*+,%2F:;=?@[]");
392+
TEST_F(
393+
PathMatcherTest,
394+
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchUnescapeAllExceptReservedExplicit) {
395+
SetUrlUnescapeSpec(UrlUnescapeSpec::kAllCharactersExceptReserved);
396+
// Set default value explicitly.
397+
MultiSegmentMatchWithReservedCharactersBase(
398+
"%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
397399
}
398400

399-
TEST_F(PathMatcherTest,
400-
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchFullyDecode) {
401-
SetAlwaysDecode(true);
402-
// All %XX are reserved characters, they should be decoded.
403-
MultiSegmentMatchWithReservedCharactersBase("!#$&'()*+,/:;=?@[]");
401+
TEST_F(
402+
PathMatcherTest,
403+
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchUnescapeAllExceptSlash) {
404+
SetUrlUnescapeSpec(UrlUnescapeSpec::kAllCharactersExceptSlash);
405+
// All %XX are reserved characters, all of them should be decoded except
406+
// slash.
407+
MultiSegmentMatchWithReservedCharactersBase("!#$&'()*+,%2F:;=?@[]");
404408
}
405409

406410
TEST_F(PathMatcherTest,
407-
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchAlwaysAndFully) {
408-
SetAlwaysDecode(true);
409-
SetFullyDecodeReservedExpansion(true);
410-
// AlwaysDecode wins.
411+
OnlyUnreservedCharsAreUnescapedForMultiSegmentMatchUnescapeAll) {
412+
SetUrlUnescapeSpec(UrlUnescapeSpec::kAllCharacters);
413+
// All %XX are reserved characters, they should be decoded.
411414
MultiSegmentMatchWithReservedCharactersBase("!#$&'()*+,/:;=?@[]");
412415
}
413416

0 commit comments

Comments
 (0)