Skip to content

Commit 1ddfb0e

Browse files
authored
normalize key bugfix in TM (#5024)
* fix normalize key method to not strip extra character when prefix is not immediately followed by separator in the key * Pr comments - normalizeKey take prefix as string - added test cases - javadoc * changelog * checkstyle
1 parent f79332b commit 1ddfb0e

File tree

4 files changed

+140
-32
lines changed

4 files changed

+140
-32
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "S3 Transfer Manager",
4+
"contributor": "",
5+
"description": "Fix normalize key method to not strip extra character when prefix is not immediately followed by separator in the key."
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 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+
package software.amazon.awssdk.transfer.s3.internal;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.utils.StringUtils;
20+
import software.amazon.awssdk.utils.Validate;
21+
22+
@SdkInternalApi
23+
final class DirectoryHelperUtils {
24+
25+
private DirectoryHelperUtils() {
26+
}
27+
28+
/**
29+
* If the prefix is not empty AND the key contains the delimiter, normalize the key by stripping the prefix from the key. If a
30+
* delimiter is null (not provided by user), use "/" by default.
31+
* For example: given a request with prefix = "notes/2021" or
32+
* "notes/2021/", delimiter = "/" and key = "notes/2021/1.txt", the normalized key should be "1.txt".
33+
* If the prefix is not the full name of the folder, the folder name will be truncated. For example: given a request
34+
* with prefix = "top-" , delimiter = "/" and key = "top-level/sub-folder/1.txt", the normalized key should be
35+
* "level/sub-folder/1.txt"
36+
*/
37+
static String normalizeKey(String prefix,
38+
String key,
39+
String delimiter) {
40+
Validate.notNull(delimiter, "delimiter must not be null");
41+
42+
if (StringUtils.isEmpty(key) || StringUtils.isEmpty(prefix)) {
43+
return key;
44+
}
45+
46+
if (!key.startsWith(prefix)) {
47+
return key;
48+
}
49+
50+
if (!key.contains(delimiter)) {
51+
return key;
52+
}
53+
54+
String stripped = key.substring(prefix.length());
55+
if (prefix.endsWith(delimiter) || !stripped.startsWith(delimiter)) {
56+
return stripped;
57+
}
58+
59+
return stripped.substring(1);
60+
}
61+
62+
}

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/DownloadDirectoryHelper.java

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private Path determineDestinationPath(DownloadDirectoryRequest downloadDirectory
143143
S3Object s3Object) {
144144
FileSystem fileSystem = downloadDirectoryRequest.destination().getFileSystem();
145145
String delimiter = listRequest.delimiter() == null ? DEFAULT_DELIMITER : listRequest.delimiter();
146-
String key = normalizeKey(listRequest, s3Object.key(), delimiter);
146+
String key = DirectoryHelperUtils.normalizeKey(listRequest.prefix(), s3Object.key(), delimiter);
147147
String relativePath = getRelativePath(fileSystem, delimiter, key);
148148
Path destinationPath = downloadDirectoryRequest.destination().resolve(relativePath);
149149
validatePath(downloadDirectoryRequest.destination(), destinationPath, s3Object.key());
@@ -192,37 +192,6 @@ private CompletableFuture<CompletedFileDownload> doDownloadSingleFile(DownloadDi
192192
}
193193
}
194194

195-
/**
196-
* If the prefix is not empty AND the key contains the delimiter, normalize the key by stripping the prefix from the key.
197-
*
198-
* If a delimiter is null (not provided by user), use "/" by default.
199-
*
200-
* For example: given a request with prefix = "notes/2021" or "notes/2021/", delimiter = "/" and key = "notes/2021/1.txt",
201-
* the normalized key should be "1.txt".
202-
*/
203-
private static String normalizeKey(ListObjectsV2Request listObjectsRequest,
204-
String key,
205-
String delimiter) {
206-
if (StringUtils.isEmpty(listObjectsRequest.prefix())) {
207-
return key;
208-
}
209-
210-
String prefix = listObjectsRequest.prefix();
211-
212-
if (!key.contains(delimiter)) {
213-
return key;
214-
}
215-
216-
String normalizedKey;
217-
218-
if (prefix.endsWith(delimiter)) {
219-
normalizedKey = key.substring(prefix.length());
220-
} else {
221-
normalizedKey = key.substring(prefix.length() + delimiter.length());
222-
}
223-
return normalizedKey;
224-
225-
}
226195

227196
private static String getRelativePath(FileSystem fileSystem, String delimiter, String key) {
228197
if (delimiter == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 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+
package software.amazon.awssdk.transfer.s3.internal;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
import java.util.Arrays;
22+
import java.util.List;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.junit.jupiter.params.provider.ValueSource;
28+
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
29+
30+
class DirectoryHelperUtilsTest {
31+
32+
@ParameterizedTest
33+
@MethodSource("arguments")
34+
void testNormalizeKey(String prefix, String key, String expected) {
35+
String normalized = DirectoryHelperUtils.normalizeKey(prefix, key, "/");
36+
assertThat(normalized).isEqualTo(expected);
37+
}
38+
39+
@ParameterizedTest
40+
@ValueSource(strings = {"/", "//", "\\", "|", "delim"})
41+
void testDelimiter(String delimiter) {
42+
String prefix = String.format("notes%s2021%s", delimiter, delimiter);
43+
String key = String.format("notes%s2021%s1.txt", delimiter, delimiter);
44+
String normalized = DirectoryHelperUtils.normalizeKey(prefix, key, delimiter);
45+
assertThat(normalized).isEqualTo("1.txt");
46+
}
47+
48+
private static List<Arguments> arguments() {
49+
return Arrays.asList(
50+
arg(null, "no-delim", "no-delim"),
51+
arg("", "no-delim", "no-delim"),
52+
arg("", "delim/with/separator", "delim/with/separator"),
53+
arg("no-delim", "", ""),
54+
arg("no-delim", "no-delim", "no-delim"),
55+
arg("delim", "delim/", ""),
56+
arg("prefix", "not-in-key", "not-in-key"),
57+
arg("notes/2021", "notes/2021/1.txt", "1.txt"),
58+
arg("notes/2021/", "notes/2021/1.txt", "1.txt"),
59+
arg("top-", "top-level/sub-folder/1.txt", "level/sub-folder/1.txt"),
60+
arg("someInner", "someInnerFolder/another/file1.txt", "Folder/another/file1.txt"),
61+
arg("someInner", "someInnerF/another/file1.txt", "F/another/file1.txt"),
62+
arg("someInner", "someInner/another/file1.txt", "another/file1.txt"),
63+
arg("someInner/a", "someInner/another/file1.txt", "nother/file1.txt")
64+
);
65+
}
66+
67+
private static Arguments arg(String prefix, String key, String expected) {
68+
return Arguments.of(prefix, key, expected);
69+
}
70+
71+
}

0 commit comments

Comments
 (0)