Skip to content

Commit b907335

Browse files
authored
Fix URI encoding when path segment contains slashes ("/") and update Chime global endpoint
1 parent cf36048 commit b907335

File tree

16 files changed

+805
-934
lines changed

16 files changed

+805
-934
lines changed

aws-cpp-sdk-chime/source/ChimeClient.cpp

+495-816
Large diffs are not rendered by default.

aws-cpp-sdk-chime/source/ChimeEndpoint.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace ChimeEndpoint
2222
{
2323
AWS_UNREFERENCED_PARAM(regionName);
2424
AWS_UNREFERENCED_PARAM(useDualStack);
25-
return "service.chime.aws.amazon.com";
25+
return "chime.us-east-1.amazonaws.com";
2626
}
2727

2828
} // namespace ChimeEndpoint

aws-cpp-sdk-core-tests/http/URITest.cpp

+51-5
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ TEST(URITest, TestSetPath)
4646
uri.SetPath(path);
4747
EXPECT_EQ(path, uri.GetPath());
4848

49-
path = "path/to/resource/";
49+
path = "path/to/resource";
5050
uri.SetPath(path);
51-
EXPECT_EQ("/path/to/resource/", uri.GetPath());
51+
EXPECT_EQ("/path/to/resource", uri.GetPath());
5252

5353
path = "//path/to//resource";
5454
uri.SetPath(path);
@@ -62,6 +62,52 @@ TEST(URITest, TestSetPath)
6262
EXPECT_EQ(path, uri.GetPath());
6363
}
6464

65+
TEST(URITest, TestAddPathSegments)
66+
{
67+
URI uri;
68+
69+
uri.AddPathSegment("//");
70+
EXPECT_STREQ("/", uri.GetPath().c_str());
71+
EXPECT_STREQ("/", uri.GetURLEncodedPath().c_str());
72+
73+
uri.SetPath("");
74+
uri.AddPathSegment("/path/");
75+
EXPECT_STREQ("/path", uri.GetPath().c_str());
76+
EXPECT_STREQ("/path", uri.GetURLEncodedPath().c_str());
77+
78+
uri.SetPath("");
79+
uri.AddPathSegment("/path/");
80+
uri.AddPathSegment("to");
81+
uri.AddPathSegment("/some");
82+
uri.AddPathSegment("resource/");
83+
EXPECT_STREQ("/path/to/some/resource", uri.GetPath().c_str());
84+
EXPECT_STREQ("/path/to/some/resource", uri.GetURLEncodedPath().c_str());
85+
86+
uri.SetPath("");
87+
uri.AddPathSegment("/int");
88+
uri.AddPathSegment(10);
89+
uri.AddPathSegment("float");
90+
uri.AddPathSegment(12.34);
91+
EXPECT_STREQ("/int/10/float/12.34", uri.GetPath().c_str());
92+
EXPECT_STREQ("/int/10/float/12.34", uri.GetURLEncodedPath().c_str());
93+
94+
uri.SetPath("");
95+
// There is no way to tell the differences between slashes in path segment and slashed as delimiters before encoding.
96+
uri.AddPathSegment("/path/segment/");
97+
EXPECT_STREQ("/path/segment", uri.GetPath().c_str());
98+
EXPECT_STREQ("/path%2Fsegment", uri.GetURLEncodedPath().c_str());
99+
100+
uri.SetPath("");
101+
uri.AddPathSegments("//");
102+
EXPECT_STREQ("/", uri.GetPath().c_str());
103+
EXPECT_STREQ("/", uri.GetURLEncodedPath().c_str());
104+
105+
uri.SetPath("");
106+
uri.AddPathSegments("//path/to//resource/");
107+
EXPECT_STREQ("/path/to/resource", uri.GetPath().c_str());
108+
EXPECT_STREQ("/path/to/resource", uri.GetURLEncodedPath().c_str());
109+
}
110+
65111
TEST(URITest, TestAddQueryStringParameters)
66112
{
67113
URI uri;
@@ -173,8 +219,8 @@ TEST(URITest, TestParse)
173219
EXPECT_EQ(Scheme::HTTP, uriThatBrokeTheOtherDay.GetScheme());
174220
EXPECT_EQ("sqs.us-east-1.amazonaws.com", uriThatBrokeTheOtherDay.GetAuthority());
175221
EXPECT_EQ(80, uriThatBrokeTheOtherDay.GetPort());
176-
EXPECT_EQ("/686094048/testQueueName/", uriThatBrokeTheOtherDay.GetPath());
177-
EXPECT_EQ("http://sqs.us-east-1.amazonaws.com/686094048/testQueueName/", uriThatBrokeTheOtherDay.GetURIString());
222+
EXPECT_EQ("/686094048/testQueueName", uriThatBrokeTheOtherDay.GetPath());
223+
EXPECT_EQ("http://sqs.us-east-1.amazonaws.com/686094048/testQueueName", uriThatBrokeTheOtherDay.GetURIString());
178224
}
179225

