Skip to content

Commit ee4f581

Browse files
committed
Bug Fix: Preserve trailing slashes in HTTP paths.
Fixes: CloudTrail integration test failures. Details: S3 allows defining resources with trailing slashes in the name, and requires specifying the trailing slashes in the URL to be able to delete these resources. Eg. a Bucket "foo" can have a key "/bar/", and the request must be initiated as DELETE /bar/.
1 parent 8d0c49c commit ee4f581

File tree

8 files changed

+46
-40
lines changed

8 files changed

+46
-40
lines changed

core/src/main/java/software/amazon/awssdk/core/auth/AbstractAwsSigner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private InputStream getContentUnwrapped(InputStream is) {
278278
}
279279

280280
protected String getCanonicalizedResourcePath(String resourcePath, boolean urlEncode) {
281-
if (resourcePath == null || resourcePath.isEmpty()) {
281+
if (StringUtils.isEmpty(resourcePath)) {
282282
return "/";
283283
} else {
284284
String value = urlEncode ? SdkHttpUtils.urlEncodeIgnoreSlashes(resourcePath) : resourcePath;

core/src/test/java/software/amazon/awssdk/core/http/SdkHttpFullRequestAdapterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public void adapt_ResourcePathPreserved() {
9696

9797
SdkHttpFullRequest adapted = SdkHttpFullRequestAdapter.toHttpFullRequest(request);
9898

99-
assertThat(adapted.encodedPath(), equalTo("/foo/bar"));
99+
assertThat(adapted.encodedPath(), equalTo("/foo/bar/"));
100100
}
101101

102102
@Test

http-client-spi/src/main/java/software/amazon/awssdk/http/DefaultSdkHttpFullRequest.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private String standardizeProtocol(String protocol) {
6969
}
7070

7171
private String standardizePath(String path) {
72-
if (StringUtils.isEmpty(path) || path.equals("/")) {
72+
if (StringUtils.isEmpty(path)) {
7373
return "";
7474
}
7575

@@ -82,11 +82,6 @@ private String standardizePath(String path) {
8282

8383
standardizedPath.append(path);
8484

85-
// Path must never end with '/', unless they're an empty path.
86-
if (standardizedPath.length() > 1 && path.endsWith("/")) {
87-
standardizedPath.setLength(standardizedPath.length() - 1);
88-
}
89-
9085
return standardizedPath.toString();
9186
}
9287

http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpRequest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ public interface SdkHttpRequest extends SdkHttpHeaders {
6464
/**
6565
* Returns the URL-encoded path that should be used in the HTTP request.
6666
*
67-
* <p>If a path is configured, the path will always start with '/' and never end with '/' (eg. "/my/path").
68-
* If a path is not configured, this will always return empty-string (ie. "").</p>
67+
* <p>If a path is configured, the path will always start with '/' and may or may not end with '/', depending on what the
68+
* service might expect. If a path is not configured, this will always return empty-string (ie. ""). Note that '/' is also a
69+
* valid path.</p>
6970
*
7071
* @return The path to the resource being requested.
7172
*/

http-client-spi/src/test/java/software/amazon/awssdk/http/SdkHttpRequestResponseTest.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,20 @@ public void uriConversionIsCorrect() {
4949
assertThat(normalizedUri(b -> b.protocol("https").host("localhost").port(443))).isEqualTo("https://localhost");
5050
assertThat(normalizedUri(b -> b.protocol("https").host("localhost").port(8443))).isEqualTo("https://localhost:8443");
5151
assertThat(normalizedUri(b -> b.protocol("https").host("localhost").port(80))).isEqualTo("https://localhost:80");
52+
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("foo")))
53+
.isEqualTo("http://localhost/foo");
5254
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("/foo")))
5355
.isEqualTo("http://localhost/foo");
56+
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("foo/")))
57+
.isEqualTo("http://localhost/foo/");
58+
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("/foo/")))
59+
.isEqualTo("http://localhost/foo/");
5460
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).rawQueryParameter("foo", "bar ")))
5561
.isEqualTo("http://localhost?foo=bar%20");
5662
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("/foo").rawQueryParameter("foo", "bar")))
5763
.isEqualTo("http://localhost/foo?foo=bar");
64+
assertThat(normalizedUri(b -> b.protocol("http").host("localhost").port(80).encodedPath("foo/").rawQueryParameter("foo", "bar")))
65+
.isEqualTo("http://localhost/foo/?foo=bar");
5866
}
5967

