Skip to content

Commit

Permalink
chore: modify range to allow queries specifying only the start index (#…
Browse files Browse the repository at this point in the history
…184)

* chore: modify range regex to allow queries specifying only the start index

* address feedback and add unit tests
  • Loading branch information
imabhichow authored Mar 1, 2024
1 parent a5d714f commit 765b9c6
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ public static void aesGcmV3RangedGetOperations(String bucket) {
// but still within the same cipher block, the Amazon S3 Encryption Client returns an empty object.
assertEquals("", output);

// 5. Call getObject to retrieve a range starting from byte 40 to the end of the object,
// where the start index is within the object range, and the end index is unspecified.
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(bucket)
.range("bytes=40-")
.key(objectKey));
output = objectResponse.asUtf8String();

// Verify that when the start index is specified without an end index,
// the S3 Encryption Client returns the object from the start index to the end of the original plaintext object.
assertEquals(OBJECT_CONTENT.substring(40), output);

// Cleanup
v3Client.deleteObject(builder -> builder.bucket(bucket).key(objectKey));
v3Client.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ public static long[] getRange(String range) {
if (range == null) {
return null;
}
if (!range.matches("bytes=[0-9]+-[0-9]+")) {
if (!range.matches("^bytes=(\\d+-\\d+|\\d+-)$")) {
return null;
}
String[] rangeSplit = range.split("[-=]");
String[] rangeSplit = range.substring(6).split("-");
long[] adjustedRange = new long[2];
adjustedRange[0] = Integer.parseInt(rangeSplit[1]);
adjustedRange[1] = Integer.parseInt(rangeSplit[2]);
adjustedRange[0] = Long.parseLong(rangeSplit[0]);
adjustedRange[1] = (rangeSplit.length < 2 || rangeSplit[1].isEmpty()) ? Long.MAX_VALUE : Long.parseLong(rangeSplit[1]);
return adjustedRange;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ public void AsyncAesGcmV3toV3RangedGet() {
output = objectResponse.asUtf8String();
assertEquals("KLMNOPQRST", output);

// Valid start index within input and without specifying end index of range, returns object from start index to End of Stream
objectResponse = asyncClient.getObject(builder -> builder
.bucket(BUCKET)
.range("bytes=40-")
.key(objectKey), AsyncResponseTransformer.toBytes()).join();
output = objectResponse.asUtf8String();
assertEquals(input.substring(40), output);

// Invalid range with only specifying the end index, returns entire object
objectResponse = asyncClient.getObject(builder -> builder
.bucket(BUCKET)
.range("bytes=-40")
.key(objectKey), AsyncResponseTransformer.toBytes()).join();
output = objectResponse.asUtf8String();
assertEquals(input, output);

// Invalid range start index range greater than ending index, returns entire object
objectResponse = asyncClient.getObject(builder -> builder
.bucket(BUCKET)
Expand Down Expand Up @@ -283,6 +299,22 @@ public void AesGcmV3toV3RangedGet() {
output = objectResponse.asUtf8String();
assertEquals("KLMNOPQRST", output);

// Valid start index within input and without specifying end index of range, returns object from start index to End of Stream
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
.range("bytes=40-")
.key(objectKey));
output = objectResponse.asUtf8String();
assertEquals(input.substring(40), output);

// Invalid range with only specifying the end index, returns entire object
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
.range("bytes=-40")
.key(objectKey));
output = objectResponse.asUtf8String();
assertEquals(input, output);

// Invalid range start index range greater than ending index, returns entire object
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
Expand Down Expand Up @@ -432,6 +464,22 @@ public void AesCbcV1toV3RangedGet() {
output = objectResponse.asUtf8String();
assertEquals(input, output);

// Valid start index within input and without specifying end index of range, returns object from start index to End of Stream
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
.range("bytes=40-")
.key(objectKey));
output = objectResponse.asUtf8String();
assertEquals(input.substring(40), output);

// Invalid range with only specifying the end index, returns entire object
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
.range("bytes=-40")
.key(objectKey));
output = objectResponse.asUtf8String();
assertEquals(input, output);

// Invalid range format, returns entire object
objectResponse = v3Client.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package software.amazon.encryption.s3.legacy.internal;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;

public class RangedGetUtilsTest {
@Test
public void testGetRangeWithValidRanges() {
// Valid single and complete ranges
assertArrayEquals(new long[]{10, Long.MAX_VALUE}, RangedGetUtils.getRange("bytes=10-"), "Start range should return expected output");
assertArrayEquals(new long[]{10, 20}, RangedGetUtils.getRange("bytes=10-20"), "Complete range should return expected output");
assertArrayEquals(new long[]{15, 15}, RangedGetUtils.getRange("bytes=15-15"), "Range with start equals end should return expected output");

// Testing with Long.MAX_VALUE
assertArrayEquals(new long[]{0, Long.MAX_VALUE}, RangedGetUtils.getRange("bytes=0-"));
assertArrayEquals(new long[]{Long.MAX_VALUE - 1, Long.MAX_VALUE}, RangedGetUtils.getRange("bytes=" + (Long.MAX_VALUE - 1) + "-" + Long.MAX_VALUE));
}

@Test
public void testGetRangeWithInvalidInputs() {
// Null, empty, and invalid format inputs
assertNull(RangedGetUtils.getRange(null), "Range should be null for null input");
assertNull(RangedGetUtils.getRange(""), "Range should be null for empty input");
assertNull(RangedGetUtils.getRange("bytes=abc"), "Range should be null for non-numeric input");
assertNull(RangedGetUtils.getRange("10-100"), "Range should be null for missing 'bytes=' prefix");
assertNull(RangedGetUtils.getRange("bytes=-"), "Range should be null for invalid range without start or end specified" );
assertNull(RangedGetUtils.getRange("bytes=-10"), "Range should be null for invalid range with only end specified");
}

@Test
public void testGetCryptoRangeWithInvalidRanges() {
assertNull(RangedGetUtils.getCryptoRangeAsString("bytes=-100"), "Should return null for not specifying start range");
assertNull(RangedGetUtils.getCryptoRangeAsString("bytes=100-10"), "Should return null for start greater than end range");
}

@Test
public void testGetCryptoRangeAsStringAndAdjustmentWithValidRanges() {
// Adjusted to include the full block that contains byte 0 and the full block after byte 15, given block size of 16
assertEquals("bytes=0-32", RangedGetUtils.getCryptoRangeAsString("bytes=0-15"), "Should correctly adjust to full blocks for range as string");

// Adjusted to include the full block before byte 10 and after byte 100 after adding offset
assertEquals("bytes=0-128", RangedGetUtils.getCryptoRangeAsString("bytes=10-100"), "Should adjust range according to block size");

// Edge case: Testing with Long.MAX_VALUE
assertEquals("bytes=0-"+ Long.MAX_VALUE, RangedGetUtils.getCryptoRangeAsString("bytes=0-"));
assertEquals("bytes=16-" + Long.MAX_VALUE, RangedGetUtils.getCryptoRangeAsString("bytes=40-" + Long.MAX_VALUE));
}
}

0 comments on commit 765b9c6

Please sign in to comment.