180226
TEST(URITest, TestParseWithColon)
@@ -232,7 +278,7 @@ TEST(URITest, TestGetURLEncodedPath)
232278
TEST(URITest, TestGetRFC3986URLEncodedPath)
233279
{
234280
URI uri = "https://test.com/path/1234/";
235-
EXPECT_STREQ("/path/1234/", URI::URLEncodePathRFC3986(uri.GetPath()).c_str());
281+
EXPECT_STREQ("/path/1234", URI::URLEncodePathRFC3986(uri.GetPath()).c_str());
236282

237283
uri = "https://test.com/path/$omething";
238284
EXPECT_STREQ("/path/$omething", URI::URLEncodePathRFC3986(uri.GetPath()).c_str());

aws-cpp-sdk-core/include/aws/core/http/URI.h

+37-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
#include <aws/core/http/Scheme.h>
1111
#include <aws/core/utils/memory/stl/AWSMap.h>
12-
#include <aws/core/utils/memory/stl/AWSString.h>
12+
#include <aws/core/utils/StringUtils.h>
1313

1414
#include <stdint.h>
1515

@@ -89,18 +89,51 @@ namespace Aws
8989
* Gets the path portion of the uri e.g. the portion after the first slash after the authority and prior to the
9090
* query string. This is not url encoded.
9191
*/
92-
inline const Aws::String& GetPath() const { return m_path; }
92+
Aws::String GetPath() const;
9393

9494
/**
9595
* Gets the path portion of the uri, url encodes it and returns it
9696
*/
97-
inline Aws::String GetURLEncodedPath() const { return URLEncodePath(m_path); }
97+
Aws::String GetURLEncodedPath() const;
98+
99+
/**
100+
* Gets the path portion of the uri, url encodes it according to RFC3986 and returns it.
101+
*/
102+
Aws::String GetURLEncodedPathRFC3986() const;
98103

99104
/**
100105
* Sets the path portion of the uri. URL encodes it if needed
101106
*/
102107
void SetPath(const Aws::String& value);
103108

109+
/**
110+
* Add a path segment to the uri.
111+
*/
112+
template<typename T>
113+
inline void AddPathSegment(T pathSegment)
114+
{
115+
Aws::StringStream ss;
116+
ss << pathSegment;
117+
Aws::String segment = ss.str();
118+
segment.erase(0, segment.find_first_not_of('/'));
119+
segment.erase(segment.find_last_not_of('/') + 1);
120+
m_pathSegments.push_back(segment);
121+
}
122+
123+
/**
124+
* Add path segments to the uri.
125+
*/
126+
template<typename T>
127+
inline void AddPathSegments(T pathSegments)
128+
{
129+
Aws::StringStream ss;
130+
ss << pathSegments;
131+
for (const auto& segment : Aws::Utils::StringUtils::Split(ss.str(), '/'))
132+
{
133+
m_pathSegments.push_back(segment);
134+
}
135+
}
136+
104137
/**
105138
* Gets the raw query string including the ?
106139
*/
@@ -162,7 +195,7 @@ namespace Aws
162195
Scheme m_scheme;
163196
Aws::String m_authority;
164197
uint16_t m_port;
165-
Aws::String m_path;
198+
Aws::Vector<Aws::String> m_pathSegments;
166199
Aws::String m_queryString;
167200
};
168201