6068
private String normalizedUri(Consumer<SdkHttpFullRequest.Builder> builderMutator) {
@@ -104,17 +112,17 @@ private int normalizedPort(String protocol, Integer port) {
104112
@Test
105113
public void requestPathNormalizationIsCorrect() {
106114
assertThat(normalizedPath(null)).isEqualTo("");
107-
assertThat(normalizedPath("/")).isEqualTo("");
115+
assertThat(normalizedPath("/")).isEqualTo("/");
108116
assertThat(normalizedPath(" ")).isEqualTo("/ ");
109-
assertThat(normalizedPath(" /")).isEqualTo("/ ");
117+
assertThat(normalizedPath(" /")).isEqualTo("/ /");
110118
assertThat(normalizedPath("/ ")).isEqualTo("/ ");
111-
assertThat(normalizedPath("/ /")).isEqualTo("/ ");
119+
assertThat(normalizedPath("/ /")).isEqualTo("/ /");
112120
assertThat(normalizedPath(" / ")).isEqualTo("/ / ");
113-
assertThat(normalizedPath("/Foo/")).isEqualTo("/Foo");
114-
assertThat(normalizedPath("Foo/")).isEqualTo("/Foo");
121+
assertThat(normalizedPath("/Foo/")).isEqualTo("/Foo/");
122+
assertThat(normalizedPath("Foo/")).isEqualTo("/Foo/");
115123
assertThat(normalizedPath("Foo")).isEqualTo("/Foo");
116-
assertThat(normalizedPath("/Foo/bar/")).isEqualTo("/Foo/bar");
117-
assertThat(normalizedPath("Foo/bar/")).isEqualTo("/Foo/bar");
124+
assertThat(normalizedPath("/Foo/bar/")).isEqualTo("/Foo/bar/");
125+
assertThat(normalizedPath("Foo/bar/")).isEqualTo("/Foo/bar/");
118126
assertThat(normalizedPath("/Foo/bar")).isEqualTo("/Foo/bar");
119127
assertThat(normalizedPath("Foo/bar")).isEqualTo("/Foo/bar");
120128
assertThat(normalizedPath("%2F")).isEqualTo("/%2F");
@@ -328,7 +336,7 @@ private SdkHttpFullRequest validRequest() {
328336
}
329337

330338
private SdkHttpFullRequest.Builder validRequestBuilder() {
331-
return SdkHttpFullRequest.builder().protocol("http").host("localhost").encodedPath("/").method(SdkHttpMethod.GET);
339+
return SdkHttpFullRequest.builder().protocol("http").host("localhost").method(SdkHttpMethod.GET);
332340
}
333341

334342
private SdkHttpFullResponse validResponse() {

services/s3/src/test/java/software/amazon/awssdk/services/s3/handlers/S3EndpointResolutionTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ public void serviceLevelOperation_UsesStandardEndpoint() throws Exception {
6767

6868
assertThat(mockHttpClient.getLastRequest().getUri())
6969
.as("Uses regional S3 endpoint without bucket")
70-
.isEqualTo(URI.create(ENDPOINT_WITHOUT_BUCKET));
70+
.isEqualTo(URI.create(ENDPOINT_WITHOUT_BUCKET + "/"));
7171

7272
assertThat(mockHttpClient.getLastRequest().encodedPath())
7373
.as("Bucket is not in resource path")
74-
.isEqualTo("");
74+
.isEqualTo("/");
7575
}
7676

7777
/**
@@ -86,7 +86,7 @@ public void serviceLevelOperation_WithDualstackEnabled_UsesDualstackEndpoint() t
8686

8787
assertThat(mockHttpClient.getLastRequest().getUri())
8888
.as("Uses regional S3 endpoint without bucket")
89-
.isEqualTo(URI.create("https://s3.dualstack.ap-south-1.amazonaws.com"));
89+
.isEqualTo(URI.create("https://s3.dualstack.ap-south-1.amazonaws.com/"));
9090
}
9191

9292
/**
@@ -103,7 +103,7 @@ public void customEndpointProvided_UsesCustomEndpoint() throws Exception {
103103

104104
assertThat(mockHttpClient.getLastRequest().getUri())
105105
.as("Uses custom endpoint")
106-
.isEqualTo(customEndpoint);
106+
.isEqualTo(URI.create(customEndpoint + "/"));
107107
}
108108

109109
/**
@@ -272,7 +272,7 @@ public void unsupportedAccelerateOption_UsesStandardEndpoint() throws Exception
272272

273273
assertThat(mockHttpClient.getLastRequest().getUri())
274274
.as("Uses regional S3 endpoint")
275-
.isEqualTo(URI.create("https://s3.ap-south-1.amazonaws.com"));
275+
.isEqualTo(URI.create("https://s3.ap-south-1.amazonaws.com/"));
276276
}
277277

278278
/**

utils/src/main/java/software/amazon/awssdk/utils/http/SdkHttpUtils.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,22 +230,19 @@ public static int standardPort(String protocol) {
230230
}
231231

232232
/**
233-
* Append the given path to the given baseUri, separating them with a slash, if required. The result is always guaranteed to
234-
* have a trailing slash.
233+
* Append the given path to the given baseUri, separating them with a slash, if required. The result will preserve the
234+
* trailing slash of the provided path.
235235
*/
236236
public static String appendUri(String baseUri, String path) {
237+
Validate.paramNotNull(baseUri, "baseUri");
237238
StringBuilder resultUri = new StringBuilder(baseUri);
238239

239-
if (!baseUri.endsWith("/")) {
240-
resultUri.append('/');
241-
}
240+
if (!StringUtils.isEmpty(path)) {
241+
if (!baseUri.endsWith("/")) {
242+
resultUri.append("/");
243+
}
242244

243-
if (path != null && path.length() > 0) {
244245
resultUri.append(path.startsWith("/") ? path.substring(1) : path);
245-
246-
if (resultUri.charAt(resultUri.length() - 1) != '/') {
247-
resultUri.append('/');
248-
}
249246
}
250247

251248
return resultUri.toString();

utils/src/test/java/software/amazon/awssdk/utils/SdkHttpUtilsTest.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,19 @@ public void encodeFlattenBehavesCorrectly() {
9595
public void urisAppendCorrectly() {
9696
assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> SdkHttpUtils.appendUri(null, ""));
9797
assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> SdkHttpUtils.appendUri(null, null));
98-
assertThat(SdkHttpUtils.appendUri("", null)).isEqualTo("/");
99-
assertThat(SdkHttpUtils.appendUri("", "")).isEqualTo("/");
100-
assertThat(SdkHttpUtils.appendUri("foo.com", "")).isEqualTo("foo.com/");
98+
assertThat(SdkHttpUtils.appendUri("", null)).isEqualTo("");
99+
assertThat(SdkHttpUtils.appendUri("", "")).isEqualTo("");
100+
assertThat(SdkHttpUtils.appendUri("", "bar")).isEqualTo("/bar");
101+
assertThat(SdkHttpUtils.appendUri("", "/bar")).isEqualTo("/bar");
102+
assertThat(SdkHttpUtils.appendUri("", "bar/")).isEqualTo("/bar/");
103+
assertThat(SdkHttpUtils.appendUri("", "/bar/")).isEqualTo("/bar/");
104+
assertThat(SdkHttpUtils.appendUri("foo.com", null)).isEqualTo("foo.com");
105+
assertThat(SdkHttpUtils.appendUri("foo.com", "")).isEqualTo("foo.com");
101106
assertThat(SdkHttpUtils.appendUri("foo.com/", "")).isEqualTo("foo.com/");
102-
assertThat(SdkHttpUtils.appendUri("foo.com", "bar")).isEqualTo("foo.com/bar/");
103-
assertThat(SdkHttpUtils.appendUri("foo.com/", "bar")).isEqualTo("foo.com/bar/");
104-
assertThat(SdkHttpUtils.appendUri("foo.com", "/bar")).isEqualTo("foo.com/bar/");
105-
assertThat(SdkHttpUtils.appendUri("foo.com/", "/bar")).isEqualTo("foo.com/bar/");
107+
assertThat(SdkHttpUtils.appendUri("foo.com", "bar")).isEqualTo("foo.com/bar");
108+
assertThat(SdkHttpUtils.appendUri("foo.com/", "bar")).isEqualTo("foo.com/bar");
109+
assertThat(SdkHttpUtils.appendUri("foo.com", "/bar")).isEqualTo("foo.com/bar");
110+
assertThat(SdkHttpUtils.appendUri("foo.com/", "/bar")).isEqualTo("foo.com/bar");
106111
assertThat(SdkHttpUtils.appendUri("foo.com/", "/bar/")).isEqualTo("foo.com/bar/");
107112
assertThat(SdkHttpUtils.appendUri("foo.com/", "//bar/")).isEqualTo("foo.com//bar/");
108113
}

0 commit comments

Comments
 (0)