1 /*
2 * \copyright Copyright 2013 Google Inc. All Rights Reserved.
3 * \license @{
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * @}
18 */
19
20 // Implementation of RFC 6570 based on (open source implementation) at
21 // java/com/google/api/client/http/UriTemplate.java
22 // The URI Template spec is at http://tools.ietf.org/html/rfc6570
23 // Templates up to level 3 are supported.
24
25 #include "net/third_party/uri_template/uri_template.h"
26
27 #include <set>
28 #include <string>
29 #include <vector>
30
31 #include "base/strings/escape.h"
32 #include "base/strings/string_split.h"
33
34 using std::string;
35
36 namespace uri_template {
37
38 namespace {
39
40 // The UriTemplateConfig is used to represent variable sections and to construct
41 // the expanded url.
42 struct UriTemplateConfig {
43 public:
UriTemplateConfiguri_template::__anon6f82db490111::UriTemplateConfig44 UriTemplateConfig(const char* prefix,
45 const char* joiner,
46 bool requires_variable_assignment,
47 bool allow_reserved_expansion,
48 bool no_variable_assignment_if_empty = false)
49 : prefix_(prefix),
50 joiner_(joiner),
51 requires_variable_assignment_(requires_variable_assignment),
52 no_variable_assignment_if_empty_(no_variable_assignment_if_empty),
53 allow_reserved_expansion_(allow_reserved_expansion) {}
54
AppendValueuri_template::__anon6f82db490111::UriTemplateConfig55 void AppendValue(const string& variable,
56 const string& value,
57 bool use_prefix,
58 string* target) const {
59 string joiner = use_prefix ? prefix_ : joiner_;
60 if (requires_variable_assignment_) {
61 if (value.empty() && no_variable_assignment_if_empty_) {
62 target->append(joiner + EscapedValue(variable));
63 } else {
64 target->append(joiner + EscapedValue(variable) + "=" +
65 EscapedValue(value));
66 }
67 } else {
68 target->append(joiner + EscapedValue(value));
69 }
70 }
71
72 private:
EscapedValueuri_template::__anon6f82db490111::UriTemplateConfig73 string EscapedValue(const string& value) const {
74 string escaped;
75 if (allow_reserved_expansion_) {
76 // Reserved expansion passes through reserved and pct-encoded characters.
77 escaped = base::EscapeExternalHandlerValue(value);
78 } else {
79 escaped = base::EscapeAllExceptUnreserved(value);
80 }
81 return escaped;
82 }
83
84 const char* prefix_;
85 const char* joiner_;
86 bool requires_variable_assignment_;
87 bool no_variable_assignment_if_empty_;
88 bool allow_reserved_expansion_;
89 };
90
91 // variable is an in-out argument. On input it is the content between the
92 // '{}' in the source. On result the control parameters are stripped off
93 // leaving just the comma-separated variable name(s) that we should try to
94 // resolve.
MakeConfig(string * variable)95 UriTemplateConfig MakeConfig(string* variable) {
96 switch (*variable->data()) {
97 // Reserved expansion.
98 case '+':
99 *variable = variable->substr(1);
100 return UriTemplateConfig("", ",", false, true);
101
102 // Fragment expansion.
103 case '#':
104 *variable = variable->substr(1);
105 return UriTemplateConfig("#", ",", false, true);
106
107 // Label with dot-prefix.
108 case '.':
109 *variable = variable->substr(1);
110 return UriTemplateConfig(".", ".", false, false);
111
112 // Path segment expansion.
113 case '/':
114 *variable = variable->substr(1);
115 return UriTemplateConfig("/", "/", false, false);
116
117 // Path segment parameter expansion.
118 case ';':
119 *variable = variable->substr(1);
120 return UriTemplateConfig(";", ";", true, false, true);
121
122 // Form-style query expansion.
123 case '?':
124 *variable = variable->substr(1);
125 return UriTemplateConfig("?", "&", true, false);
126
127 // Form-style query continuation.
128 case '&':
129 *variable = variable->substr(1);
130 return UriTemplateConfig("&", "&", true, false);
131
132 // Simple expansion.
133 default:
134 return UriTemplateConfig("", ",", false, false);
135 }
136 }
137
ProcessVariableSection(string * variable_section,const std::unordered_map<string,string> & parameters,string * target,std::set<string> * vars_found)138 void ProcessVariableSection(
139 string* variable_section,
140 const std::unordered_map<string, string>& parameters,
141 string* target,
142 std::set<string>* vars_found) {
143 // Note that this function will modify the variable_section string to remove
144 // the decorators, leaving just comma-separated variable name(s).
145 UriTemplateConfig config = MakeConfig(variable_section);
146 std::vector<string> variables = base::SplitString(
147 *variable_section, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
148 bool first_var = true;
149 for (const string& variable : variables) {
150 auto found = parameters.find(variable);
151 if (found != parameters.end()) {
152 config.AppendValue(variable, found->second, first_var, target);
153 first_var = false;
154 if (vars_found) {
155 vars_found->insert(variable);
156 }
157 }
158 }
159 }
160
161 } // namespace
162
Expand(const string & path_uri,const std::unordered_map<string,string> & parameters,string * target,std::set<string> * vars_found)163 bool Expand(const string& path_uri,
164 const std::unordered_map<string, string>& parameters,
165 string* target,
166 std::set<string>* vars_found) {
167 size_t cur = 0;
168 size_t length = path_uri.length();
169 while (cur < length) {
170 size_t open = path_uri.find('{', cur);
171 size_t close = path_uri.find('}', cur);
172 if (open == string::npos) {
173 if (close == string::npos) {
174 // No more variables to process.
175 target->append(path_uri.substr(cur).data(), path_uri.length() - cur);
176 return true;
177 } else {
178 // Template was malformed. Unexpected closing brace.
179 target->clear();
180 return false;
181 }
182 }
183 target->append(path_uri, cur, open - cur);
184 size_t next_open = path_uri.find('{', open + 1);
185 if (close == string::npos || close < open || next_open < close) {
186 // Template was malformed.
187 target->clear();
188 return false;
189 }
190 string variable_section(path_uri, open + 1, close - open - 1);
191 cur = close + 1;
192
193 ProcessVariableSection(&variable_section, parameters, target, vars_found);
194 }
195 return true;
196 }
197
198 } // namespace uri_template
199