aws-cpp-sdk-core/source/auth/AWSAuthSigner.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,14 @@ static Aws::String CanonicalizeRequestSigningString(HttpRequest& request, bool u
8282
if(urlEscapePath)
8383
{
8484
// RFC3986 is how we encode the URL before sending it on the wire.
85-
auto rfc3986EncodedPath = URI::URLEncodePathRFC3986(uriCpy.GetPath());
86-
uriCpy.SetPath(rfc3986EncodedPath);
85+
uriCpy.SetPath(uriCpy.GetURLEncodedPathRFC3986());
8786
// However, SignatureV4 uses this URL encoding scheme
8887
signingStringStream << NEWLINE << uriCpy.GetURLEncodedPath() << NEWLINE;
8988
}
9089
else
9190
{
9291
// For the services that DO decode the URL first; we don't need to double encode it.
93-
uriCpy.SetPath(uriCpy.GetURLEncodedPath());
94-
signingStringStream << NEWLINE << uriCpy.GetPath() << NEWLINE;
92+
signingStringStream << NEWLINE << uriCpy.GetURLEncodedPath() << NEWLINE;
9593
}
9694

9795
if (request.GetQueryString().find('=') != std::string::npos)

aws-cpp-sdk-core/source/http/URI.cpp

+75-12
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
#include <aws/core/http/URI.h>
77

8-
#include <aws/core/utils/StringUtils.h>
9-
#include <aws/core/utils/memory/stl/AWSStringStream.h>
108
#include <aws/core/utils/memory/stl/AWSSet.h>
119

1210
#include <cstdlib>
@@ -176,23 +174,88 @@ Aws::String URI::URLEncodePath(const Aws::String& path)
176174
}
177175
}
178176

179-
void URI::SetPath(const Aws::String& value)
177+
Aws::String URI::GetPath() const
180178
{
181-
const Aws::Vector<Aws::String> pathParts = StringUtils::Split(value, '/');
182-
Aws::String path;
183-
path.reserve(value.length() + 1/* in case we have to append slash before the path. */);
179+
Aws::String path = "";
184180

185-
for (const auto& segment : pathParts)
181+
for (auto const& segment : m_pathSegments)
186182
{
187183
path.push_back('/');
188184
path.append(segment);
189185
}
190186

191-
if (value.back() == '/')
187+
if (m_pathSegments.empty())
192188
{
193189
path.push_back('/');
194190
}
195-
m_path = std::move(path);
191+
192+
return path;
193+
}
194+
195+
Aws::String URI::GetURLEncodedPath() const
196+
{
197+
Aws::StringStream ss;
198+
199+
for (auto const& segment : m_pathSegments)
200+
{
201+
ss << '/' << StringUtils::URLEncode(segment.c_str());
202+
}
203+
204+
if (m_pathSegments.empty())
205+
{
206+
ss << '/';
207+
}
208+
209+
return ss.str();
210+
}
211+
212+
Aws::String URI::GetURLEncodedPathRFC3986() const
213+
{
214+
Aws::StringStream ss;
215+
ss << std::hex << std::uppercase;
216+
217+
// escape characters appearing in a URL path according to RFC 3986
218+
for (const auto& segment : m_pathSegments)
219+
{
220+
ss << '/';
221+
for(unsigned char c : segment) // alnum results in UB if the value of c is not unsigned char & is not EOF
222+
{
223+
// §2.3 unreserved characters
224+
if (StringUtils::IsAlnum(c))
225+
{
226+
ss << c;
227+
continue;
228+
}
229+
switch(c)
230+
{
231+
// §2.3 unreserved characters
232+
case '-': case '_': case '.': case '~':
233+
// The path section of the URL allow reserved characters to appear unescaped
234+
// RFC 3986 §2.2 Reserved characters
235+
// NOTE: this implementation does not accurately implement the RFC on purpose to accommodate for
236+
// discrepancies in the implementations of URL encoding between AWS services for legacy reasons.
237+
case '$': case '&': case ',':
238+
case ':': case '=': case '@':
239+
ss << c;
240+
break;
241+
default:
242+
ss << '%' << std::setfill('0') << std::setw(2) << (int)((unsigned char)c) << std::setw(0);
243+
}
244+
}
245+
}
246+
247+
if (m_pathSegments.empty())
248+
{
249+
ss << '/';
250+
}
251+
252+
return ss.str();
253+
}
254+
255+
void URI::SetPath(const Aws::String& value)
256+
{
257+
const Aws::Vector<Aws::String> pathParts = StringUtils::Split(value, '/');
258+
m_pathSegments = std::move(pathParts);
196259
}
197260

