Skip to content

Commit 765b9c6

Browse files
authored
chore: modify range to allow queries specifying only the start index (#184)
* chore: modify range regex to allow queries specifying only the start index * address feedback and add unit tests
1 parent a5d714f commit 765b9c6

File tree

4 files changed

+115
-4
lines changed

4 files changed

+115
-4
lines changed

src/examples/java/software/amazon/encryption/s3/examples/RangedGetExample.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ public static void aesGcmV3RangedGetOperations(String bucket) {
134134
// but still within the same cipher block, the Amazon S3 Encryption Client returns an empty object.
135135
assertEquals("", output);
136136

137+
// 5. Call getObject to retrieve a range starting from byte 40 to the end of the object,
138+
// where the start index is within the object range, and the end index is unspecified.
139+
objectResponse = v3Client.getObjectAsBytes(builder -> builder
140+
.bucket(bucket)
141+
.range("bytes=40-")
142+
.key(objectKey));
143+
output = objectResponse.asUtf8String();
144+
145+
// Verify that when the start index is specified without an end index,
146+
// the S3 Encryption Client returns the object from the start index to the end of the original plaintext object.
147+
assertEquals(OBJECT_CONTENT.substring(40), output);
148+
137149
// Cleanup
138150
v3Client.deleteObject(builder -> builder.bucket(bucket).key(objectKey));
139151
v3Client.close();

src/main/java/software/amazon/encryption/s3/legacy/internal/RangedGetUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ public static long[] getRange(String range) {
1818
if (range == null) {
1919
return null;
2020
}
21-
if (!range.matches("bytes=[0-9]+-[0-9]+")) {
21+
if (!range.matches("^bytes=(\\d+-\\d+|\\d+-)$")) {
2222
return null;
2323
}
24-
String[] rangeSplit = range.split("[-=]");
24+
String[] rangeSplit = range.substring(6).split("-");
2525
long[] adjustedRange = new long[2];
26-
adjustedRange[0] = Integer.parseInt(rangeSplit[1]);
27-
adjustedRange[1] = Integer.parseInt(rangeSplit[2]);
26+
adjustedRange[0] = Long.parseLong(rangeSplit[0]);
27+
adjustedRange[1] = (rangeSplit.length < 2 || rangeSplit[1].isEmpty()) ? Long.MAX_VALUE : Long.parseLong(rangeSplit[1]);
2828
return adjustedRange;
2929
}
3030

src/test/java/software/amazon/encryption/s3/S3EncryptionClientRangedGetCompatibilityTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,22 @@ public void AsyncAesGcmV3toV3RangedGet() {
8181
output = objectResponse.asUtf8String();
8282
assertEquals("KLMNOPQRST", output);
8383

84+
// Valid start index within input and without specifying end index of range, returns object from start index to End of Stream
85+
objectResponse = asyncClient.getObject(builder -> builder
86+
.bucket(BUCKET)
87+
.range("bytes=40-")
88+
.key(objectKey), AsyncResponseTransformer.toBytes()).join();
89+
output = objectResponse.asUtf8String();
90+
assertEquals(input.substring(40), output);
91+
92+
// Invalid range with only specifying the end index, returns entire object
93+
objectResponse = asyncClient.getObject(builder -> builder
94+
.bucket(BUCKET)
95+
.range("bytes=-40")
96+
.key(objectKey), AsyncResponseTransformer.toBytes()).join();
97+
output = objectResponse.asUtf8String();
98+
assertEquals(input, output);
99+
84100
// Invalid range start index range greater than ending index, returns entire object
85101
objectResponse = asyncClient.getObject(builder -> builder
86102
.bucket(BUCKET)
@@ -283,6 +299,22 @@ public void AesGcmV3toV3RangedGet() {
283299
output = objectResponse.asUtf8String();
284300
assertEquals("KLMNOPQRST", output);
285301

302+
// Valid start index within input and without specifying end index of range, returns object from start index to End of Stream
303+
objectResponse = v3Client.getObjectAsBytes(builder -> builder
304+
.bucket(BUCKET)
305+
.range("bytes=40-")
306+
.key(objectKey));
307+
output = objectResponse.asUtf8String();
308+
assertEquals(input.substring(40), output);
309+
310+
// Invalid range with only specifying the end index, returns entire object
311+
objectResponse = v3Client.getObjectAsBytes(builder -> builder
312+
.bucket(BUCKET)
313+
.range("bytes=-40")
314+
.key(objectKey));
315+
output = objectResponse.asUtf8String();
316+
assertEquals(input, output);
317+
286318
// Invalid range start index range greater than ending index, returns entire object
287319
objectResponse = v3Client.getObjectAsBytes(builder -> builder
288320
.bucket(BUCKET)
@@ -432,6 +464,22 @@ public void AesCbcV1toV3RangedGet() {
432464
output = objectResponse.asUtf8String();
433465
assertEquals(input, output);
434466

467+
// Valid start index within input and without specifying end index of range, returns object from start index to End of Stream
468+
objectResponse = v3Client.getObjectAsBytes(builder -> builder
469+
.bucket(BUCKET)
470+
.range("bytes=40-")
471+
.key(objectKey));
472+
output = objectResponse.asUtf8String();
473+
assertEquals(input.substring(40), output);
474+
475+
// Invalid range with only specifying the end index, returns entire object
476+
objectResponse = v3Client.getObjectAsBytes(builder -> builder
477+
.bucket(BUCKET)
478+
.range("bytes=-40")
479+
.key(objectKey));
480+
output = objectResponse.asUtf8String();
481+
assertEquals(input, output);
482+
435483
// Invalid range format, returns entire object
436484
objectResponse = v3Client.getObjectAsBytes(builder -> builder
437485
.bucket(BUCKET)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package software.amazon.encryption.s3.legacy.internal;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
public class RangedGetUtilsTest {
10+
@Test
11+
public void testGetRangeWithValidRanges() {
12+
// Valid single and complete ranges
13+
assertArrayEquals(new long[]{10, Long.MAX_VALUE}, RangedGetUtils.getRange("bytes=10-"), "Start range should return expected output");
14+
assertArrayEquals(new long[]{10, 20}, RangedGetUtils.getRange("bytes=10-20"), "Complete range should return expected output");
15+
assertArrayEquals(new long[]{15, 15}, RangedGetUtils.getRange("bytes=15-15"), "Range with start equals end should return expected output");
16+
17+
// Testing with Long.MAX_VALUE
18+
assertArrayEquals(new long[]{0, Long.MAX_VALUE}, RangedGetUtils.getRange("bytes=0-"));
19+
assertArrayEquals(new long[]{Long.MAX_VALUE - 1, Long.MAX_VALUE}, RangedGetUtils.getRange("bytes=" + (Long.MAX_VALUE - 1) + "-" + Long.MAX_VALUE));
20+
}
21+
22+
@Test
23+
public void testGetRangeWithInvalidInputs() {
24+
// Null, empty, and invalid format inputs
25+
assertNull(RangedGetUtils.getRange(null), "Range should be null for null input");
26+
assertNull(RangedGetUtils.getRange(""), "Range should be null for empty input");
27+
assertNull(RangedGetUtils.getRange("bytes=abc"), "Range should be null for non-numeric input");
28+
assertNull(RangedGetUtils.getRange("10-100"), "Range should be null for missing 'bytes=' prefix");
29+
assertNull(RangedGetUtils.getRange("bytes=-"), "Range should be null for invalid range without start or end specified" );
30+
assertNull(RangedGetUtils.getRange("bytes=-10"), "Range should be null for invalid range with only end specified");
31+
}
32+
33+
@Test
34+
public void testGetCryptoRangeWithInvalidRanges() {
35+
assertNull(RangedGetUtils.getCryptoRangeAsString("bytes=-100"), "Should return null for not specifying start range");
36+
assertNull(RangedGetUtils.getCryptoRangeAsString("bytes=100-10"), "Should return null for start greater than end range");
37+
}
38+
39+
@Test
40+
public void testGetCryptoRangeAsStringAndAdjustmentWithValidRanges() {
41+
// Adjusted to include the full block that contains byte 0 and the full block after byte 15, given block size of 16
42+
assertEquals("bytes=0-32", RangedGetUtils.getCryptoRangeAsString("bytes=0-15"), "Should correctly adjust to full blocks for range as string");
43+
44+
// Adjusted to include the full block before byte 10 and after byte 100 after adding offset
45+
assertEquals("bytes=0-128", RangedGetUtils.getCryptoRangeAsString("bytes=10-100"), "Should adjust range according to block size");
46+
47+
// Edge case: Testing with Long.MAX_VALUE
48+
assertEquals("bytes=0-"+ Long.MAX_VALUE, RangedGetUtils.getCryptoRangeAsString("bytes=0-"));
49+
assertEquals("bytes=16-" + Long.MAX_VALUE, RangedGetUtils.getCryptoRangeAsString("bytes=40-" + Long.MAX_VALUE));
50+
}
51+
}

0 commit comments

Comments
 (0)