diff --git a/src/main/java/org/gaul/s3proxy/AwsHttpHeaders.java b/src/main/java/org/gaul/s3proxy/AwsHttpHeaders.java new file mode 100644 index 0000000..4965344 --- /dev/null +++ b/src/main/java/org/gaul/s3proxy/AwsHttpHeaders.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2018 Andrew Gaul + * + * Licensed 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.gaul.s3proxy; + +final class AwsHttpHeaders { + static final String ACL = "x-amz-acl"; + static final String CONTENT_SHA256 = "x-amz-content-sha256"; + static final String COPY_SOURCE = "x-amz-copy-source"; + static final String COPY_SOURCE_IF_MATCH = "x-amz-copy-source-if-match"; + static final String COPY_SOURCE_IF_MODIFIED_SINCE = + "x-amz-copy-source-if-modified-since"; + static final String COPY_SOURCE_IF_NONE_MATCH = + "x-amz-copy-source-if-none-match"; + static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = + "x-amz-copy-source-if-unmodified-since"; + static final String COPY_SOURCE_RANGE = "x-amz-copy-source-range"; + static final String DATE = "x-amz-date"; + static final String DECODED_CONTENT_LENGTH = + "x-amz-decoded-content-length"; + static final String METADATA_DIRECTIVE = "x-amz-metadata-directive"; + static final String STORAGE_CLASS = "x-amz-storage-class"; + + private AwsHttpHeaders() { + throw new AssertionError("intentionally unimplemented"); + } +} diff --git a/src/main/java/org/gaul/s3proxy/AwsSignature.java b/src/main/java/org/gaul/s3proxy/AwsSignature.java index 4d85885..08e8da1 100644 --- a/src/main/java/org/gaul/s3proxy/AwsSignature.java +++ b/src/main/java/org/gaul/s3proxy/AwsSignature.java @@ -94,7 +94,7 @@ final class AwsSignature { request.getHeaders(headerName)); headerName = headerName.toLowerCase(); if (!headerName.startsWith("x-amz-") || (bothDateHeader && - headerName.equalsIgnoreCase("x-amz-date"))) { + headerName.equalsIgnoreCase(AwsHttpHeaders.DATE))) { continue; } if (headerValues.isEmpty()) { @@ -125,14 +125,14 @@ final class AwsSignature { builder.append(Strings.nullToEmpty(expires)); } else { if (!bothDateHeader) { - if (canonicalizedHeaders.containsKey("x-amz-date")) { + if (canonicalizedHeaders.containsKey(AwsHttpHeaders.DATE)) { builder.append(""); } else { builder.append(request.getHeader(HttpHeaders.DATE)); } } else { - if (!canonicalizedHeaders.containsKey("x-amz-date")) { - builder.append(request.getHeader("x-amz-date")); + if (!canonicalizedHeaders.containsKey(AwsHttpHeaders.DATE)) { + builder.append(request.getHeader(AwsHttpHeaders.DATE)); } else { // panic } @@ -258,7 +258,8 @@ final class AwsSignature { String hashAlgorithm) throws IOException, NoSuchAlgorithmException { String authorizationHeader = request.getHeader("Authorization"); - String xAmzContentSha256 = request.getHeader("x-amz-content-sha256"); + String xAmzContentSha256 = request.getHeader( + AwsHttpHeaders.CONTENT_SHA256); if (xAmzContentSha256 == null) { xAmzContentSha256 = request.getParameter("X-Amz-SignedHeaders"); } @@ -336,7 +337,7 @@ final class AwsSignature { byte[] signingKey = signMessage( "aws4_request".getBytes(StandardCharsets.UTF_8), dateRegionServiceKey, algorithm); - String date = request.getHeader("x-amz-date"); + String date = request.getHeader(AwsHttpHeaders.DATE); if (date == null) { date = request.getParameter("X-Amz-Date"); } diff --git a/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java b/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java index 322a1a4..aeec7c4 100644 --- a/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java +++ b/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java @@ -158,18 +158,18 @@ public class S3ProxyHandler { ); /** All supported x-amz- headers, except for x-amz-meta- user metadata. */ private static final Set SUPPORTED_X_AMZ_HEADERS = ImmutableSet.of( - "x-amz-acl", - "x-amz-content-sha256", - "x-amz-copy-source", - "x-amz-copy-source-if-match", - "x-amz-copy-source-if-modified-since", - "x-amz-copy-source-if-none-match", - "x-amz-copy-source-if-unmodified-since", - "x-amz-copy-source-range", - "x-amz-date", - "x-amz-decoded-content-length", - "x-amz-metadata-directive", - "x-amz-storage-class" + AwsHttpHeaders.ACL, + AwsHttpHeaders.CONTENT_SHA256, + AwsHttpHeaders.COPY_SOURCE, + AwsHttpHeaders.COPY_SOURCE_IF_MATCH, + AwsHttpHeaders.COPY_SOURCE_IF_MODIFIED_SINCE, + AwsHttpHeaders.COPY_SOURCE_IF_NONE_MATCH, + AwsHttpHeaders.COPY_SOURCE_IF_UNMODIFIED_SINCE, + AwsHttpHeaders.COPY_SOURCE_RANGE, + AwsHttpHeaders.DATE, + AwsHttpHeaders.DECODED_CONTENT_LENGTH, + AwsHttpHeaders.METADATA_DIRECTIVE, + AwsHttpHeaders.STORAGE_CLASS ); private static final Set CANNED_ACLS = ImmutableSet.of( "private", @@ -307,11 +307,11 @@ public class S3ProxyHandler { } if (headerName.equalsIgnoreCase(HttpHeaders.DATE)) { hasDateHeader = true; - } else if (headerName.equalsIgnoreCase("x-amz-date")) { + } else if (headerName.equalsIgnoreCase(AwsHttpHeaders.DATE)) { logger.debug("have the x-amz-date heaer {}", headerName); // why x-amz-date name exist,but value is null? - if ("".equals(request.getHeader("x-amz-date")) || - request.getHeader("x-amz-date") == null) { + if ("".equals(request.getHeader(AwsHttpHeaders.DATE)) || + request.getHeader(AwsHttpHeaders.DATE) == null) { logger.debug("have empty x-amz-date"); } else { hasXAmzDateHeader = true; @@ -421,14 +421,15 @@ public class S3ProxyHandler { if (hasXAmzDateHeader) { //format diff between v2 and v4 if (finalAuthType == AuthenticationType.AWS_V2) { - dateSkew = request.getDateHeader("x-amz-date"); + dateSkew = request.getDateHeader(AwsHttpHeaders.DATE); dateSkew /= 1000; //case sensetive? } else if (finalAuthType == AuthenticationType.AWS_V4) { logger.debug("into process v4 {}", - request.getHeader("x-amz-date")); + request.getHeader(AwsHttpHeaders.DATE)); - dateSkew = parseIso8601(request.getHeader("x-amz-date")); + dateSkew = parseIso8601(request.getHeader( + AwsHttpHeaders.DATE)); } } else if (request.getParameter("X-Amz-Date") != null) { // v4 query String dateString = request.getParameter("X-Amz-Date"); @@ -464,7 +465,8 @@ public class S3ProxyHandler { path.length > 2 ? path[2] : null); if (anonymousIdentity) { blobStore = provider.getValue(); - String contentSha256 = request.getHeader("x-amz-content-sha256"); + String contentSha256 = request.getHeader( + AwsHttpHeaders.CONTENT_SHA256); if ("STREAMING-AWS4-HMAC-SHA256-PAYLOAD".equals(contentSha256)) { is = new ChunkedInputStream(is); } @@ -538,7 +540,7 @@ public class S3ProxyHandler { haveBothDateHeader); } else { String contentSha256 = request.getHeader( - "x-amz-content-sha256"); + AwsHttpHeaders.CONTENT_SHA256); try { byte[] payload; if (request.getParameter("X-Amz-Algorithm") != null) { @@ -606,7 +608,7 @@ public class S3ProxyHandler { if (!headerName.startsWith("x-amz-")) { continue; } - if (headerName.startsWith("x-amz-meta-")) { + if (headerName.startsWith(USER_METADATA_PREFIX)) { continue; } if (!SUPPORTED_X_AMZ_HEADERS.contains(headerName.toLowerCase())) { @@ -708,7 +710,7 @@ public class S3ProxyHandler { path[1]); return; } else if (uploadId != null) { - if (request.getHeader("x-amz-copy-source") != null) { + if (request.getHeader(AwsHttpHeaders.COPY_SOURCE) != null) { handleCopyPart(request, response, blobStore, path[1], path[2], uploadId); } else { @@ -716,7 +718,7 @@ public class S3ProxyHandler { path[2], uploadId); } return; - } else if (request.getHeader("x-amz-copy-source") != null) { + } else if (request.getHeader(AwsHttpHeaders.COPY_SOURCE) != null) { handleCopyBlob(request, response, is, blobStore, path[1], path[2]); return; @@ -902,7 +904,7 @@ public class S3ProxyHandler { String containerName) throws IOException, S3Exception { ContainerAccess access; - String cannedAcl = request.getHeader("x-amz-acl"); + String cannedAcl = request.getHeader(AwsHttpHeaders.ACL); if (cannedAcl == null || "private".equalsIgnoreCase(cannedAcl)) { access = ContainerAccess.PRIVATE; } else if ("public-read".equalsIgnoreCase(cannedAcl)) { @@ -1001,7 +1003,7 @@ public class S3ProxyHandler { throws IOException, S3Exception { BlobAccess access; - String cannedAcl = request.getHeader("x-amz-acl"); + String cannedAcl = request.getHeader(AwsHttpHeaders.ACL); if (cannedAcl == null || "private".equalsIgnoreCase(cannedAcl)) { access = BlobAccess.PRIVATE; } else if ("public-read".equalsIgnoreCase(cannedAcl)) { @@ -1250,7 +1252,7 @@ public class S3ProxyHandler { logger.debug("Creating bucket with location: {}", location); CreateContainerOptions options = new CreateContainerOptions(); - String acl = request.getHeader("x-amz-acl"); + String acl = request.getHeader(AwsHttpHeaders.ACL); if ("public-read".equalsIgnoreCase(acl)) { options.publicRead(); } @@ -1715,7 +1717,7 @@ public class S3ProxyHandler { HttpServletResponse response, InputStream is, BlobStore blobStore, String destContainerName, String destBlobName) throws IOException, S3Exception { - String copySourceHeader = request.getHeader("x-amz-copy-source"); + String copySourceHeader = request.getHeader(AwsHttpHeaders.COPY_SOURCE); copySourceHeader = URLDecoder.decode(copySourceHeader, UTF_8); if (copySourceHeader.startsWith("/")) { // Some clients like boto do not include the leading slash @@ -1728,7 +1730,7 @@ public class S3ProxyHandler { String sourceContainerName = path[0]; String sourceBlobName = path[1]; boolean replaceMetadata = "REPLACE".equalsIgnoreCase(request.getHeader( - "x-amz-metadata-directive")); + AwsHttpHeaders.METADATA_DIRECTIVE)); if (sourceContainerName.equals(destContainerName) && sourceBlobName.equals(destBlobName) && @@ -1738,22 +1740,22 @@ public class S3ProxyHandler { CopyOptions.Builder options = CopyOptions.builder(); - String ifMatch = request.getHeader("x-amz-copy-source-if-match"); + String ifMatch = request.getHeader(AwsHttpHeaders.COPY_SOURCE_IF_MATCH); if (ifMatch != null) { options.ifMatch(ifMatch); } String ifNoneMatch = request.getHeader( - "x-amz-copy-source-if-none-match"); + AwsHttpHeaders.COPY_SOURCE_IF_NONE_MATCH); if (ifNoneMatch != null) { options.ifNoneMatch(ifNoneMatch); } long ifModifiedSince = request.getDateHeader( - "x-amz-copy-source-if-modified-since"); + AwsHttpHeaders.COPY_SOURCE_IF_MODIFIED_SINCE); if (ifModifiedSince != -1) { options.ifModifiedSince(new Date(ifModifiedSince)); } long ifUnmodifiedSince = request.getDateHeader( - "x-amz-copy-source-if-unmodified-since"); + AwsHttpHeaders.COPY_SOURCE_IF_UNMODIFIED_SINCE); if (ifUnmodifiedSince != -1) { options.ifUnmodifiedSince(new Date(ifUnmodifiedSince)); } @@ -1804,7 +1806,7 @@ public class S3ProxyHandler { } // TODO: jclouds should include this in CopyOptions - String cannedAcl = request.getHeader("x-amz-acl"); + String cannedAcl = request.getHeader(AwsHttpHeaders.ACL); if (cannedAcl != null && !cannedAcl.equalsIgnoreCase("private")) { handleSetBlobAcl(request, response, is, blobStore, destContainerName, destBlobName); @@ -1847,7 +1849,7 @@ public class S3ProxyHandler { if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { contentLengthString = headerValue; } else if (headerName.equalsIgnoreCase( - "x-amz-decoded-content-length")) { + AwsHttpHeaders.DECODED_CONTENT_LENGTH)) { decodedContentLengthString = headerValue; } else if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_MD5)) { contentMD5String = headerValue; @@ -1884,7 +1886,7 @@ public class S3ProxyHandler { } BlobAccess access; - String cannedAcl = request.getHeader("x-amz-acl"); + String cannedAcl = request.getHeader(AwsHttpHeaders.ACL); if (cannedAcl == null || cannedAcl.equalsIgnoreCase("private")) { access = BlobAccess.PRIVATE; } else if (cannedAcl.equalsIgnoreCase("public-read")) { @@ -1910,7 +1912,7 @@ public class S3ProxyHandler { .payload(is) .contentLength(contentLength); - String storageClass = request.getHeader("x-amz-storage-class"); + String storageClass = request.getHeader(AwsHttpHeaders.STORAGE_CLASS); if (storageClass == null || storageClass.equalsIgnoreCase("STANDARD")) { // defaults to STANDARD } else { @@ -2118,7 +2120,7 @@ public class S3ProxyHandler { addContentMetdataFromHttpRequest(builder, request); builder.contentLength(payload.size()); - String storageClass = request.getHeader("x-amz-storage-class"); + String storageClass = request.getHeader(AwsHttpHeaders.STORAGE_CLASS); if (storageClass == null || storageClass.equalsIgnoreCase("STANDARD")) { // defaults to STANDARD } else { @@ -2126,7 +2128,7 @@ public class S3ProxyHandler { } BlobAccess access; - String cannedAcl = request.getHeader("x-amz-acl"); + String cannedAcl = request.getHeader(AwsHttpHeaders.ACL); if (cannedAcl == null || cannedAcl.equalsIgnoreCase("private")) { access = BlobAccess.PRIVATE; } else if (cannedAcl.equalsIgnoreCase("public-read")) { @@ -2428,7 +2430,7 @@ public class S3ProxyHandler { String containerName, String blobName, String uploadId) throws IOException, S3Exception { // TODO: duplicated from handlePutBlob - String copySourceHeader = request.getHeader("x-amz-copy-source"); + String copySourceHeader = request.getHeader(AwsHttpHeaders.COPY_SOURCE); copySourceHeader = URLDecoder.decode(copySourceHeader, UTF_8); if (copySourceHeader.startsWith("/")) { // Some clients like boto do not include the leading slash @@ -2442,7 +2444,7 @@ public class S3ProxyHandler { String sourceBlobName = path[1]; GetOptions options = new GetOptions(); - String range = request.getHeader("x-amz-copy-source-range"); + String range = request.getHeader(AwsHttpHeaders.COPY_SOURCE_RANGE); long expectedSize = -1; if (range != null && range.startsWith("bytes=") && // ignore multiple ranges @@ -2501,13 +2503,13 @@ public class S3ProxyHandler { } String ifMatch = request.getHeader( - "x-amz-copy-source-if-match"); + AwsHttpHeaders.COPY_SOURCE_IF_MATCH); String ifNoneMatch = request.getHeader( - "x-amz-copy-source-if-none-match"); + AwsHttpHeaders.COPY_SOURCE_IF_NONE_MATCH); long ifModifiedSince = request.getDateHeader( - "x-amz-copy-source-if-modified-since"); + AwsHttpHeaders.COPY_SOURCE_IF_MODIFIED_SINCE); long ifUnmodifiedSince = request.getDateHeader( - "x-amz-copy-source-if-unmodified-since"); + AwsHttpHeaders.COPY_SOURCE_IF_UNMODIFIED_SINCE); String eTag = blobMetadata.getETag(); if (eTag != null) { eTag = maybeQuoteETag(eTag); @@ -2602,7 +2604,7 @@ public class S3ProxyHandler { if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { contentLengthString = headerValue; } else if (headerName.equalsIgnoreCase( - "x-amz-decoded-content-length")) { + AwsHttpHeaders.DECODED_CONTENT_LENGTH)) { decodedContentLengthString = headerValue; } else if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_MD5)) { contentMD5String = headerValue; @@ -2763,7 +2765,7 @@ public class S3ProxyHandler { metadata.getLastModified().getTime()); Tier tier = metadata.getTier(); if (tier != null) { - response.addHeader("x-amz-storage-class", + response.addHeader(AwsHttpHeaders.STORAGE_CLASS, StorageClass.fromTier(tier).toString()); } for (Map.Entry entry :