diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
index 87776c3499cf..b6f45fcb3cf3 100644
--- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
+++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
@@ -29,6 +29,7 @@
import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonServiceException.ErrorType;
+import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AbortMultipartUploadRequest;
import com.amazonaws.services.s3.model.AccessControlList;
@@ -37,6 +38,7 @@
import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
import com.amazonaws.services.s3.model.CreateBucketRequest;
+import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.Grantee;
import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest;
@@ -69,17 +71,22 @@
import com.amazonaws.services.s3.transfer.TransferManagerBuilder;
import com.amazonaws.services.s3.transfer.Upload;
import com.amazonaws.services.s3.transfer.model.UploadResult;
+import com.amazonaws.util.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -988,6 +995,42 @@ public void testQuotaExceeded() throws IOException {
assertEquals("QuotaExceeded", ase.getErrorCode());
}
+ @Test
+ public void testPresignedUrlGet() throws IOException {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "bar";
+ s3Client.createBucket(bucketName);
+
+ InputStream is = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+
+ s3Client.putObject(bucketName, keyName, is, new ObjectMetadata());
+
+ // Set the presigned URL to expire after one hour.
+ Date expiration = Date.from(Instant.now().plusMillis(1000 * 60 * 60));
+
+ // Generate the presigned URL
+ GeneratePresignedUrlRequest generatePresignedUrlRequest =
+ new GeneratePresignedUrlRequest(bucketName, keyName)
+ .withMethod(HttpMethod.GET)
+ .withExpiration(expiration);
+ generatePresignedUrlRequest.addRequestParameter("x-custom-parameter", "custom-value");
+ URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
+
+ // Download the object using HttpUrlConnection (since v1.1)
+ // Capture the response body to a byte array.
+ URL presignedUrl = new URL(url.toExternalForm());
+ HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
+ connection.setRequestMethod("GET");
+ // Download the result of executing the request.
+ try (InputStream s3is = connection.getInputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(
+ content.getBytes(StandardCharsets.UTF_8).length)) {
+ IOUtils.copy(s3is, bos);
+ assertEquals(content, bos.toString("UTF-8"));
+ }
+ }
+
private boolean isBucketEmpty(Bucket bucket) {
ObjectListing objectListing = s3Client.listObjects(bucket.getName());
return objectListing.getObjectSummaries().isEmpty();
diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
index 6087787e2dfa..19d03210c474 100644
--- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
+++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
@@ -25,14 +25,18 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
+import java.net.HttpURLConnection;
+import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -63,14 +67,22 @@
import org.junit.jupiter.api.io.TempDir;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.http.HttpExecuteRequest;
+import software.amazon.awssdk.http.HttpExecuteResponse;
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse;
import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload;
import software.amazon.awssdk.services.s3.model.CompletedPart;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.services.s3.model.CopyObjectResponse;
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
@@ -84,10 +96,14 @@
import software.amazon.awssdk.services.s3.model.Tagging;
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
import software.amazon.awssdk.services.s3.model.UploadPartResponse;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
+import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
import software.amazon.awssdk.transfer.s3.model.FileDownload;
import software.amazon.awssdk.transfer.s3.model.ResumableFileDownload;
+import software.amazon.awssdk.utils.IoUtils;
/**
* This is an abstract class to test the AWS Java S3 SDK operations.
@@ -389,6 +405,78 @@ public void testResumableDownloadWithEtagMismatch() throws Exception {
}
}
+ @Test
+ public void testPresignedUrlGet() throws Exception {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "bar";
+ s3Client.createBucket(b -> b.bucket(bucketName));
+
+ s3Client.putObject(b -> b
+ .bucket(bucketName)
+ .key(keyName),
+ RequestBody.fromString(content));
+
+ try (S3Presigner presigner = S3Presigner.builder()
+ // TODO: Find a way to retrieve the path style configuration from S3Client instead
+ .serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build())
+ .endpointOverride(s3Client.serviceClientConfiguration().endpointOverride().get())
+ .region(s3Client.serviceClientConfiguration().region())
+ .credentialsProvider(s3Client.serviceClientConfiguration().credentialsProvider()).build()) {
+ GetObjectRequest objectRequest = GetObjectRequest.builder()
+ .bucket(bucketName)
+ .key(keyName)
+ .build();
+
+ GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
+ .signatureDuration(Duration.ofMinutes(10)) // The URL will expire in 10 minutes.
+ .getObjectRequest(objectRequest)
+ .build();
+
+ PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest);
+
+ // Download the object using HttpUrlConnection (since v1.1)
+ // Capture the response body to a byte array.
+ URL presignedUrl = presignedRequest.url();
+ HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
+ connection.setRequestMethod("GET");
+ // Download the result of executing the request.
+ try (InputStream s3is = connection.getInputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(
+ content.getBytes(StandardCharsets.UTF_8).length)) {
+ IoUtils.copy(s3is, bos);
+ assertEquals(content, bos.toString("UTF-8"));
+ }
+
+ // Use the AWS SDK for Java SdkHttpClient class to do the download
+ SdkHttpRequest request = SdkHttpRequest.builder()
+ .method(SdkHttpMethod.GET)
+ .uri(presignedUrl.toURI())
+ .build();
+
+ HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
+ .request(request)
+ .build();
+
+ try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(
+ content.getBytes(StandardCharsets.UTF_8).length)) {
+ HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
+ assertTrue(response.responseBody().isPresent(), () -> "The presigned url download request " +
+ "should have a response body");
+ response.responseBody().ifPresent(
+ abortableInputStream -> {
+ try {
+ IoUtils.copy(abortableInputStream, bos);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ assertEquals(content, bos.toString("UTF-8"));
+ }
+ }
+ }
+
private String getBucketName() {
return getBucketName("");
}
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/SignedChunksInputStream.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/SignedChunksInputStream.java
index 725b75591307..f3c825db4e63 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/SignedChunksInputStream.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/SignedChunksInputStream.java
@@ -17,21 +17,57 @@
package org.apache.hadoop.ozone.s3;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.eol;
+
import java.io.IOException;
import java.io.InputStream;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * Input stream implementation to read body with chunked signatures. This should also work
+ * Input stream implementation to read body of a signed chunked upload. This should also work
* with the chunked payloads with trailer.
*
+ *
+ * Example chunk data:
+ *
+ * 10000;chunk-signature=b474d8862b1487a5145d686f57f013e54db672cee1c953b3010fb58501ef5aa2\r\n
+ * <65536-bytes>\r\n
+ * 400;chunk-signature=1c1344b170168f8e65b41376b44b20fe354e373826ccbbe2c1d40a8cae51e5c7\r\n
+ * <1024-bytes>\r\n
+ * 0;chunk-signature=b6c6ea8a5354eaf15b3cb7646744f4275b71ea724fed81ceb9323e279d449df9\r\n
+ * x-amz-checksum-crc32c:sOO8/Q==\r\n
+ * x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n
+ *
+ *
+ * For the first chunk 10000 will be read and decoded from base-16 representation to 65536, which is the size of
+ * the first chunk payload. Each chunk upload ends with a zero-byte final additional chunk.
+ * At the end, there might be a trailer checksum payload and signature, depending on whether the x-amz-content-sha256
+ * header value contains "-TRAILER" suffix (e.g. STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER
+ * and STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER) and "x-amz-trailer" is specified (e.g. x-amz-checksum-crc32c).
+ *
+ *
+ *
+ * The logic is similar to {@link UnsignedChunksInputStream}, but there is a "chunk-signature" to parse.
+ *
+ *
+ *
* Note that there are no actual chunk signature verification taking place. The InputStream only
* returns the actual chunk payload from chunked signatures format.
+ *
*
- * See
- * - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
- * - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming-trailers.html
+ * Reference:
+ *
*/
public class SignedChunksInputStream extends InputStream {
@@ -46,12 +82,21 @@ public class SignedChunksInputStream extends InputStream {
*/
private int remainingData = 0;
+ /**
+ * Every chunked uploads (multiple chunks) contains an additional final zero-byte
+ * chunk. This can be used as the end-of-file marker.
+ */
+ private boolean isFinalChunkEncountered = false;
+
public SignedChunksInputStream(InputStream inputStream) {
originalStream = inputStream;
}
@Override
public int read() throws IOException {
+ if (isFinalChunkEncountered) {
+ return -1;
+ }
if (remainingData > 0) {
int curr = originalStream.read();
remainingData--;
@@ -63,7 +108,10 @@ public int read() throws IOException {
return curr;
} else {
remainingData = readContentLengthFromHeader();
- if (remainingData == -1) {
+ if (remainingData <= 0) {
+ // there is always a final zero byte chunk so we can stop reading
+ // if we encounter this chunk
+ isFinalChunkEncountered = true;
return -1;
}
return read();
@@ -72,12 +120,14 @@ public int read() throws IOException {
@Override
public int read(byte[] b, int off, int len) throws IOException {
- if (b == null) {
- throw new NullPointerException();
- } else if (off < 0 || len < 0 || len > b.length - off) {
- throw new IndexOutOfBoundsException();
+ Objects.requireNonNull(b, "b == null");
+ if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException("Offset=" + off + " and len="
+ + len + " don't match the array length of " + b.length);
} else if (len == 0) {
return 0;
+ } else if (isFinalChunkEncountered) {
+ return -1;
}
int currentOff = off;
int currentLen = len;
@@ -103,7 +153,12 @@ public int read(byte[] b, int off, int len) throws IOException {
}
} else {
remainingData = readContentLengthFromHeader();
- if (remainingData == -1) {
+ if (remainingData == 0) {
+ // there is always a final zero byte chunk so we can stop reading
+ // if we encounter this chunk
+ isFinalChunkEncountered = true;
+ }
+ if (isFinalChunkEncountered || remainingData == -1) {
break;
}
}
@@ -125,10 +180,9 @@ private int readContentLengthFromHeader() throws IOException {
prev = curr;
curr = next;
}
- // Example
- // The chunk data sent:
- // 10000;chunk-signature=b474d8862b1487a5145d686f57f013e54db672cee1c953b3010fb58501ef5aa2
- // <65536-bytes>
+ // Example of a single chunk data:
+ // 10000;chunk-signature=b474d8862b1487a5145d686f57f013e54db672cee1c953b3010fb58501ef5aa2\r\n
+ // <65536-bytes>\r\n
//
// 10000 will be read and decoded from base-16 representation to 65536, which is the size of
// the subsequent chunk payload.
@@ -145,8 +199,4 @@ private int readContentLengthFromHeader() throws IOException {
throw new IOException("Invalid signature line: " + signatureLine);
}
}
-
- private boolean eol(int prev, int curr) {
- return prev == 13 && curr == 10;
- }
}
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/UnsignedChunksInputStream.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/UnsignedChunksInputStream.java
new file mode 100644
index 000000000000..93565b0d820e
--- /dev/null
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/UnsignedChunksInputStream.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3;
+
+import static org.apache.hadoop.ozone.s3.util.S3Utils.eol;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+
+/**
+ * Input stream implementation to read body of an unsigned chunked upload.
+ *
+ * Currently, the only valid value of x-amz-content-sha256 header to indicate
+ * transfer unsigned payload in multiple chunks is STREAMING-UNSIGNED-PAYLOAD-TRAILER.
+ * Therefore, the input stream should work with chunked payloads with checksum trailer.
+ * Nevertheless, this input stream also supports chunked upload without trailer.
+ *
+ *
+ * Example chunk data:
+ *
+ * 10000\r\n
+ * <65536-bytes>\r\n
+ * 0\r\n
+ * x-amz-checksum-crc64nvme:2wstOANdZ/o=\r\n
+ *
+ *
+ *
+ * The 10000 will be read and decoded from base-16 representation to 65536, which is the size of
+ * the subsequent chunk payload. Each chunk upload ends with a zero-byte final additional chunk.
+ * At the end, there will be a trailer checksum payload
+ *
+ *
+ *
+ * The logic is similar to {@link SignedChunksInputStream}, but since it is an unsigned chunked upload
+ * there is no "chunk-signature" to parse.
+ *
+ *
+ *
+ * Note that there is not actual trailer checksum verification taking place. The InputStream only
+ * returns the actual chunk payload from chunked signatures format.
+ *
+ *
+ * Reference:
+ *
+ */
+public class UnsignedChunksInputStream extends InputStream {
+
+ private final InputStream originalStream;
+
+ /**
+ * Size of the chunk payload. If zero, the content length should be parsed to
+ * retrieve the subsequent chunk payload size.
+ */
+ private int remainingData = 0;
+
+ /**
+ * Every chunked uploads (multiple chunks) contains an additional final zero-byte
+ * chunk. This can be used as the end-of-file marker.
+ */
+ private boolean isFinalChunkEncountered = false;
+
+ public UnsignedChunksInputStream(InputStream inputStream) {
+ originalStream = inputStream;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (isFinalChunkEncountered) {
+ return -1;
+ }
+ if (remainingData > 0) {
+ int curr = originalStream.read();
+ remainingData--;
+ if (remainingData == 0) {
+ //read the "\r\n" at the end of the data section
+ originalStream.read();
+ originalStream.read();
+ }
+ return curr;
+ } else {
+ remainingData = readContentLengthFromHeader();
+ if (remainingData <= 0) {
+ // since currently trailer checksum verification is not supported, we can
+ // stop reading after encountering the final zero-byte chunk.
+ isFinalChunkEncountered = true;
+ return -1;
+ }
+ return read();
+ }
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ Objects.requireNonNull(b, "b == null");
+ if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException("Offset=" + off + " and len="
+ + len + " don't match the array length of " + b.length);
+ } else if (len == 0) {
+ return 0;
+ } else if (isFinalChunkEncountered) {
+ return -1;
+ }
+ int currentOff = off;
+ int currentLen = len;
+ int totalReadBytes = 0;
+ int realReadLen = 0;
+ int maxReadLen = 0;
+ do {
+ if (remainingData > 0) {
+ // The chunk payload size has been decoded, now read the actual chunk payload
+ maxReadLen = Math.min(remainingData, currentLen);
+ realReadLen = originalStream.read(b, currentOff, maxReadLen);
+ if (realReadLen == -1) {
+ break;
+ }
+ currentOff += realReadLen;
+ currentLen -= realReadLen;
+ totalReadBytes += realReadLen;
+ remainingData -= realReadLen;
+ if (remainingData == 0) {
+ //read the "\r\n" at the end of the data section
+ originalStream.read();
+ originalStream.read();
+ }
+ } else {
+ remainingData = readContentLengthFromHeader();
+ if (remainingData == 0) {
+ // there is always a final zero byte chunk so we can stop reading
+ // if we encounter this chunk
+ isFinalChunkEncountered = true;
+ }
+ if (isFinalChunkEncountered || remainingData == -1) {
+ break;
+ }
+ }
+ } while (currentLen > 0);
+ return totalReadBytes > 0 ? totalReadBytes : -1;
+ }
+
+ private int readContentLengthFromHeader() throws IOException {
+ int prev = -1;
+ int curr = 0;
+ StringBuilder buf = new StringBuilder();
+
+ //read everything until the next \r\n
+ while (!eol(prev, curr) && curr != -1) {
+ int next = originalStream.read();
+ if (next != -1) {
+ buf.append((char) next);
+ }
+ prev = curr;
+ curr = next;
+ }
+ // Example of a single chunk data:
+ // 10000\r\n
+ // <65536-bytes>\r\n
+ //
+ // 10000 will be read and decoded from base-16 representation to 65536, which is the size of
+ // the subsequent chunk payload.
+ String readString = buf.toString().trim();
+ if (readString.isEmpty()) {
+ return -1;
+ }
+ return Integer.parseInt(readString, 16);
+ }
+}
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
index bf3d0c59928f..45e1f9d34e63 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
@@ -57,7 +57,11 @@
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_COUNT_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_DIRECTIVE_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.hasMultiChunksPayload;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.hasUnsignedPayload;
import static org.apache.hadoop.ozone.s3.util.S3Utils.urlDecode;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.validateMultiChunksUpload;
+import static org.apache.hadoop.ozone.s3.util.S3Utils.validateSignatureHeader;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
@@ -100,6 +104,7 @@
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.bind.DatatypeConverter;
+import net.jcip.annotations.Immutable;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
@@ -126,6 +131,7 @@
import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadCompleteInfo;
import org.apache.hadoop.ozone.s3.HeaderPreprocessor;
import org.apache.hadoop.ozone.s3.SignedChunksInputStream;
+import org.apache.hadoop.ozone.s3.UnsignedChunksInputStream;
import org.apache.hadoop.ozone.s3.endpoint.S3Tagging.Tag;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
@@ -303,17 +309,13 @@ public Response put(
}
// Normal put object
+ S3ChunkInputStreamInfo chunkInputStreamInfo = getS3ChunkInputStreamInfo(body,
+ length, amzDecodedLength, keyPath);
+ digestInputStream = chunkInputStreamInfo.getDigestInputStream();
+ length = chunkInputStreamInfo.getEffectiveLength();
+
Map customMetadata =
getCustomMetadataFromHeaders(headers.getRequestHeaders());
-
- if (S3Utils.hasSignedPayloadHeader(headers)) {
- digestInputStream = new DigestInputStream(new SignedChunksInputStream(body),
- getMessageDigestInstance());
- length = Long.parseLong(amzDecodedLength);
- } else {
- digestInputStream = new DigestInputStream(body, getMessageDigestInstance());
- }
-
Map tags = getTaggingFromHeaders(headers);
long putLength;
@@ -962,15 +964,11 @@ private Response createMultipartKey(OzoneVolume volume, String bucket,
String copyHeader = null;
DigestInputStream digestInputStream = null;
try {
-
- if (S3Utils.hasSignedPayloadHeader(headers)) {
- digestInputStream = new DigestInputStream(new SignedChunksInputStream(body),
- getMessageDigestInstance());
- length = Long.parseLong(
- headers.getHeaderString(DECODED_CONTENT_LENGTH_HEADER));
- } else {
- digestInputStream = new DigestInputStream(body, getMessageDigestInstance());
- }
+ String amzDecodedLength = headers.getHeaderString(DECODED_CONTENT_LENGTH_HEADER);
+ S3ChunkInputStreamInfo chunkInputStreamInfo = getS3ChunkInputStreamInfo(
+ body, length, amzDecodedLength, key);
+ digestInputStream = chunkInputStreamInfo.getDigestInputStream();
+ length = chunkInputStreamInfo.getEffectiveLength();
copyHeader = headers.getHeaderString(COPY_SOURCE_HEADER);
String storageType = headers.getHeaderString(STORAGE_CLASS_HEADER);
@@ -1536,4 +1534,54 @@ private int getIOBufferSize(long fileLength) {
return fileLength < bufferSize ? (int) fileLength : bufferSize;
}
}
+
+ /**
+ * Create a {@link S3ChunkInputStreamInfo} that contains the necessary information to handle
+ * the S3 chunk upload.
+ */
+ private S3ChunkInputStreamInfo getS3ChunkInputStreamInfo(
+ InputStream body, long contentLength, String amzDecodedLength, String keyPath) throws OS3Exception {
+ final String amzContentSha256Header = validateSignatureHeader(headers, keyPath);
+ final InputStream chunkInputStream;
+ final long effectiveLength;
+ if (hasMultiChunksPayload(amzContentSha256Header)) {
+ validateMultiChunksUpload(headers, amzDecodedLength, keyPath);
+ if (hasUnsignedPayload(amzContentSha256Header)) {
+ chunkInputStream = new UnsignedChunksInputStream(body);
+ } else {
+ chunkInputStream = new SignedChunksInputStream(body);
+ }
+ effectiveLength = Long.parseLong(amzDecodedLength);
+ } else {
+ // Single chunk upload: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ // Possible x-amz-content-sha256 header values
+ // - Actual payload checksum value: For signed payload
+ // - UNSIGNED-PAYLOAD: For unsigned payload
+ chunkInputStream = body;
+ effectiveLength = contentLength;
+ }
+
+ // DigestInputStream is used for ETag calculation
+ DigestInputStream digestInputStream = new DigestInputStream(chunkInputStream, getMessageDigestInstance());
+ return new S3ChunkInputStreamInfo(digestInputStream, effectiveLength);
+ }
+
+ @Immutable
+ static final class S3ChunkInputStreamInfo {
+ private final DigestInputStream digestInputStream;
+ private final long effectiveLength;
+
+ S3ChunkInputStreamInfo(DigestInputStream digestInputStream, long effectiveLength) {
+ this.digestInputStream = digestInputStream;
+ this.effectiveLength = effectiveLength;
+ }
+
+ public DigestInputStream getDigestInputStream() {
+ return digestInputStream;
+ }
+
+ public long getEffectiveLength() {
+ return effectiveLength;
+ }
+ }
}
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
index 5a474a024e29..ea460f62d494 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
@@ -19,7 +19,6 @@
import static java.time.temporal.ChronoUnit.SECONDS;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR;
-import static org.apache.hadoop.ozone.s3.util.S3Consts.STREAMING_UNSIGNED_PAYLOAD_TRAILER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.UNSIGNED_PAYLOAD;
import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
@@ -201,31 +200,45 @@ public static String buildCanonicalRequest(
validateCanonicalHeaders(canonicalHeaders.toString(), headers,
unsignedPayload);
- String payloadHash;
- if (UNSIGNED_PAYLOAD.equals(headers.get(X_AMZ_CONTENT_SHA256)) ||
- STREAMING_UNSIGNED_PAYLOAD_TRAILER.equals(headers.get(X_AMZ_CONTENT_SHA256)) ||
- unsignedPayload) {
- payloadHash = UNSIGNED_PAYLOAD;
- } else {
- // According to AWS Sig V4 documentation
- // https://docs.aws.amazon.com/AmazonS3/latest/API/
- // sig-v4-header-based-auth.html
- // Note: The x-amz-content-sha256 header is required
- // for all AWS Signature Version 4 requests.(using Authorization header)
- if (!headers.containsKey(X_AMZ_CONTENT_SHA256)) {
- LOG.error("The request must include " + X_AMZ_CONTENT_SHA256
- + " header for signed payload");
- throw S3_AUTHINFO_CREATION_ERROR;
- }
- payloadHash = headers.get(X_AMZ_CONTENT_SHA256);
- }
- String canonicalRequest = method + NEWLINE
+ String payloadHash = getPayloadHash(headers, unsignedPayload);
+
+ return method + NEWLINE
+ canonicalUri + NEWLINE
+ canonicalQueryStr + NEWLINE
+ canonicalHeaders + NEWLINE
+ signedHeaders + NEWLINE
+ payloadHash;
- return canonicalRequest;
+ }
+
+ private static String getPayloadHash(Map headers, boolean isUsingQueryParameter)
+ throws OS3Exception {
+ if (isUsingQueryParameter) {
+ // According to AWS Signature V4 documentation using Query Parameters
+ // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
+ return UNSIGNED_PAYLOAD;
+ }
+ String contentSignatureHeaderValue = headers.get(X_AMZ_CONTENT_SHA256);
+ // According to AWS Signature V4 documentation using Authorization Header
+ // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ // The x-amz-content-sha256 header is required
+ // for all AWS Signature Version 4 requests using Authorization header.
+ if (contentSignatureHeaderValue == null) {
+ LOG.error("The request must include " + X_AMZ_CONTENT_SHA256
+ + " header for signed payload");
+ throw S3_AUTHINFO_CREATION_ERROR;
+ }
+ // Simply return the header value of x-amz-content-sha256 as the payload hash
+ // These are the possible cases:
+ // 1. Actual payload checksum for single chunk upload
+ // 2. Unsigned payloads for multiple chunks upload
+ // - UNSIGNED-PAYLOAD
+ // - STREAMING-UNSIGNED-PAYLOAD-TRAILER
+ // 3. Signed payloads for multiple chunks upload
+ // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD
+ // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER
+ // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD
+ // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER
+ return contentSignatureHeaderValue;
}
/**
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
index 1060f2568c80..e5f49383fcc3 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java
@@ -32,8 +32,8 @@ public final class S3Consts {
public static final String STORAGE_CLASS_HEADER = "x-amz-storage-class";
public static final String ENCODING_TYPE = "url";
- // Constants related to Signature calculation
- // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
+ // Constants related to AWS Signature Version V4 calculation
+ // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
public static final String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256";
public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
@@ -45,6 +45,9 @@ public final class S3Consts {
public static final String STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER =
"STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER";
+ public static final String AWS_CHUNKED = "aws-chunked";
+ public static final String MULTI_CHUNKS_UPLOAD_PREFIX = "STREAMING";
+
// Constants related to Range Header
public static final String COPY_SOURCE_IF_PREFIX = "x-amz-copy-source-if-";
public static final String COPY_SOURCE_IF_MODIFIED_SINCE =
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java
index a99bfca73721..2dd9f497d099 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Utils.java
@@ -20,15 +20,19 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_ARGUMENT;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.newError;
-import static org.apache.hadoop.ozone.s3.util.S3Consts.STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD;
-import static org.apache.hadoop.ozone.s3.util.S3Consts.STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER;
-import static org.apache.hadoop.ozone.s3.util.S3Consts.STREAMING_AWS4_HMAC_SHA256_PAYLOAD;
-import static org.apache.hadoop.ozone.s3.util.S3Consts.STREAMING_AWS4_HMAC_SHA256_PAYLOAD_TRAILER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.AWS_CHUNKED;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.DECODED_CONTENT_LENGTH_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.MULTI_CHUNKS_UPLOAD_PREFIX;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.STREAMING_UNSIGNED_PAYLOAD_TRAILER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.UNSIGNED_PAYLOAD;
import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
+import jakarta.annotation.Nonnull;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Objects;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@@ -37,6 +41,7 @@
import org.apache.hadoop.hdds.client.ReplicationType;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
/**
* Utilities.
@@ -131,16 +136,77 @@ public static WebApplicationException wrapOS3Exception(OS3Exception ex) {
.build());
}
- public static boolean hasSignedPayloadHeader(HttpHeaders headers) {
- final String signingAlgorithm = headers.getHeaderString(X_AMZ_CONTENT_SHA256);
- if (signingAlgorithm == null) {
- return false;
+ public static boolean hasUnsignedPayload(@Nonnull String amzContentSha256Header) {
+ Objects.requireNonNull(amzContentSha256Header);
+ return amzContentSha256Header.equals(UNSIGNED_PAYLOAD) ||
+ amzContentSha256Header.equals(STREAMING_UNSIGNED_PAYLOAD_TRAILER);
+ }
+
+ public static boolean hasMultiChunksPayload(@Nonnull String amzContentSha256Header) {
+ Objects.requireNonNull(amzContentSha256Header);
+ // Multiple chunk uploads
+ // - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
+ // - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming-trailers.html
+ // Possible values
+ // - STREAMING-UNSIGNED-PAYLOAD-TRAILER
+ // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD
+ // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER
+ // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD
+ // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER
+ // Currently since all the multi chunks values have x-amz-content-sha256 header value that starts
+ // with STREAMING, we can use this prefix to differentiates between multi chunks and single chunks upload.
+ // In the future if there are more multi chunks signature algorithms that has the same prefix,
+ // this function will be able to handle detect it.
+ return amzContentSha256Header.startsWith(MULTI_CHUNKS_UPLOAD_PREFIX);
+ }
+
+ public static void validateMultiChunksUpload(HttpHeaders headers, String amzDecodedContentLength,
+ String resource) throws OS3Exception {
+ final String contentEncoding = headers.getHeaderString(HttpHeaders.CONTENT_ENCODING);
+ // "Content-Encoding : aws-chunked" seems to only be sent for SDK V2, so ignore if there is no
+ // Content-Encoding header
+ if (contentEncoding != null) {
+ // Amazon S3 supports multiple content encoding values for example "Content-Encoding : aws-chunked,gzip"
+ // We are only interested on "aws-chunked"
+ boolean containsAwsChunked = Arrays.stream(contentEncoding.split(","))
+ .map(String::trim)
+ .anyMatch(AWS_CHUNKED::equals);
+ if (!containsAwsChunked) {
+ OS3Exception ex = S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, resource);
+ ex.setErrorMessage("An error occurred (InvalidArgument) for multi chunks upload: " +
+ "The " + HttpHeaders.CONTENT_ENCODING + " header does not contain " + AWS_CHUNKED);
+ throw ex;
+ }
+ }
+
+ if (amzDecodedContentLength == null) {
+ OS3Exception ex = S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, resource);
+ ex.setErrorMessage("An error occurred (InvalidArgument) for multi chunks upload: " +
+ "The " + DECODED_CONTENT_LENGTH_HEADER + " header is not specified");
+ throw ex;
}
+ }
- // Handles both AWS Signature Version 4 (HMAC-256) and AWS Signature Version 4A (ECDSA-P256-SHA256)
- return signingAlgorithm.equals(STREAMING_AWS4_HMAC_SHA256_PAYLOAD) ||
- signingAlgorithm.equals(STREAMING_AWS4_HMAC_SHA256_PAYLOAD_TRAILER) ||
- signingAlgorithm.equals(STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD) ||
- signingAlgorithm.equals(STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD_TRAILER);
+ public static String validateSignatureHeader(HttpHeaders headers, String resource) throws OS3Exception {
+ String xAmzContentSha256Header = headers.getHeaderString(X_AMZ_CONTENT_SHA256);
+ if (xAmzContentSha256Header == null) {
+ OS3Exception ex = S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, resource);
+ ex.setErrorMessage("An error occurred (InvalidArgument): " +
+ "The " + X_AMZ_CONTENT_SHA256 + " header is not specified");
+ throw ex;
+ }
+
+ return xAmzContentSha256Header;
+ }
+
+ /**
+ * Checks if the given pair of bytes represent the end-of-line sequence (\r\n).
+ *
+ * @param prev the previous byte value (should be 13 for '\r')
+ * @param curr the current byte value (should be 10 for '\n')
+ * @return true if the pair forms a CRLF sequence, false otherwise
+ */
+ public static boolean eol(int prev, int curr) {
+ return prev == 13 && curr == 10;
}
}
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestSignedChunksInputStream.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestSignedChunksInputStream.java
index cf4334ab4d40..870684098863 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestSignedChunksInputStream.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestSignedChunksInputStream.java
@@ -27,90 +27,209 @@
import org.junit.jupiter.api.Test;
/**
- * Test input stream parsing with signatures.
+ * Test {@link SignedChunksInputStream}.
*/
public class TestSignedChunksInputStream {
@Test
- public void emptyfile() throws IOException {
- InputStream is = fileContent("0;chunk-signature"
- +
- "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40");
- String result = IOUtils.toString(is, UTF_8);
- assertEquals("", result);
-
- is = fileContent("0;chunk-signature"
- +
- "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r"
- + "\n");
- result = IOUtils.toString(is, UTF_8);
- assertEquals("", result);
+ void testEmptyFile() throws IOException {
+ try (InputStream is = wrapContent("0;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n")) {
+ assertEquals("", IOUtils.toString(is, UTF_8));
+ }
}
@Test
- public void singlechunk() throws IOException {
+ void testEmptyFileWithTrailer() throws IOException {
+ try (InputStream is = wrapContent("0;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ assertEquals("", IOUtils.toString(is, UTF_8));
+ }
+ }
+
+ @Test
+ void testEmptyFileWithoutEnd() throws IOException {
+ try (InputStream is = wrapContent("0;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40")) {
+ assertEquals("", IOUtils.toString(is, UTF_8));
+ }
+ }
+
+ @Test
+ void testSingleChunk() throws IOException {
+ //test simple read()
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890\r\n")) {
+ assertEquals("1234567890", IOUtils.toString(is, UTF_8));
+ }
+
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890\r\n")) {
+ byte[] bytes = new byte[10];
+ IOUtils.read(is, bytes, 0, 10);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890\r\n")) {
+ byte[] bytes = new byte[10];
+ int readLength = IOUtils.read(is, bytes, 0, 10);
+ assertEquals(10, readLength);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+ }
+
+ @Test
+ void testSingleChunkWithTrailer() throws IOException {
//test simple read()
- InputStream is = fileContent("0A;chunk-signature"
- +
- "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r"
- + "\n1234567890\r\n");
- String result = IOUtils.toString(is, UTF_8);
- assertEquals("1234567890", result);
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890\r\n"
+ + "0;chunk-signature=signature\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ assertEquals("1234567890", IOUtils.toString(is, UTF_8));
+ }
+
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890\r\n"
+ + "0;chunk-signature=signature\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ byte[] bytes = new byte[10];
+ IOUtils.read(is, bytes, 0, 10);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890\r\n"
+ + "0;chunk-signature=signature\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ byte[] bytes = new byte[10];
+ int readLength = IOUtils.read(is, bytes, 0, 10);
+ assertEquals(10, readLength);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+ }
+ @Test
+ void testSingleChunkWithoutEnd() throws IOException {
+ //test simple read()
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890")) {
+ assertEquals("1234567890", IOUtils.toString(is, UTF_8));
+ }
//test read(byte[],int,int)
- is = fileContent("0A;chunk-signature"
- +
- "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r"
- + "\n1234567890\r\n");
- byte[] bytes = new byte[10];
- IOUtils.read(is, bytes, 0, 10);
- assertEquals("1234567890",
- new String(bytes, UTF_8));
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890")) {
+ byte[] bytes = new byte[10];
+ IOUtils.read(is, bytes, 0, 10);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0A;chunk-signature"
+ + "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r\n"
+ + "1234567890")) {
+ byte[] bytes = new byte[15];
+ int readLength = IOUtils.read(is, bytes, 0, 15);
+ assertEquals(10, readLength);
+ assertEquals("1234567890", new String(bytes, UTF_8).substring(0, 10));
+ }
}
@Test
- public void singlechunkwithoutend() throws IOException {
+ void testMultiChunks() throws IOException {
//test simple read()
- InputStream is = fileContent("0A;chunk-signature"
- +
- "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r"
- + "\n1234567890");
- String result = IOUtils.toString(is, UTF_8);
- assertEquals("1234567890", result);
+ try (InputStream is = wrapContent("0a;chunk-signature=signature\r\n"
+ + "1234567890\r\n"
+ + "05;chunk-signature=signature\r\n"
+ + "abcde\r\n"
+ + "0;chunk-signature=signature\r\n")) {
+ String result = IOUtils.toString(is, UTF_8);
+ assertEquals("1234567890abcde", result);
+ }
//test read(byte[],int,int)
- is = fileContent("0A;chunk-signature"
- +
- "=23abb2bd920ddeeaac78a63ed808bc59fa6e7d3ef0e356474b82cdc2f8c93c40\r"
- + "\n1234567890");
- byte[] bytes = new byte[10];
- IOUtils.read(is, bytes, 0, 10);
- assertEquals("1234567890",
- new String(bytes, UTF_8));
+ try (InputStream is = wrapContent("0a;chunk-signature=signature\r\n"
+ + "1234567890\r\n"
+ + "05;chunk-signature=signature\r\n"
+ + "abcde\r\n"
+ + "0;chunk-signature=signature\r\n")) {
+ byte[] bytes = new byte[15];
+ IOUtils.read(is, bytes, 0, 15);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0a;chunk-signature=signature\r\n"
+ + "1234567890\r\n"
+ + "05;chunk-signature=signature\r\n"
+ + "abcde\r\n"
+ + "0;chunk-signature=signature\r\n")) {
+ byte[] bytes = new byte[20];
+ int readLength = IOUtils.read(is, bytes, 0, 20);
+ assertEquals(15, readLength);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8).substring(0, 15));
+ }
}
@Test
- public void multichunks() throws IOException {
+ void testMultiChunksWithTrailer() throws Exception {
//test simple read()
- InputStream is = fileContent("0a;chunk-signature=signature\r\n"
+ try (InputStream is = wrapContent("0a;chunk-signature=signature\r\n"
+ "1234567890\r\n"
+ "05;chunk-signature=signature\r\n"
- + "abcde\r\n");
- String result = IOUtils.toString(is, UTF_8);
- assertEquals("1234567890abcde", result);
+ + "abcde\r\n"
+ + "0;chunk-signature=signature\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ String result = IOUtils.toString(is, UTF_8);
+ assertEquals("1234567890abcde", result);
+ }
//test read(byte[],int,int)
- is = fileContent("0a;chunk-signature=signature\r\n"
+ try (InputStream is = wrapContent("0a;chunk-signature=signature\r\n"
+ + "1234567890\r\n"
+ + "05;chunk-signature=signature\r\n"
+ + "abcde\r\n"
+ + "0;chunk-signature=signature\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ byte[] bytes = new byte[15];
+ IOUtils.read(is, bytes, 0, 15);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0a;chunk-signature=signature\r\n"
+ "1234567890\r\n"
+ "05;chunk-signature=signature\r\n"
- + "abcde\r\n");
- byte[] bytes = new byte[15];
- IOUtils.read(is, bytes, 0, 15);
- assertEquals("1234567890abcde",
- new String(bytes, UTF_8));
+ + "abcde\r\n"
+ + "0;chunk-signature=signature\r\n"
+ + "x-amz-checksum-crc32c:sOO8/Q==\r\n"
+ + "x-amz-trailer-signature:63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f\r\n")) {
+ byte[] bytes = new byte[20];
+ int readLength = IOUtils.read(is, bytes, 0, 20);
+ assertEquals(15, readLength);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8).substring(0, 15));
+ }
}
- private InputStream fileContent(String content) {
+ private InputStream wrapContent(String content) {
return new SignedChunksInputStream(
new ByteArrayInputStream(content.getBytes(UTF_8)));
}
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestUnsignedChunkInputStream.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestUnsignedChunkInputStream.java
new file mode 100644
index 000000000000..0f468923cfa3
--- /dev/null
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestUnsignedChunkInputStream.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.s3;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test {@link UnsignedChunksInputStream}.
+ */
+public class TestUnsignedChunkInputStream {
+
+ @Test
+ void testEmptyFile() throws IOException {
+ try (InputStream is = wrapContent("0\r\n")) {
+ assertEquals("", IOUtils.toString(is, UTF_8));
+ }
+ }
+
+ @Test
+ void testEmptyFileWithTrailer() throws IOException {
+ try (InputStream is = wrapContent("0\r\n"
+ + "x-amz-checksum-crc64nvme:AAAAAAAAAAA=\r\n")) {
+ assertEquals("", IOUtils.toString(is, UTF_8));
+ }
+ }
+
+ @Test
+ public void testEmptyFileWithoutEnd() throws IOException {
+ try (InputStream is = wrapContent("0\r\n"
+ + "x-amz-checksum-crc64nvme:AAAAAAAAAAA=")) {
+ assertEquals("", IOUtils.toString(is, UTF_8));
+ }
+ }
+
+ @Test
+ void testSingleChunk() throws IOException {
+ //test simple read()
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0\r\n")) {
+ assertEquals("1234567890", IOUtils.toString(is, UTF_8));
+ }
+
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0\r\n")) {
+ byte[] bytes = new byte[10];
+ IOUtils.read(is, bytes, 0, 10);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0\r\n")) {
+ byte[] bytes = new byte[15];
+ int readLength = IOUtils.read(is, bytes, 0, 15);
+ assertEquals(10, readLength);
+ assertEquals("1234567890", new String(bytes, UTF_8).substring(0, 10));
+ }
+ }
+
+ @Test
+ void testSingleChunkWithTrailer() throws IOException {
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0\r\n"
+ + "x-amz-checksum-crc64nvme:2wstOANdZ/o=\r\n")) {
+ assertEquals("1234567890", IOUtils.toString(is, UTF_8));
+ }
+
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0\r\n"
+ + "x-amz-checksum-crc64nvme:2wstOANdZ/o=\r\n")) {
+ byte[] bytes = new byte[10];
+ IOUtils.read(is, bytes, 0, 10);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0\r\n"
+ + "x-amz-checksum-crc64nvme:2wstOANdZ/o=\r\n")) {
+ byte[] bytes = new byte[15];
+ int readLength = IOUtils.read(is, bytes, 0, 15);
+ assertEquals(10, readLength);
+ assertEquals("1234567890", new String(bytes, UTF_8).substring(0, 10));
+ }
+ }
+
+ @Test
+ void testSingleChunkWithoutEnd() throws IOException {
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0")) {
+ assertEquals("1234567890", IOUtils.toString(is, UTF_8));
+ }
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0")) {
+ byte[] bytes = new byte[10];
+ IOUtils.read(is, bytes, 0, 10);
+ assertEquals("1234567890", new String(bytes, UTF_8));
+ }
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0A\r\n"
+ + "1234567890\r\n"
+ + "0")) {
+ byte[] bytes = new byte[15];
+ int readLength = IOUtils.read(is, bytes, 0, 10);
+ assertEquals(10, readLength);
+ assertEquals("1234567890", new String(bytes, UTF_8).substring(0, 10));
+ }
+ }
+
+ @Test
+ void testMultiChunks() throws IOException {
+ //test simple read()
+ try (InputStream is = wrapContent("0a\r\n"
+ + "1234567890\r\n"
+ + "05\r\n"
+ + "abcde\r\n"
+ + "0\r\n")) {
+ String result = IOUtils.toString(is, UTF_8);
+ assertEquals("1234567890abcde", result);
+ }
+
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0a\r\n"
+ + "1234567890\r\n"
+ + "05\r\n"
+ + "abcde\r\n"
+ + "0\r\n")) {
+ byte[] bytes = new byte[15];
+ IOUtils.read(is, bytes, 0, 15);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0a\r\n"
+ + "1234567890\r\n"
+ + "05\r\n"
+ + "abcde\r\n"
+ + "0\r\n")) {
+ byte[] bytes = new byte[20];
+ int readLength = IOUtils.read(is, bytes, 0, 20);
+ assertEquals(15, readLength);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8).substring(0, 15));
+ }
+ }
+
+ @Test
+ void testMultiChunksWithTrailer() throws IOException {
+ //test simple read()
+ try (InputStream is = wrapContent("0a\r\n"
+ + "1234567890\r\n"
+ + "05\r\n"
+ + "abcde\r\n"
+ + "0\r\n"
+ + "x-amz-checksum-crc64nvme:2wstOANdZ/o=\r\n")) {
+ String result = IOUtils.toString(is, UTF_8);
+ assertEquals("1234567890abcde", result);
+ }
+
+ //test read(byte[],int,int)
+ try (InputStream is = wrapContent("0a\r\n"
+ + "1234567890\r\n"
+ + "05\r\n"
+ + "abcde\r\n"
+ + "0\r\n"
+ + "x-amz-checksum-crc64nvme:2wstOANdZ/o=\n")) {
+ byte[] bytes = new byte[15];
+ IOUtils.read(is, bytes, 0, 15);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8));
+ }
+
+ //test read(byte[],int,int) with length parameter larger than the payload
+ try (InputStream is = wrapContent("0a\r\n"
+ + "1234567890\r\n"
+ + "05\r\n"
+ + "abcde\r\n"
+ + "0\r\n"
+ + "x-amz-checksum-crc64nvme:2wstOANdZ/o=\n")) {
+ byte[] bytes = new byte[20];
+ int readLength = IOUtils.read(is, bytes, 0, 20);
+ assertEquals(15, readLength);
+ assertEquals("1234567890abcde", new String(bytes, UTF_8).substring(0, 15));
+ }
+ }
+
+ private InputStream wrapContent(String content) {
+ return new UnsignedChunksInputStream(
+ new ByteArrayInputStream(content.getBytes(UTF_8)));
+ }
+
+}
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestListParts.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestListParts.java
index 0800b81bb7e9..30be715b5305 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestListParts.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestListParts.java
@@ -19,6 +19,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -52,6 +53,8 @@ public void setUp() throws Exception {
client.getObjectStore().createS3Bucket(OzoneConsts.S3_BUCKET);
HttpHeaders headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
index 46a141df74b5..fde336f48079 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadComplete.java
@@ -20,6 +20,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.hadoop.ozone.s3.util.S3Consts.CUSTOM_METADATA_HEADER_PREFIX;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -64,6 +65,8 @@ public void setUp() throws Exception {
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
rest = EndpointBuilder.newObjectEndpointBuilder()
.setHeaders(headers)
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
index f1321820f15a..5189ddf39aaf 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
@@ -23,6 +23,7 @@
import static org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_IF_MODIFIED_SINCE;
import static org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_IF_UNMODIFIED_SINCE;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
@@ -121,6 +122,8 @@ public static void setUp() throws Exception {
HttpHeaders headers = mock(HttpHeaders.class);
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
REST.setHeaders(headers);
REST.setClient(CLIENT);
@@ -434,6 +437,8 @@ private void setHeaders(Map additionalHeaders) {
HttpHeaders headers = mock(HttpHeaders.class);
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
additionalHeaders
.forEach((k, v) -> when(headers.getHeaderString(k)).thenReturn(v));
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
index 9bf5f27ddd4b..3e772f8b8bf7 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectGet.java
@@ -23,6 +23,7 @@
import static org.apache.hadoop.ozone.s3.util.S3Consts.RANGE_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_COUNT_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -83,6 +84,7 @@ public void init() throws OS3Exception, IOException {
client.getObjectStore().createS3Bucket(BUCKET_NAME);
headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
rest = EndpointBuilder.newObjectEndpointBuilder()
.setClient(client)
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
index 4f22bd418447..673fb6a75635 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
@@ -30,6 +30,7 @@
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_KEY_LENGTH_LIMIT;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_NUM_LIMIT;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_VALUE_LENGTH_LIMIT;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.apache.hadoop.ozone.s3.util.S3Utils.urlEncode;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -127,6 +128,7 @@ void setup() throws IOException {
clientStub.getObjectStore().createS3Bucket(DEST_BUCKET_NAME);
headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
// Create PutObject and setClient to OzoneClientStub
objectEndpoint = EndpointBuilder.newObjectEndpointBuilder()
@@ -208,6 +210,7 @@ void testPutObjectContentLengthForStreaming()
@Test
public void testPutObjectWithTags() throws IOException, OS3Exception {
HttpHeaders headersWithTags = Mockito.mock(HttpHeaders.class);
+ when(headersWithTags.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
when(headersWithTags.getHeaderString(TAG_HEADER)).thenReturn("tag1=value1&tag2=value2");
ByteArrayInputStream body =
@@ -232,6 +235,7 @@ public void testPutObjectWithOnlyTagKey() throws Exception {
ByteArrayInputStream body =
new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
HttpHeaders headerWithOnlyTagKey = Mockito.mock(HttpHeaders.class);
+ when(headerWithOnlyTagKey.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
// Try to send with only the key (no value)
when(headerWithOnlyTagKey.getHeaderString(TAG_HEADER)).thenReturn("tag1");
objectEndpoint.setHeaders(headerWithOnlyTagKey);
@@ -252,6 +256,7 @@ public void testPutObjectWithDuplicateTagKey() throws Exception {
ByteArrayInputStream body =
new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
HttpHeaders headersWithDuplicateTagKey = Mockito.mock(HttpHeaders.class);
+ when(headersWithDuplicateTagKey.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
when(headersWithDuplicateTagKey.getHeaderString(TAG_HEADER)).thenReturn("tag1=value1&tag1=value2");
objectEndpoint.setHeaders(headersWithDuplicateTagKey);
try {
@@ -270,6 +275,7 @@ public void testPutObjectWithLongTagKey() throws Exception {
ByteArrayInputStream body =
new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
HttpHeaders headersWithLongTagKey = Mockito.mock(HttpHeaders.class);
+ when(headersWithLongTagKey.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
String longTagKey = StringUtils.repeat('k', TAG_KEY_LENGTH_LIMIT + 1);
when(headersWithLongTagKey.getHeaderString(TAG_HEADER)).thenReturn(longTagKey + "=value1");
objectEndpoint.setHeaders(headersWithLongTagKey);
@@ -289,6 +295,7 @@ public void testPutObjectWithLongTagValue() throws Exception {
ByteArrayInputStream body =
new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
HttpHeaders headersWithLongTagValue = Mockito.mock(HttpHeaders.class);
+ when(headersWithLongTagValue.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
objectEndpoint.setHeaders(headersWithLongTagValue);
String longTagValue = StringUtils.repeat('v', TAG_VALUE_LENGTH_LIMIT + 1);
when(headersWithLongTagValue.getHeaderString(TAG_HEADER)).thenReturn("tag1=" + longTagValue);
@@ -308,6 +315,7 @@ public void testPutObjectWithTooManyTags() throws Exception {
ByteArrayInputStream body =
new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
HttpHeaders headersWithTooManyTags = Mockito.mock(HttpHeaders.class);
+ when(headersWithTooManyTags.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < TAG_NUM_LIMIT + 1; i++) {
sb.append(String.format("tag%d=value%d", i, i));
@@ -579,6 +587,7 @@ public void testCopyObjectMessageDigestResetDuringException() throws IOException
public void testCopyObjectWithTags() throws IOException, OS3Exception {
// Put object in to source bucket
HttpHeaders headersForPut = Mockito.mock(HttpHeaders.class);
+ when(headersForPut.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
when(headersForPut.getHeaderString(TAG_HEADER)).thenReturn("tag1=value1&tag2=value2");
ByteArrayInputStream body =
new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
@@ -600,6 +609,7 @@ public void testCopyObjectWithTags() throws IOException, OS3Exception {
// Copy object without x-amz-tagging-directive (default to COPY)
String destKey = "key=value/2";
HttpHeaders headersForCopy = Mockito.mock(HttpHeaders.class);
+ when(headersForCopy.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
when(headersForCopy.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
BUCKET_NAME + "/" + urlEncode(sourceKeyName));
@@ -738,6 +748,7 @@ void testDirectoryCreationOverFile() throws IOException, OS3Exception {
@Test
public void testPutEmptyObject() throws IOException, OS3Exception {
HttpHeaders headersWithTags = Mockito.mock(HttpHeaders.class);
+ when(headersWithTags.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
String emptyString = "";
ByteArrayInputStream body = new ByteArrayInputStream(emptyString.getBytes(UTF_8));
objectEndpoint.setHeaders(headersWithTags);
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
index 1d3363487939..04c4bfd65c10 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingDelete.java
@@ -25,6 +25,7 @@
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_BUCKET;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_KEY;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -83,6 +84,8 @@ public void init() throws OS3Exception, IOException {
body = new ByteArrayInputStream(CONTENT.getBytes(UTF_8));
// Create a key with object tags
Mockito.when(headers.getHeaderString(TAG_HEADER)).thenReturn("tag1=value1&tag2=value2");
+ Mockito.when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
rest.put(BUCKET_NAME, KEY_WITH_TAG, CONTENT.length(),
1, null, null, null, body);
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingGet.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingGet.java
index 8b5ffcb3b2c1..c4eb4c25ff87 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingGet.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingGet.java
@@ -23,6 +23,7 @@
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_BUCKET;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_KEY;
import static org.apache.hadoop.ozone.s3.util.S3Consts.TAG_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
@@ -61,6 +62,8 @@ public void init() throws OS3Exception, IOException {
client.getObjectStore().createS3Bucket(BUCKET_NAME);
HttpHeaders headers = Mockito.mock(HttpHeaders.class);
+ Mockito.when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
rest = EndpointBuilder.newObjectEndpointBuilder()
.setClient(client)
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
index de698116f531..02b71e8772c4 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectTaggingPut.java
@@ -26,6 +26,7 @@
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_BUCKET;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_KEY;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.doThrow;
@@ -75,6 +76,7 @@ void setup() throws IOException, OS3Exception {
clientStub.getObjectStore().createS3Bucket(BUCKET_NAME);
HttpHeaders headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
// Create PutObject and setClient to OzoneClientStub
objectEndpoint = EndpointBuilder.newObjectEndpointBuilder()
@@ -83,7 +85,7 @@ void setup() throws IOException, OS3Exception {
.setHeaders(headers)
.build();
-
+
ByteArrayInputStream body =
new ByteArrayInputStream("".getBytes(UTF_8));
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUpload.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUpload.java
index 7a3faf6bd53a..4981069528a8 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUpload.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUpload.java
@@ -21,6 +21,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.hadoop.ozone.s3.util.S3Consts.DECODED_CONTENT_LENGTH_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -71,6 +72,8 @@ public void setUp() throws Exception {
HttpHeaders headers = mock(HttpHeaders.class);
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
rest = EndpointBuilder.newObjectEndpointBuilder()
.setHeaders(headers)
@@ -148,6 +151,8 @@ public void testPartUploadWithIncorrectUploadID() throws Exception {
public void testPartUploadStreamContentLength()
throws IOException, OS3Exception {
HttpHeaders headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
ObjectEndpoint objectEndpoint = EndpointBuilder.newObjectEndpointBuilder()
.setHeaders(headers)
.setClient(client)
@@ -206,6 +211,8 @@ public void testPartUploadMessageDigestResetDuringException() throws IOException
HttpHeaders headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUploadWithStream.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUploadWithStream.java
index 2e3025e0e96b..4b2d8a49efb9 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUploadWithStream.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPartUploadWithStream.java
@@ -20,6 +20,7 @@
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -58,6 +59,8 @@ public void setUp() throws Exception {
HttpHeaders headers = mock(HttpHeaders.class);
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn("STANDARD");
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
OzoneConfiguration conf = new OzoneConfiguration();
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java
index 0e4319d6956b..9b34b6ec86ac 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestPermissionCheck.java
@@ -19,6 +19,7 @@
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -87,6 +88,8 @@ public void setup() {
when(client.getObjectStore()).thenReturn(objectStore);
when(client.getConfiguration()).thenReturn(conf);
headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
clientProtocol = mock(ClientProtocol.class);
S3GatewayMetrics.create(conf);
when(client.getProxy()).thenReturn(clientProtocol);
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUploadWithStream.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUploadWithStream.java
index 1fcb5d8d406d..02026c2ef9ce 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUploadWithStream.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUploadWithStream.java
@@ -21,6 +21,7 @@
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_FS_DATASTREAM_AUTO_THRESHOLD;
import static org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
@@ -69,6 +70,7 @@ public void setUp() throws Exception {
client.getObjectStore().createS3Bucket(S3BUCKET);
HttpHeaders headers = mock(HttpHeaders.class);
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256)).thenReturn("mockSignature");
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn("STANDARD");
OzoneConfiguration conf = new OzoneConfiguration();
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java
index 53a8736bddec..63465ef7552e 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/metrics/TestS3GatewayMetrics.java
@@ -23,6 +23,7 @@
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.BUCKET_ALREADY_EXISTS;
import static org.apache.hadoop.ozone.s3.util.S3Consts.COPY_SOURCE_HEADER;
import static org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import static org.apache.hadoop.ozone.s3.util.S3Utils.urlEncode;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -97,6 +98,8 @@ public void setup() throws Exception {
headers = mock(HttpHeaders.class);
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
+ when(headers.getHeaderString(X_AMZ_CONTENT_SHA256))
+ .thenReturn("mockSignature");
keyEndpoint.setHeaders(headers);
metrics = bucketEndpoint.getMetrics();