1 | /* |
2 | * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"). |
5 | * You may not use this file except in compliance with the License. |
6 | * A copy of the License is located at |
7 | * |
8 | * http://aws.amazon.com/apache2.0 |
9 | * |
10 | * or in the "license" file accompanying this file. This file is distributed |
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
12 | * express or implied. See the License for the specific language governing |
13 | * permissions and limitations under the License. |
14 | */ |
15 | |
16 | #include <aws/core/http/URI.h> |
17 | |
18 | #include <aws/core/utils/StringUtils.h> |
19 | #include <aws/core/utils/memory/stl/AWSStringStream.h> |
20 | #include <aws/core/utils/memory/stl/AWSSet.h> |
21 | |
22 | #include <cstdlib> |
23 | #include <cctype> |
24 | #include <cassert> |
25 | #include <algorithm> |
26 | #include <iomanip> |
27 | |
28 | using namespace Aws::Http; |
29 | using namespace Aws::Utils; |
30 | |
31 | namespace Aws |
32 | { |
33 | namespace Http |
34 | { |
35 | |
36 | const char* SEPARATOR = "://" ; |
37 | |
38 | } // namespace Http |
39 | } // namespace Aws |
40 | |
41 | URI::URI() : m_scheme(Scheme::HTTP), m_port(HTTP_DEFAULT_PORT) |
42 | { |
43 | } |
44 | |
45 | URI::URI(const Aws::String& uri) : m_scheme(Scheme::HTTP), m_port(HTTP_DEFAULT_PORT) |
46 | { |
47 | ParseURIParts(uri); |
48 | } |
49 | |
50 | URI::URI(const char* uri) : m_scheme(Scheme::HTTP), m_port(HTTP_DEFAULT_PORT) |
51 | { |
52 | ParseURIParts(uri); |
53 | } |
54 | |
55 | URI& URI::operator =(const Aws::String& uri) |
56 | { |
57 | this->ParseURIParts(uri); |
58 | return *this; |
59 | } |
60 | |
61 | URI& URI::operator =(const char* uri) |
62 | { |
63 | this->ParseURIParts(uri); |
64 | return *this; |
65 | } |
66 | |
67 | bool URI::operator ==(const URI& other) const |
68 | { |
69 | return CompareURIParts(other); |
70 | } |
71 | |
72 | bool URI::operator ==(const Aws::String& other) const |
73 | { |
74 | return CompareURIParts(other); |
75 | } |
76 | |
77 | bool URI::operator ==(const char* other) const |
78 | { |
79 | return CompareURIParts(other); |
80 | } |
81 | |
82 | bool URI::operator !=(const URI& other) const |
83 | { |
84 | return !(*this == other); |
85 | } |
86 | |
87 | bool URI::operator !=(const Aws::String& other) const |
88 | { |
89 | return !(*this == other); |
90 | } |
91 | |
92 | bool URI::operator !=(const char* other) const |
93 | { |
94 | return !(*this == other); |
95 | } |
96 | |
97 | void URI::SetScheme(Scheme value) |
98 | { |
99 | assert(value == Scheme::HTTP || value == Scheme::HTTPS); |
100 | |
101 | if (value == Scheme::HTTP) |
102 | { |
103 | m_port = m_port == HTTPS_DEFAULT_PORT || m_port == 0 ? HTTP_DEFAULT_PORT : m_port; |
104 | m_scheme = value; |
105 | } |
106 | else if (value == Scheme::HTTPS) |
107 | { |
108 | m_port = m_port == HTTP_DEFAULT_PORT || m_port == 0 ? HTTPS_DEFAULT_PORT : m_port; |
109 | m_scheme = value; |
110 | } |
111 | } |
112 | |
113 | Aws::String URI::URLEncodePathRFC3986(const Aws::String& path) |
114 | { |
115 | if(path.empty()) |
116 | { |
117 | return path; |
118 | } |
119 | |
120 | const Aws::Vector<Aws::String> pathParts = StringUtils::Split(path, '/'); |
121 | Aws::StringStream ss; |
122 | ss << std::hex << std::uppercase; |
123 | |
124 | // escape characters appearing in a URL path according to RFC 3986 |
125 | for (const auto& segment : pathParts) |
126 | { |
127 | ss << '/'; |
128 | for(unsigned char c : segment) // alnum results in UB if the value of c is not unsigned char & is not EOF |
129 | { |
130 | // §2.3 unreserved characters |
131 | if (StringUtils::IsAlnum(c)) |
132 | { |
133 | ss << c; |
134 | continue; |
135 | } |
136 | switch(c) |
137 | { |
138 | // §2.3 unreserved characters |
139 | case '-': case '_': case '.': case '~': |
140 | // The path section of the URL allow reserved characters to appear unescaped |
141 | // RFC 3986 §2.2 Reserved characters |
142 | // NOTE: this implementation does not accurately implement the RFC on purpose to accommodate for |
143 | // discrepancies in the implementations of URL encoding between AWS services for legacy reasons. |
144 | case '$': case '&': case ',': |
145 | case ':': case '=': case '@': |
146 | ss << c; |
147 | break; |
148 | default: |
149 | ss << '%' << std::setfill('0') << std::setw(2) << (int)((unsigned char)c) << std::setw(0); |
150 | } |
151 | } |
152 | } |
153 | |
154 | //if the last character was also a slash, then add that back here. |
155 | if (path.back() == '/') |
156 | { |
157 | ss << '/'; |
158 | } |
159 | |
160 | return ss.str(); |
161 | } |
162 | |
163 | Aws::String URI::URLEncodePath(const Aws::String& path) |
164 | { |
165 | Aws::Vector<Aws::String> pathParts = StringUtils::Split(path, '/'); |
166 | Aws::StringStream ss; |
167 | |
168 | for (Aws::Vector<Aws::String>::iterator iter = pathParts.begin(); iter != pathParts.end(); ++iter) |
169 | { |
170 | ss << '/' << StringUtils::URLEncode(iter->c_str()); |
171 | } |
172 | |
173 | //if the last character was also a slash, then add that back here. |
174 | if (path[path.length() - 1] == '/') |
175 | { |
176 | ss << '/'; |
177 | } |
178 | |
179 | return ss.str(); |
180 | } |
181 | |
182 | void URI::SetPath(const Aws::String& value) |
183 | { |
184 | const Aws::Vector<Aws::String> pathParts = StringUtils::Split(value, '/'); |
185 | Aws::String path; |
186 | path.reserve(value.length() + 1/* in case we have to append slash before the path. */); |
187 | |
188 | for (const auto& segment : pathParts) |
189 | { |
190 | path.push_back('/'); |
191 | path.append(segment); |
192 | } |
193 | |
194 | if (value.back() == '/') |
195 | { |
196 | path.push_back('/'); |
197 | } |
198 | m_path = std::move(path); |
199 | } |
200 | |
201 | //ugh, this isn't even part of the canonicalization spec. It is part of how our services have implemented their signers though.... |
202 | //it doesn't really hurt anything to reorder it though, so go ahead and sort the values for parameters with the same key |
203 | void InsertValueOrderedParameter(QueryStringParameterCollection& queryParams, const Aws::String& key, const Aws::String& value) |
204 | { |
205 | auto entriesAtKey = queryParams.equal_range(key); |
206 | for (auto& entry = entriesAtKey.first; entry != entriesAtKey.second; ++entry) |
207 | { |
208 | if (entry->second > value) |
209 | { |
210 | queryParams.emplace_hint(entry, key, value); |
211 | return; |
212 | } |
213 | } |
214 | |
215 | queryParams.emplace(key, value); |
216 | } |
217 | |
218 | QueryStringParameterCollection URI::GetQueryStringParameters(bool decode) const |
219 | { |
220 | Aws::String queryString = GetQueryString(); |
221 | |
222 | QueryStringParameterCollection parameterCollection; |
223 | |
224 | //if we actually have a query string |
225 | if (queryString.size() > 0) |
226 | { |
227 | size_t currentPos = 1, locationOfNextDelimiter = 1; |
228 | |
229 | //while we have params left to parse |
230 | while (currentPos < queryString.size()) |
231 | { |
232 | //find next key/value pair |
233 | locationOfNextDelimiter = queryString.find('&', currentPos); |
234 | |
235 | Aws::String keyValuePair; |
236 | |
237 | //if this isn't the last parameter |
238 | if (locationOfNextDelimiter != Aws::String::npos) |
239 | { |
240 | keyValuePair = queryString.substr(currentPos, locationOfNextDelimiter - currentPos); |
241 | } |
242 | //if it is the last parameter |
243 | else |
244 | { |
245 | keyValuePair = queryString.substr(currentPos); |
246 | } |
247 | |
248 | //split on = |
249 | size_t locationOfEquals = keyValuePair.find('='); |
250 | Aws::String key = keyValuePair.substr(0, locationOfEquals); |
251 | Aws::String value = keyValuePair.substr(locationOfEquals + 1); |
252 | |
253 | if(decode) |
254 | { |
255 | InsertValueOrderedParameter(parameterCollection, StringUtils::URLDecode(key.c_str()), StringUtils::URLDecode(value.c_str())); |
256 | } |
257 | else |
258 | { |
259 | InsertValueOrderedParameter(parameterCollection, key, value); |
260 | } |
261 | |
262 | currentPos += keyValuePair.size() + 1; |
263 | } |
264 | } |
265 | |
266 | return parameterCollection; |
267 | } |
268 | |
269 | void URI::CanonicalizeQueryString() |
270 | { |
271 | QueryStringParameterCollection sortedParameters = GetQueryStringParameters(false); |
272 | Aws::StringStream queryStringStream; |
273 | |
274 | bool first = true; |
275 | |
276 | if(sortedParameters.size() > 0) |
277 | { |
278 | queryStringStream << "?" ; |
279 | } |
280 | |
281 | if(m_queryString.find('=') != std::string::npos) |
282 | { |
283 | for (QueryStringParameterCollection::iterator iter = sortedParameters.begin(); |
284 | iter != sortedParameters.end(); ++iter) |
285 | { |
286 | if (!first) |
287 | { |
288 | queryStringStream << "&" ; |
289 | } |
290 | |
291 | first = false; |
292 | queryStringStream << iter->first.c_str() << "=" << iter->second.c_str(); |
293 | } |
294 | |
295 | m_queryString = queryStringStream.str(); |
296 | } |
297 | } |
298 | |
299 | void URI::AddQueryStringParameter(const char* key, const Aws::String& value) |
300 | { |
301 | if (m_queryString.size() <= 0) |
302 | { |
303 | m_queryString.append("?" ); |
304 | } |
305 | else |
306 | { |
307 | m_queryString.append("&" ); |
308 | } |
309 | |
310 | m_queryString.append(StringUtils::URLEncode(key) + "=" + StringUtils::URLEncode(value.c_str())); |
311 | } |
312 | |
313 | void URI::AddQueryStringParameter(const Aws::Map<Aws::String, Aws::String>& queryStringPairs) |
314 | { |
315 | for(const auto& entry: queryStringPairs) |
316 | { |
317 | AddQueryStringParameter(entry.first.c_str(), entry.second); |
318 | } |
319 | } |
320 | |
321 | void URI::SetQueryString(const Aws::String& str) |
322 | { |
323 | m_queryString = "" ; |
324 | |
325 | if (str.empty()) return; |
326 | |
327 | if (str.front() != '?') |
328 | { |
329 | m_queryString.append("?" ).append(str); |
330 | } |
331 | else |
332 | { |
333 | m_queryString = str; |
334 | } |
335 | } |
336 | |
337 | Aws::String URI::GetURIString(bool includeQueryString) const |
338 | { |
339 | assert(m_authority.size() > 0); |
340 | |
341 | Aws::StringStream ss; |
342 | ss << SchemeMapper::ToString(m_scheme) << SEPARATOR << m_authority; |
343 | |
344 | if (m_scheme == Scheme::HTTP && m_port != HTTP_DEFAULT_PORT) |
345 | { |
346 | ss << ":" << m_port; |
347 | } |
348 | else if (m_scheme == Scheme::HTTPS && m_port != HTTPS_DEFAULT_PORT) |
349 | { |
350 | ss << ":" << m_port; |
351 | } |
352 | |
353 | if(m_path != "/" ) |
354 | { |
355 | ss << URLEncodePathRFC3986(m_path); |
356 | } |
357 | |
358 | if(includeQueryString) |
359 | { |
360 | ss << m_queryString; |
361 | } |
362 | |
363 | return ss.str(); |
364 | } |
365 | |
366 | void URI::ParseURIParts(const Aws::String& uri) |
367 | { |
368 | ExtractAndSetScheme(uri); |
369 | ExtractAndSetAuthority(uri); |
370 | ExtractAndSetPort(uri); |
371 | ExtractAndSetPath(uri); |
372 | ExtractAndSetQueryString(uri); |
373 | } |
374 | |
375 | void URI::ExtractAndSetScheme(const Aws::String& uri) |
376 | { |
377 | size_t posOfSeparator = uri.find(SEPARATOR); |
378 | |
379 | if (posOfSeparator != Aws::String::npos) |
380 | { |
381 | Aws::String schemePortion = uri.substr(0, posOfSeparator); |
382 | SetScheme(SchemeMapper::FromString(schemePortion.c_str())); |
383 | } |
384 | else |
385 | { |
386 | SetScheme(Scheme::HTTP); |
387 | } |
388 | } |
389 | |
390 | void URI::ExtractAndSetAuthority(const Aws::String& uri) |
391 | { |
392 | size_t authorityStart = uri.find(SEPARATOR); |
393 | |
394 | if (authorityStart == Aws::String::npos) |
395 | { |
396 | authorityStart = 0; |
397 | } |
398 | else |
399 | { |
400 | authorityStart += 3; |
401 | } |
402 | |
403 | size_t posOfEndOfAuthorityPort = uri.find(':', authorityStart); |
404 | size_t posOfEndOfAuthoritySlash = uri.find('/', authorityStart); |
405 | size_t posOfEndOfAuthorityQuery = uri.find('?', authorityStart); |
406 | size_t posEndOfAuthority = (std::min)({posOfEndOfAuthorityPort, posOfEndOfAuthoritySlash, posOfEndOfAuthorityQuery}); |
407 | if (posEndOfAuthority == Aws::String::npos) |
408 | { |
409 | posEndOfAuthority = uri.length(); |
410 | } |
411 | |
412 | SetAuthority(uri.substr(authorityStart, posEndOfAuthority - authorityStart)); |
413 | } |
414 | |
415 | void URI::ExtractAndSetPort(const Aws::String& uri) |
416 | { |
417 | size_t authorityStart = uri.find(SEPARATOR); |
418 | |
419 | if(authorityStart == Aws::String::npos) |
420 | { |
421 | authorityStart = 0; |
422 | } |
423 | else |
424 | { |
425 | authorityStart += 3; |
426 | } |
427 | |
428 | size_t positionOfPortDelimiter = uri.find(':', authorityStart); |
429 | |
430 | bool hasPort = positionOfPortDelimiter != Aws::String::npos; |
431 | |
432 | if ((uri.find('/', authorityStart) < positionOfPortDelimiter) || (uri.find('?', authorityStart) < positionOfPortDelimiter)) |
433 | { |
434 | hasPort = false; |
435 | } |
436 | |
437 | if (hasPort) |
438 | { |
439 | Aws::String strPort; |
440 | |
441 | size_t i = positionOfPortDelimiter + 1; |
442 | char currentDigit = uri[i]; |
443 | |
444 | while (std::isdigit(currentDigit)) |
445 | { |
446 | strPort += currentDigit; |
447 | currentDigit = uri[++i]; |
448 | } |
449 | |
450 | SetPort(static_cast<uint16_t>(atoi(strPort.c_str()))); |
451 | } |
452 | } |
453 | |
454 | void URI::ExtractAndSetPath(const Aws::String& uri) |
455 | { |
456 | size_t authorityStart = uri.find(SEPARATOR); |
457 | |
458 | if (authorityStart == Aws::String::npos) |
459 | { |
460 | authorityStart = 0; |
461 | } |
462 | else |
463 | { |
464 | authorityStart += 3; |
465 | } |
466 | |
467 | size_t pathEnd = uri.find('?'); |
468 | |
469 | if (pathEnd == Aws::String::npos) |
470 | { |
471 | pathEnd = uri.length(); |
472 | } |
473 | |
474 | Aws::String authorityAndPath = uri.substr(authorityStart, pathEnd - authorityStart); |
475 | |
476 | size_t pathStart = authorityAndPath.find('/'); |
477 | |
478 | if (pathStart != Aws::String::npos) |
479 | { |
480 | SetPath(authorityAndPath.substr(pathStart, pathEnd - pathStart)); |
481 | } |
482 | else |
483 | { |
484 | SetPath("/" ); |
485 | } |
486 | } |
487 | |
488 | void URI::ExtractAndSetQueryString(const Aws::String& uri) |
489 | { |
490 | size_t queryStart = uri.find('?'); |
491 | |
492 | if (queryStart != Aws::String::npos) |
493 | { |
494 | m_queryString = uri.substr(queryStart); |
495 | } |
496 | } |
497 | |
498 | Aws::String URI::GetFormParameters() const |
499 | { |
500 | if(m_queryString.length() == 0) |
501 | { |
502 | return "" ; |
503 | } |
504 | else |
505 | { |
506 | return m_queryString.substr(1); |
507 | } |
508 | } |
509 | |
510 | bool URI::CompareURIParts(const URI& other) const |
511 | { |
512 | return m_scheme == other.m_scheme && m_authority == other.m_authority && m_path == other.m_path && m_queryString == other.m_queryString; |
513 | } |
514 | |