xref: /aosp_15_r20/external/cronet/net/third_party/uri_template/uri_template.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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