198261
//ugh, this isn't even part of the canonicalization spec. It is part of how our services have implemented their signers though....
@@ -347,9 +410,9 @@ Aws::String URI::GetURIString(bool includeQueryString) const
347410
ss << ":" << m_port;
348411
}
349412

350-
if(m_path != "/")
413+
if (!m_pathSegments.empty())
351414
{
352-
ss << URLEncodePathRFC3986(m_path);
415+
ss << GetURLEncodedPathRFC3986();
353416
}
354417

355418
if(includeQueryString)
@@ -506,5 +569,5 @@ Aws::String URI::GetFormParameters() const
506569

507570
bool URI::CompareURIParts(const URI& other) const
508571
{
509-
return m_scheme == other.m_scheme && m_authority == other.m_authority && m_path == other.m_path && m_queryString == other.m_queryString;
572+
return m_scheme == other.m_scheme && m_authority == other.m_authority && GetPath() == other.GetPath() && m_queryString == other.m_queryString;
510573
}

aws-cpp-sdk-core/source/http/windows/WinSyncHttpClient.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ std::shared_ptr<HttpResponse> WinSyncHttpClient::MakeRequest(const std::shared_p
260260
{
261261
//we URL encode right before going over the wire to avoid double encoding problems with the signer.
262262
URI& uriRef = request->GetUri();
263-
uriRef.SetPath(URI::URLEncodePathRFC3986(uriRef.GetPath()));
263+
uriRef.SetPath(uriRef.GetURLEncodedPathRFC3986());
264264

265265
AWS_LOGSTREAM_TRACE(GetLogTag(), "Making " << HttpMethodMapper::GetNameForHttpMethod(request->GetMethod()) <<
266266
" request to uri " << uriRef.GetURIString(true));

code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Http.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,18 @@
1515

1616
@Data
1717
public class Http {
18-
private static final Pattern URI_PARAM_PATTERN = Pattern.compile(".*\\{[\\w\\d-]+\\}");
18+
private static final Pattern URI_PARAM_PATTERN = Pattern.compile(".*\\{[\\w\\d-]+\\+?\\}");
1919

2020
private String method;
2121
private String requestUri;
2222
private String responseCode;
2323

2424
public List<String> getRequestUriParts() {
25-
String sanitizedUri = requestUri.replace("+", "");
26-
return Arrays.asList(sanitizedUri.split("\\{[\\w\\d-]+\\}"));
25+
return Arrays.asList(requestUri.split("\\{[\\w\\d-]+\\+?\\}"));
2726
}
2827

2928
public List<String> getRequestParameters() {
30-
String sanitizedUri = requestUri.replace("+", "");
31-
String[] parts = sanitizedUri.split("/|\\?|&");
29+
String[] parts = requestUri.split("/|\\?|&");
3230
List<String> paramList = new LinkedList<>();
3331

3432
for (String part : parts) {

code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/generators/cpp/CppClientGenerator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ protected Map<String, String> computeEndpointMappingForService(ServiceModel serv
383383
serviceModel.getMetadata().setGlobalEndpoint("ce.us-east-1.amazonaws.com");
384384

385385
} else if (serviceModel.getServiceName().equals("chime")) {
386-
serviceModel.getMetadata().setGlobalEndpoint("service.chime.aws.amazon.com");
386+
serviceModel.getMetadata().setGlobalEndpoint("chime.us-east-1.amazonaws.com");
387387

388388
} else if (serviceModel.getServiceName().equals("iam")) {
389389
endpoints.put("cn-north-1", "iam.cn-north-1.amazonaws.com.cn");

0 commit comments

Comments
 (0)