/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #include #include #include using namespace Aws::Http; using namespace Aws::Utils; namespace Aws { namespace Http { const char* SEPARATOR = "://"; bool s_compliantRfc3986Encoding = false; void SetCompliantRfc3986Encoding(bool compliant) { s_compliantRfc3986Encoding = compliant; } Aws::String urlEncodeSegment(const Aws::String& segment) { // consolidates legacy escaping logic into one local method if (s_compliantRfc3986Encoding) { return StringUtils::URLEncode(segment.c_str()); } else { Aws::StringStream ss; ss << std::hex << std::uppercase; for(unsigned char c : segment) // alnum results in UB if the value of c is not unsigned char & is not EOF { // RFC 3986 §2.3 unreserved characters if (StringUtils::IsAlnum(c)) { ss << c; continue; } switch(c) { // §2.3 unreserved characters // The path section of the URL allows unreserved characters to appear unescaped case '-': case '_': case '.': case '~': // RFC 3986 §2.2 Reserved characters // NOTE: this implementation does not accurately implement the RFC on purpose to accommodate for // discrepancies in the implementations of URL encoding between AWS services for legacy reasons. case '$': case '&': case ',': case ':': case '=': case '@': ss << c; break; default: ss << '%' << std::setfill('0') << std::setw(2) << (int)c << std::setw(0); } } return ss.str(); } } } // namespace Http } // namespace Aws URI::URI() : m_scheme(Scheme::HTTP), m_port(HTTP_DEFAULT_PORT), m_pathHasTrailingSlash(false) { } URI::URI(const Aws::String& uri) : m_scheme(Scheme::HTTP), m_port(HTTP_DEFAULT_PORT) { ParseURIParts(uri); } URI::URI(const char* uri) : m_scheme(Scheme::HTTP), m_port(HTTP_DEFAULT_PORT) { ParseURIParts(uri); } URI& URI::operator =(const Aws::String& uri) { this->ParseURIParts(uri); return *this; } URI& URI::operator =(const char* uri) { this->ParseURIParts(uri); return *this; } bool URI::operator ==(const URI& other) const { return CompareURIParts(other); } bool URI::operator ==(const Aws::String& other) const { return CompareURIParts(other); } bool URI::operator ==(const char* other) const { return CompareURIParts(other); } bool URI::operator !=(const URI& other) const { return !(*this == other); } bool URI::operator !=(const Aws::String& other) const { return !(*this == other); } bool URI::operator !=(const char* other) const { return !(*this == other); } void URI::SetScheme(Scheme value) { assert(value == Scheme::HTTP || value == Scheme::HTTPS); if (value == Scheme::HTTP) { m_port = m_port == HTTPS_DEFAULT_PORT || m_port == 0 ? HTTP_DEFAULT_PORT : m_port; m_scheme = value; } else if (value == Scheme::HTTPS) { m_port = m_port == HTTP_DEFAULT_PORT || m_port == 0 ? HTTPS_DEFAULT_PORT : m_port; m_scheme = value; } } Aws::String URI::URLEncodePathRFC3986(const Aws::String& path) { if (path.empty()) { return path; } const Aws::Vector pathParts = StringUtils::Split(path, '/'); Aws::StringStream ss; ss << std::hex << std::uppercase; // escape characters appearing in a URL path according to RFC 3986 for (const auto& segment : pathParts) { ss << '/' << urlEncodeSegment(segment); } // if the last character was also a slash, then add that back here. if (path.back() == '/') { ss << '/'; } return ss.str(); } Aws::String URI::URLEncodePath(const Aws::String& path) { Aws::Vector pathParts = StringUtils::Split(path, '/'); Aws::StringStream ss; for (Aws::Vector::iterator iter = pathParts.begin(); iter != pathParts.end(); ++iter) { ss << '/' << StringUtils::URLEncode(iter->c_str()); } //if the last character was also a slash, then add that back here. if (path.length() > 0 && path[path.length() - 1] == '/') { ss << '/'; } if (path.length() > 0 && path[0] != '/') { return ss.str().substr(1); } else { return ss.str(); } } Aws::String URI::GetPath() const { Aws::String path = ""; for (auto const& segment : m_pathSegments) { path.push_back('/'); path.append(segment); } if (m_pathSegments.empty() || m_pathHasTrailingSlash) { path.push_back('/'); } return path; } Aws::String URI::GetURLEncodedPath() const { Aws::StringStream ss; for (auto const& segment : m_pathSegments) { ss << '/' << StringUtils::URLEncode(segment.c_str()); } if (m_pathSegments.empty() || m_pathHasTrailingSlash) { ss << '/'; } return ss.str(); } Aws::String URI::GetURLEncodedPathRFC3986() const { Aws::StringStream ss; ss << std::hex << std::uppercase; // escape characters appearing in a URL path according to RFC 3986 // (mostly; there is some non-standards legacy support that can be disabled) for (const auto& segment : m_pathSegments) { ss << '/' << urlEncodeSegment(segment); } if (m_pathSegments.empty() || m_pathHasTrailingSlash) { ss << '/'; } return ss.str(); } void URI::SetPath(const Aws::String& value) { m_pathSegments.clear(); AddPathSegments(value); } //ugh, this isn't even part of the canonicalization spec. It is part of how our services have implemented their signers though.... //it doesn't really hurt anything to reorder it though, so go ahead and sort the values for parameters with the same key void InsertValueOrderedParameter(QueryStringParameterCollection& queryParams, const Aws::String& key, const Aws::String& value) { auto entriesAtKey = queryParams.equal_range(key); for (auto& entry = entriesAtKey.first; entry != entriesAtKey.second; ++entry) { if (entry->second > value) { queryParams.emplace_hint(entry, key, value); return; } } queryParams.emplace(key, value); } QueryStringParameterCollection URI::GetQueryStringParameters(bool decode) const { Aws::String queryString = GetQueryString(); QueryStringParameterCollection parameterCollection; //if we actually have a query string if (queryString.size() > 0) { size_t currentPos = 1, locationOfNextDelimiter = 1; //while we have params left to parse while (currentPos < queryString.size()) { //find next key/value pair locationOfNextDelimiter = queryString.find('&', currentPos); Aws::String keyValuePair; //if this isn't the last parameter if (locationOfNextDelimiter != Aws::String::npos) { keyValuePair = queryString.substr(currentPos, locationOfNextDelimiter - currentPos); } //if it is the last parameter else { keyValuePair = queryString.substr(currentPos); } //split on = size_t locationOfEquals = keyValuePair.find('='); Aws::String key = keyValuePair.substr(0, locationOfEquals); Aws::String value = keyValuePair.substr(locationOfEquals + 1); if(decode) { InsertValueOrderedParameter(parameterCollection, StringUtils::URLDecode(key.c_str()), StringUtils::URLDecode(value.c_str())); } else { InsertValueOrderedParameter(parameterCollection, key, value); } currentPos += keyValuePair.size() + 1; } } return parameterCollection; } void URI::CanonicalizeQueryString() { QueryStringParameterCollection sortedParameters = GetQueryStringParameters(false); Aws::StringStream queryStringStream; bool first = true; if(sortedParameters.size() > 0) { queryStringStream << "?"; } if(m_queryString.find('=') != std::string::npos) { for (QueryStringParameterCollection::iterator iter = sortedParameters.begin(); iter != sortedParameters.end(); ++iter) { if (!first) { queryStringStream << "&"; } first = false; queryStringStream << iter->first.c_str() << "=" << iter->second.c_str(); } m_queryString = queryStringStream.str(); } } void URI::AddQueryStringParameter(const char* key, const Aws::String& value) { if (m_queryString.size() <= 0) { m_queryString.append("?"); } else { m_queryString.append("&"); } m_queryString.append(StringUtils::URLEncode(key) + "=" + StringUtils::URLEncode(value.c_str())); } void URI::AddQueryStringParameter(const Aws::Map& queryStringPairs) { for(const auto& entry: queryStringPairs) { AddQueryStringParameter(entry.first.c_str(), entry.second); } } void URI::SetQueryString(const Aws::String& str) { m_queryString = ""; if (str.empty()) return; if (str.front() != '?') { m_queryString.append("?").append(str); } else { m_queryString = str; } } Aws::String URI::GetURIString(bool includeQueryString) const { assert(m_authority.size() > 0); Aws::StringStream ss; ss << SchemeMapper::ToString(m_scheme) << SEPARATOR << m_authority; if (m_scheme == Scheme::HTTP && m_port != HTTP_DEFAULT_PORT) { ss << ":" << m_port; } else if (m_scheme == Scheme::HTTPS && m_port != HTTPS_DEFAULT_PORT) { ss << ":" << m_port; } if (!m_pathSegments.empty()) { ss << GetURLEncodedPathRFC3986(); } if(includeQueryString) { ss << m_queryString; } return ss.str(); } void URI::ParseURIParts(const Aws::String& uri) { ExtractAndSetScheme(uri); ExtractAndSetAuthority(uri); ExtractAndSetPort(uri); ExtractAndSetPath(uri); ExtractAndSetQueryString(uri); } void URI::ExtractAndSetScheme(const Aws::String& uri) { size_t posOfSeparator = uri.find(SEPARATOR); if (posOfSeparator != Aws::String::npos) { Aws::String schemePortion = uri.substr(0, posOfSeparator); SetScheme(SchemeMapper::FromString(schemePortion.c_str())); } else { SetScheme(Scheme::HTTP); } } void URI::ExtractAndSetAuthority(const Aws::String& uri) { size_t authorityStart = uri.find(SEPARATOR); if (authorityStart == Aws::String::npos) { authorityStart = 0; } else { authorityStart += 3; } size_t posEndOfAuthority=0; // are we extracting an ipv6 address? if (uri.length() > authorityStart && uri.at(authorityStart) == '[') { posEndOfAuthority = uri.find(']', authorityStart); if (posEndOfAuthority == Aws::String::npos) { AWS_LOGSTREAM_ERROR("Uri", "Malformed uri: " << uri.c_str()); } else { ++posEndOfAuthority; } } else { size_t posOfEndOfAuthorityPort = uri.find(':', authorityStart); size_t posOfEndOfAuthoritySlash = uri.find('/', authorityStart); size_t posOfEndOfAuthorityQuery = uri.find('?', authorityStart); posEndOfAuthority = (std::min)({posOfEndOfAuthorityPort, posOfEndOfAuthoritySlash, posOfEndOfAuthorityQuery}); } if (posEndOfAuthority == Aws::String::npos) { posEndOfAuthority = uri.length(); } SetAuthority(uri.substr(authorityStart, posEndOfAuthority - authorityStart)); } void URI::ExtractAndSetPort(const Aws::String& uri) { size_t authorityStart = uri.find(SEPARATOR); if(authorityStart == Aws::String::npos) { authorityStart = 0; } else { authorityStart += 3; } size_t portSearchStart = authorityStart; // are we extracting an ipv6 address? if (uri.length() > portSearchStart && uri.at(portSearchStart) == '[') { size_t posEndOfAuthority = uri.find(']', portSearchStart); if (posEndOfAuthority == Aws::String::npos) { AWS_LOGSTREAM_ERROR("Uri", "Malformed uri: " << uri.c_str()); } else { portSearchStart = posEndOfAuthority; } } size_t positionOfPortDelimiter = uri.find(':', portSearchStart); bool hasPort = positionOfPortDelimiter != Aws::String::npos; if ((uri.find('/', portSearchStart) < positionOfPortDelimiter) || (uri.find('?', portSearchStart) < positionOfPortDelimiter)) { hasPort = false; } if (hasPort) { Aws::String strPort; size_t i = positionOfPortDelimiter + 1; char currentDigit = uri[i]; while (std::isdigit(currentDigit)) { strPort += currentDigit; currentDigit = uri[++i]; } SetPort(static_cast(atoi(strPort.c_str()))); } } void URI::ExtractAndSetPath(const Aws::String& uri) { size_t authorityStart = uri.find(SEPARATOR); if (authorityStart == Aws::String::npos) { authorityStart = 0; } else { authorityStart += 3; } size_t pathEnd = uri.find('?'); if (pathEnd == Aws::String::npos) { pathEnd = uri.length(); } Aws::String authorityAndPath = uri.substr(authorityStart, pathEnd - authorityStart); size_t pathStart = authorityAndPath.find('/'); if (pathStart != Aws::String::npos) { SetPath(authorityAndPath.substr(pathStart, pathEnd - pathStart)); } else { SetPath("/"); } } void URI::ExtractAndSetQueryString(const Aws::String& uri) { size_t queryStart = uri.find('?'); if (queryStart != Aws::String::npos) { m_queryString = uri.substr(queryStart); } } Aws::String URI::GetFormParameters() const { if(m_queryString.length() == 0) { return ""; } else { return m_queryString.substr(1); } } bool URI::CompareURIParts(const URI& other) const { return m_scheme == other.m_scheme && m_authority == other.m_authority && GetPath() == other.GetPath() && m_queryString == other.m_queryString; }