diff --git a/apis/s3/src/main/java/org/jclouds/s3/S3PropertiesBuilder.java b/apis/s3/src/main/java/org/jclouds/s3/S3PropertiesBuilder.java index 2f94673283..f3467fa21b 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/S3PropertiesBuilder.java +++ b/apis/s3/src/main/java/org/jclouds/s3/S3PropertiesBuilder.java @@ -32,6 +32,7 @@ import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCK import java.util.Properties; import org.jclouds.PropertiesBuilder; +import org.jclouds.s3.reference.S3Headers; /** * Builds properties used in S3 Connections @@ -44,7 +45,7 @@ public class S3PropertiesBuilder extends PropertiesBuilder { Properties properties = super.defaultProperties(); properties.setProperty(PROPERTY_API_VERSION, S3AsyncClient.VERSION); properties.setProperty(PROPERTY_AUTH_TAG, "AWS"); - properties.setProperty(PROPERTY_HEADER_TAG, "amz"); + properties.setProperty(PROPERTY_HEADER_TAG, S3Headers.DEFAULT_AMAZON_HEADERTAG); properties.setProperty(PROPERTY_S3_SERVICE_PATH, "/"); properties.setProperty(PROPERTY_S3_VIRTUAL_HOST_BUCKETS, "true"); properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true"); @@ -67,8 +68,8 @@ public class S3PropertiesBuilder extends PropertiesBuilder { protected void setMetaPrefix() { if (properties.getProperty(PROPERTY_USER_METADATA_PREFIX) == null) { - properties.setProperty(PROPERTY_USER_METADATA_PREFIX, - String.format("x-%s-meta-", properties.getProperty(PROPERTY_HEADER_TAG))); + properties.setProperty(PROPERTY_USER_METADATA_PREFIX, String.format("x-%s-meta-", properties + .getProperty(PROPERTY_HEADER_TAG))); } } diff --git a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java index 36d983e095..1c2123d51c 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java +++ b/apis/s3/src/main/java/org/jclouds/s3/filters/RequestAuthorizeSignature.java @@ -20,13 +20,17 @@ package org.jclouds.s3.filters; import static com.google.common.base.Preconditions.checkArgument; - +import static com.google.common.collect.Iterables.any; +import static com.google.common.collect.Iterables.get; import static org.jclouds.Constants.PROPERTY_CREDENTIAL; import static org.jclouds.Constants.PROPERTY_IDENTITY; import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_TAG; import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; +import static org.jclouds.http.utils.ModifyRequest.parseQueryToMap; +import static org.jclouds.http.utils.ModifyRequest.replaceHeader; import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_SERVICE_PATH; import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS; +import static org.jclouds.util.Strings2.toInputStream; import java.lang.annotation.Annotation; import java.util.Arrays; @@ -43,8 +47,6 @@ import javax.inject.Singleton; import javax.ws.rs.core.HttpHeaders; import org.jclouds.Constants; -import org.jclouds.s3.Bucket; -import org.jclouds.s3.reference.S3Headers; import org.jclouds.crypto.Crypto; import org.jclouds.crypto.CryptoStreams; import org.jclouds.date.TimeStamp; @@ -53,43 +55,44 @@ import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpUtils; import org.jclouds.http.internal.SignatureWire; -import org.jclouds.http.utils.ModifyRequest; import org.jclouds.io.InputSuppliers; import org.jclouds.logging.Logger; import org.jclouds.rest.RequestSigner; import org.jclouds.rest.internal.GeneratedHttpRequest; -import org.jclouds.util.Strings2; +import org.jclouds.s3.Bucket; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; +import com.google.common.collect.Ordering; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; /** * Signs the S3 request. * - * @see + * @see * @author Adrian Cole * */ @Singleton public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSigner { + private static final Predicate ANNOTATIONTYPE_BUCKET = new Predicate() { + public boolean apply(Annotation input) { + return input.annotationType().equals(Bucket.class); + } + }; + private final String[] firstHeadersToSign = new String[] { HttpHeaders.DATE }; - /** Prefix for general Amazon headers: x-amz- */ - public static final String AMAZON_PREFIX = "x-amz-"; + public static Set SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy", + "requestPayment", "versioning", "versions", "versionId", "notification", "uploadId", "uploads", + "partNumber", "website", "response-content-type", "response-content-language", "response-expires", + "response-cache-control", "response-content-disposition", "response-content-encoding"); - public static Set SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy", "requestPayment", "versioning", - "versions", "versionId", "notification", "uploadId", "uploads", "partNumber", "website", - "response-content-type", "response-content-language", "response-expires", - "response-cache-control", "response-content-disposition", "response-content-encoding"); - private final SignatureWire signatureWire; private final String accessKey; private final String secretKey; @@ -133,31 +136,27 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign } HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) { - request = ModifyRequest.replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":" - + signature); + request = replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":" + signature); return request; } HttpRequest replaceDateHeader(HttpRequest request) { - Builder builder = ImmutableMap.builder(); - String date = timeStampProvider.get(); - builder.put(HttpHeaders.DATE, date); - request = ModifyRequest.replaceHeaders(request, Multimaps.forMap(builder.build())); + request = replaceHeader(request, HttpHeaders.DATE, timeStampProvider.get()); return request; } public String createStringToSign(HttpRequest request) { utils.logRequest(signatureLog, request, ">>"); - SortedSetMultimap canonicalizedHeaders = TreeMultimap.create(); + SortedSetMultimap canonicalizedHeaders = TreeMultimap.create(); StringBuilder buffer = new StringBuilder(); // re-sign the request appendMethod(request, buffer); appendPayloadMetadata(request, buffer); appendHttpHeaders(request, canonicalizedHeaders); - + // Remove default date timestamp if "x-amz-date" is set. - if (canonicalizedHeaders.containsKey(S3Headers.ALTERNATE_DATE)) { - canonicalizedHeaders.put("date", ""); + if (canonicalizedHeaders.containsKey("x-" + headerTag + "-date")) { + canonicalizedHeaders.removeAll("date"); } appendAmzHeaders(canonicalizedHeaders, buffer); @@ -172,7 +171,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign String calculateSignature(String toSign) throws HttpException { String signature = sign(toSign); if (signatureWire.enabled()) - signatureWire.input(Strings2.toInputStream(signature)); + signatureWire.input(toInputStream(signature)); return signature; } @@ -196,35 +195,35 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign for (Entry header : canonicalizedHeaders.entries()) { String key = header.getKey(); if (key.startsWith("x-" + headerTag + "-")) { - toSign.append(String.format("%s: %s\n", key.toLowerCase(), header.getValue())); + toSign.append(String.format("%s:%s\n", key.toLowerCase(), header.getValue())); } } } void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) { - // the following request parameters are positional in their nature + // note that we fall back to headers, and some requests such as ?uploads do not have a + // payload, yet specify payload related parameters buffer.append( - utils.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata() - .getContentMD5())).append("\n"); + request.getPayload() == null ? utils.valueOrEmpty(request.getFirstHeaderOrNull("Content-MD5")) : utils + .valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata() + .getContentMD5())).append("\n"); buffer.append( utils.valueOrEmpty(request.getPayload() == null ? request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE) - : request.getPayload().getContentMetadata().getContentType())).append("\n"); + : request.getPayload().getContentMetadata().getContentType())).append("\n"); for (String header : firstHeadersToSign) buffer.append(valueOrEmpty(request.getHeaders().get(header))).append("\n"); } @VisibleForTesting - void appendHttpHeaders(HttpRequest request, - SortedSetMultimap canonicalizedHeaders) { + void appendHttpHeaders(HttpRequest request, SortedSetMultimap canonicalizedHeaders) { Multimap headers = request.getHeaders(); for (Entry header : headers.entries()) { if (header.getKey() == null) continue; - String key = header.getKey().toString() - .toLowerCase(Locale.getDefault()); + String key = header.getKey().toString().toLowerCase(Locale.getDefault()); // Ignore any headers that are not particularly interesting. if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase("Content-MD5") - || key.equalsIgnoreCase(HttpHeaders.DATE) || key.startsWith(AMAZON_PREFIX)) { + || key.equalsIgnoreCase(HttpHeaders.DATE) || key.startsWith("x-" + headerTag + "-")) { canonicalizedHeaders.put(key, header.getValue()); } } @@ -238,12 +237,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign String bucketName = null; for (int i = 0; i < request.getJavaMethod().getParameterAnnotations().length; i++) { - if (Iterables.any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]), - new Predicate() { - public boolean apply(Annotation input) { - return input.annotationType().equals(Bucket.class); - } - })) { + if (any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]), ANNOTATIONTYPE_BUCKET)) { bucketName = (String) request.getArgs().get(i); break; } @@ -261,20 +255,14 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign // ...however, there are a few exceptions that must be included in the // signed URI. if (request.getEndpoint().getQuery() != null) { - SortedSetMultimap sortedParams = TreeMultimap.create(); - String[] params = request.getEndpoint().getQuery().split("&"); - for (String param : params) { - String[] paramNameAndValue = param.split("="); - sortedParams.put(paramNameAndValue[0], paramNameAndValue.length == 2 ? paramNameAndValue[1] : null); - } + Multimap params = parseQueryToMap(request.getEndpoint().getQuery()); char separator = '?'; - for (Entry param: sortedParams.entries()) { - String paramName = param.getKey(); + for (String paramName : Ordering.natural().sortedCopy(params.keySet())) { // Skip any parameters that aren't part of the canonical signed string - if (SIGNED_PARAMETERS.contains(paramName) == false) continue; - + if (SIGNED_PARAMETERS.contains(paramName) == false) + continue; toSign.append(separator).append(paramName); - String paramValue = param.getValue(); + String paramValue = get(params.get(paramName), 0); if (paramValue != null) { toSign.append("=").append(paramValue); } diff --git a/apis/s3/src/main/java/org/jclouds/s3/functions/ParseObjectMetadataFromHeaders.java b/apis/s3/src/main/java/org/jclouds/s3/functions/ParseObjectMetadataFromHeaders.java index 816e3acaff..a61179d4e8 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/functions/ParseObjectMetadataFromHeaders.java +++ b/apis/s3/src/main/java/org/jclouds/s3/functions/ParseObjectMetadataFromHeaders.java @@ -21,18 +21,21 @@ package org.jclouds.s3.functions; import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import javax.inject.Inject; import javax.inject.Named; import javax.ws.rs.core.HttpHeaders; -import org.jclouds.s3.blobstore.functions.BlobToObjectMetadata; -import org.jclouds.s3.domain.MutableObjectMetadata; import org.jclouds.blobstore.domain.BlobMetadata; import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders; import org.jclouds.crypto.CryptoStreams; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.rest.InvocationContext; +import org.jclouds.s3.blobstore.functions.BlobToObjectMetadata; +import org.jclouds.s3.domain.MutableObjectMetadata; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; @@ -45,19 +48,23 @@ import com.google.common.base.Function; * @author Adrian Cole */ public class ParseObjectMetadataFromHeaders implements Function, - InvocationContext { + InvocationContext { private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser; private final BlobToObjectMetadata blobToObjectMetadata; private final String userMdPrefix; @Inject public ParseObjectMetadataFromHeaders(ParseSystemAndUserMetadataFromHeaders blobMetadataParser, - BlobToObjectMetadata blobToObjectMetadata, @Named(PROPERTY_USER_METADATA_PREFIX) String userMdPrefix) { + BlobToObjectMetadata blobToObjectMetadata, @Named(PROPERTY_USER_METADATA_PREFIX) String userMdPrefix) { this.blobMetadataParser = blobMetadataParser; this.blobToObjectMetadata = blobToObjectMetadata; this.userMdPrefix = userMdPrefix; } + // eTag pattern can be "a34d7e626b350d2e326196085dfa52f4-1", which is opaque and shouldn't be + // used as content-md5, so filter etags that contain hyphens + static final Pattern MD5_FROM_ETAG = Pattern.compile("^\"?([0-9a-f]+)\"?$"); + /** * parses the http response headers to create a new * {@link org.jclouds.s3.domain.internal.MutableObjectMetadata} object. @@ -65,13 +72,20 @@ public class ParseObjectMetadataFromHeaders implements Function buildRequestHeaders() { checkState(headerTag != null, "headerTag should have been injected!"); checkState(metadataPrefix != null, "metadataPrefix should have been injected!"); - Multimap returnVal = LinkedHashMultimap.create(); + ImmutableMultimap.Builder returnVal = ImmutableMultimap. builder(); for (Entry entry : headers.entries()) { - returnVal.put(entry.getKey().replace("amz", headerTag), entry.getValue()); + returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue()); } if (metadata != null) { + returnVal.put(METADATA_DIRECTIVE.replace(DEFAULT_AMAZON_HEADERTAG, headerTag), "REPLACE"); for (String key : metadata.keySet()) { - returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata - .get(key)); + returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata.get(key)); } - returnVal.put("x-" + headerTag + "-metadata-directive", "REPLACE"); } - return returnVal; + return returnVal.build(); } /** @@ -311,8 +308,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions { /** * @see CopyObjectOptions#ifSourceETagMatches(String) */ - public static CopyObjectOptions ifSourceETagMatches(String eTag) - throws UnsupportedEncodingException { + public static CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException { CopyObjectOptions options = new CopyObjectOptions(); return options.ifSourceETagMatches(eTag); } @@ -320,8 +316,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions { /** * @see CopyObjectOptions#ifSourceETagDoesntMatch(String) */ - public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag) - throws UnsupportedEncodingException { + public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException { CopyObjectOptions options = new CopyObjectOptions(); return options.ifSourceETagDoesntMatch(eTag); } diff --git a/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java b/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java index e78396b74e..70e56f7b81 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java +++ b/apis/s3/src/main/java/org/jclouds/s3/options/PutObjectOptions.java @@ -19,28 +19,30 @@ package org.jclouds.s3.options; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; +import static org.jclouds.s3.reference.S3Headers.CANNED_ACL; +import static org.jclouds.s3.reference.S3Headers.DEFAULT_AMAZON_HEADERTAG; + import java.util.Map.Entry; import javax.inject.Inject; import javax.inject.Named; -import org.jclouds.s3.domain.CannedAccessPolicy; -import org.jclouds.s3.reference.S3Headers; import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.s3.domain.CannedAccessPolicy; -import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; -import static com.google.common.base.Preconditions.*; -import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; - /** * Contains options supported in the REST API for the PUT object operation. *

*

- * Usage

The recommended way to instantiate a PutObjectOptions object is to - * statically import PutObjectOptions.Builder.* and invoke a static creation - * method followed by an instance mutator (if needed): + * Usage The recommended way to instantiate a PutObjectOptions object is to statically import + * PutObjectOptions.Builder.* and invoke a static creation method followed by an instance mutator + * (if needed): *

* * import static org.jclouds.s3.commands.options.PutObjectOptions.Builder.* @@ -50,60 +52,62 @@ import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; * Future publicly readable = connection.putObject("bucketName",new S3Object("key","value"), withAcl(CannedAccessPolicy.PUBLIC_READ)); * * - * @see * * @author Adrian Cole * */ public class PutObjectOptions extends BaseHttpRequestOptions { - public static final PutObjectOptions NONE = new PutObjectOptions(); + public static final PutObjectOptions NONE = new PutObjectOptions(); - private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE; - - private String headerTag; + private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE; - @Inject - public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) { - this.headerTag = headerTag; - } + private String headerTag; - @Override - public Multimap buildRequestHeaders() { - checkState(headerTag != null, "headerTag should have been injected!"); - Multimap returnVal = LinkedHashMultimap.create(); - for (Entry entry : headers.entries()) { - returnVal.put(entry.getKey().replace("amz", headerTag), entry.getValue()); - } - return returnVal; - } - /** - * Override the default ACL (private) with the specified one. - * - * @see CannedAccessPolicy - */ - public PutObjectOptions withAcl(CannedAccessPolicy acl) { - this.acl = checkNotNull(acl, "acl"); - if (!acl.equals(CannedAccessPolicy.PRIVATE)) - this.replaceHeader(S3Headers.CANNED_ACL, acl.toString()); - return this; - } + @Inject + public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) { + this.headerTag = headerTag; + } - /** - * @see PutObjectOptions#withAcl(CannedAccessPolicy) - */ - public CannedAccessPolicy getAcl() { - return acl; - } + @Override + public Multimap buildRequestHeaders() { + checkState(headerTag != null, "headerTag should have been injected!"); + ImmutableMultimap.Builder returnVal = ImmutableMultimap. builder(); + for (Entry entry : headers.entries()) { + returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue()); + } + return returnVal.build(); + } - public static class Builder { + /** + * Override the default ACL (private) with the specified one. + * + * @see CannedAccessPolicy + */ + public PutObjectOptions withAcl(CannedAccessPolicy acl) { + this.acl = checkNotNull(acl, "acl"); + if (!acl.equals(CannedAccessPolicy.PRIVATE)) + this.replaceHeader(CANNED_ACL, acl.toString()); + return this; + } - /** - * @see PutObjectOptions#withAcl(CannedAccessPolicy) - */ - public static PutObjectOptions withAcl(CannedAccessPolicy acl) { - PutObjectOptions options = new PutObjectOptions(); - return options.withAcl(acl); - } - } + /** + * @see PutObjectOptions#withAcl(CannedAccessPolicy) + */ + public CannedAccessPolicy getAcl() { + return acl; + } + + public static class Builder { + + /** + * @see PutObjectOptions#withAcl(CannedAccessPolicy) + */ + public static PutObjectOptions withAcl(CannedAccessPolicy acl) { + PutObjectOptions options = new PutObjectOptions(); + return options.withAcl(acl); + } + } } diff --git a/apis/s3/src/main/java/org/jclouds/s3/reference/S3Headers.java b/apis/s3/src/main/java/org/jclouds/s3/reference/S3Headers.java index c8dc1886d2..4f156de852 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/reference/S3Headers.java +++ b/apis/s3/src/main/java/org/jclouds/s3/reference/S3Headers.java @@ -30,83 +30,105 @@ package org.jclouds.s3.reference; */ public interface S3Headers { - public static final String CONTENT_MD5 = "Content-MD5"; + /** + * Amazon S3 has clones, which often replace this with their particular tag. + */ + public static final String DEFAULT_AMAZON_HEADERTAG = "amz"; - /** Prefix for general Amazon headers: x-amz- */ - public static final String AMAZON_PREFIX = "x-amz-"; + public static final String HEADER_PREFIX = "x-" + DEFAULT_AMAZON_HEADERTAG + "-"; /** * The canned ACL to apply to the object. Options include private, public-read, * public-read-write, and authenticated-read. For more information, see REST Access Control * Policy. */ - public static final String CANNED_ACL = "x-amz-acl"; - - public static final String AMZ_MD5 = "x-amz-meta-object-eTag"; - - /** Amazon's alternative date header: x-amz-date */ - public static final String ALTERNATE_DATE = "x-amz-date"; + public static final String CANNED_ACL = HEADER_PREFIX + "acl"; - /** Prefix for user metadata: x-amz-meta- */ - public static final String USER_METADATA_PREFIX = "x-amz-meta-"; + public static final String AMZ_ETAG = HEADER_PREFIX + "meta-object-eTag"; - /** version ID header */ - public static final String VERSION_ID = "x-amz-version-id"; + /** + * Amazon's alternative date header + */ + public static final String ALTERNATE_DATE = HEADER_PREFIX + "date"; - /** Multi-Factor Authentication header */ - public static final String MFA = "x-amz-mfa"; + /** + * Prefix for user metadata + */ + public static final String USER_METADATA_PREFIX = HEADER_PREFIX + "meta-"; - /** response header for a request's AWS request ID */ - public static final String REQUEST_ID = "x-amz-request-id"; + /** + * version ID header + */ + public static final String VERSION_ID = HEADER_PREFIX + "version-id"; - /** response header for a request's extended debugging ID */ - public static final String EXTENDED_REQUEST_ID = "x-amz-id-2"; + /** + * Multi-Factor Authentication header + */ + public static final String MFA = HEADER_PREFIX + "mfa"; - /** request header indicating how to handle metadata when copying an object */ - public static final String METADATA_DIRECTIVE = "x-amz-metadata-directive"; + /** + * response header for a request's AWS request ID + */ + public static final String REQUEST_ID = HEADER_PREFIX + "request-id"; - /** DevPay token header */ - public static final String SECURITY_TOKEN = "x-amz-security-token"; + /** + * response header for a request's extended debugging ID + */ + public static final String EXTENDED_REQUEST_ID = HEADER_PREFIX + "id-2"; - /** Header describing what class of storage a user wants */ - public static final String STORAGE_CLASS = "x-amz-storage-class"; + /** + * request header indicating how to handle metadata when copying an object + */ + public static final String METADATA_DIRECTIVE = HEADER_PREFIX + "metadata-directive"; - /** ETag matching constraint header for the copy object request */ - public static final String COPY_SOURCE_IF_MATCH = "x-amz-copy-source-if-match"; + /** + * DevPay token header + */ + public static final String SECURITY_TOKEN = HEADER_PREFIX + "security-token"; - /** ETag non-matching constraint header for the copy object request */ - public static final String COPY_SOURCE_IF_NO_MATCH = "x-amz-copy-source-if-none-match"; + /** + * Header describing what class of storage a user wants + */ + public static final String STORAGE_CLASS = HEADER_PREFIX + "storage-class"; - /** Unmodified since constraint header for the copy object request */ - public static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = "x-amz-copy-source-if-unmodified-since"; + /** + * ETag matching constraint header for the copy object request + */ + public static final String COPY_SOURCE_IF_MATCH = HEADER_PREFIX + "copy-source-if-match"; - /** Modified since constraint header for the copy object request */ - public static final String COPY_SOURCE_IF_MODIFIED_SINCE = "x-amz-copy-source-if-modified-since"; + /** + * ETag non-matching constraint header for the copy object request + */ + public static final String COPY_SOURCE_IF_NO_MATCH = HEADER_PREFIX + "copy-source-if-none-match"; - /** Range header for the get object request */ - public static final String RANGE = "Range"; + /** + * Unmodified since constraint header for the copy object request + */ + public static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = HEADER_PREFIX + "copy-source-if-unmodified-since"; - /** Modified since constraint header for the get object request */ - public static final String GET_OBJECT_IF_MODIFIED_SINCE = "If-Modified-Since"; + /** + * Modified since constraint header for the copy object request + */ + public static final String COPY_SOURCE_IF_MODIFIED_SINCE = HEADER_PREFIX + "copy-source-if-modified-since"; - /** Unmodified since constraint header for the get object request */ - public static final String GET_OBJECT_IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + /** + * Encrypted symmetric key header that is used in the envelope encryption mechanism + */ + public static final String CRYPTO_KEY = HEADER_PREFIX + "key"; - /** ETag matching constraint header for the get object request */ - public static final String GET_OBJECT_IF_MATCH = "If-Match"; + /** + * Initialization vector (IV) header that is used in the symmetric and envelope encryption + * mechanisms + */ + public static final String CRYPTO_IV = HEADER_PREFIX + "iv"; - /** ETag non-matching constraint header for the get object request */ - public static final String GET_OBJECT_IF_NONE_MATCH = "If-None-Match"; + /** + * JSON-encoded description of encryption materials used during encryption + */ + public static final String MATERIALS_DESCRIPTION = HEADER_PREFIX + "matdesc"; - /** Encrypted symmetric key header that is used in the envelope encryption mechanism */ - public static final String CRYPTO_KEY = "x-amz-key"; - - /** Initialization vector (IV) header that is used in the symmetric and envelope encryption mechanisms */ - public static final String CRYPTO_IV = "x-amz-iv"; - - /** JSON-encoded description of encryption materials used during encryption */ - public static final String MATERIALS_DESCRIPTION = "x-amz-matdesc"; - - /** Instruction file header to be placed in the metadata of instruction files */ - public static final String CRYPTO_INSTRUCTION_FILE = "x-amz-crypto-instr-file"; + /** + * Instruction file header to be placed in the metadata of instruction files + */ + public static final String CRYPTO_INSTRUCTION_FILE = HEADER_PREFIX + "crypto-instr-file"; } diff --git a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java index 2d55554a2a..970e4a9306 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/filters/RequestAuthorizeSignatureTest.java @@ -36,6 +36,7 @@ import org.jclouds.s3.domain.AccessControlList; import org.jclouds.s3.domain.CannedAccessPolicy; import org.jclouds.s3.domain.S3Object; import org.jclouds.s3.options.PutObjectOptions; +import org.jclouds.s3.reference.S3Headers; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -72,13 +73,13 @@ public class RequestAuthorizeSignatureTest extends BaseS3AsyncClientTest userMetadata = ImmutableMap.of("foo", "bar"); - private MutableObjectMetadataImpl expects; - BlobToObjectMetadata blobToObjectMetadata; + @Test + void testAmzEtagStillParsesToMD5AndDoesntMistakeAmzEtagForUserMetadata() throws Exception { - private ParseSystemAndUserMetadataFromHeaders blobParser(HttpResponse response, String etag) { - ParseSystemAndUserMetadataFromHeaders parser = createMock(ParseSystemAndUserMetadataFromHeaders.class); - MutableBlobMetadata md = new MutableBlobMetadataImpl(); - md.getContentMetadata().setContentType("type"); - md.setETag(etag); - md.setName("key"); - md.setLastModified(now); - md.getContentMetadata().setContentLength(1025l); - md.getContentMetadata().setContentDisposition("contentDisposition"); - md.getContentMetadata().setContentEncoding("encoding"); - md.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd")); - md.setUserMetadata(userMetadata); - expect(parser.apply(response)).andReturn(md); - replay(parser); - return parser; - } + HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of( + S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified, + HttpHeaders.CACHE_CONTROL, "cacheControl", S3Headers.AMZ_ETAG, "\"abcd\"")); + http.getPayload().getContentMetadata().setContentLength(1025l); + http.getPayload().getContentMetadata().setContentDisposition("contentDisposition"); + http.getPayload().getContentMetadata().setContentEncoding("encoding"); + http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM); - @BeforeTest - void setUp() { - blobToObjectMetadata = new BlobToObjectMetadata(); - expects = new MutableObjectMetadataImpl(); + MutableObjectMetadata response = parser.apply(http); + + MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl(); expects.setCacheControl("cacheControl"); expects.getContentMetadata().setContentDisposition("contentDisposition"); expects.getContentMetadata().setContentEncoding("encoding"); expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd")); - expects.getContentMetadata().setContentType("type"); + expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM); expects.getContentMetadata().setContentLength(1025l); expects.setETag("\"abcd\""); expects.setKey("key"); @@ -118,5 +135,29 @@ public class ParseObjectMetadataFromHeadersTest { expects.setOwner(null); expects.setStorageClass(StorageClass.STANDARD); expects.setUserMetadata(userMetadata); + + assertEquals(response, expects); + } + + String lastModified = new SimpleDateFormatDateService().rfc822DateFormat(new Date()); + // rfc isn't accurate down to nanos, so we'll parse back to ensure tests pass + Date now = new SimpleDateFormatDateService().rfc822DateParse(lastModified); + + Map userMetadata = ImmutableMap.of("foo", "bar"); + ParseObjectMetadataFromHeaders parser; + + @BeforeTest + void setUp() { + parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(RequestSigner.class).toInstance(createMock(RequestSigner.class)); + bindConstant().annotatedWith(Names.named(PROPERTY_HEADER_TAG)).to(S3Headers.DEFAULT_AMAZON_HEADERTAG); + bindConstant().annotatedWith(Names.named(PROPERTY_USER_METADATA_PREFIX)).to(S3Headers.USER_METADATA_PREFIX); + + } + + }).getInstance(ParseObjectMetadataFromHeaders.class).setKey("key"); } } diff --git a/apis/s3/src/test/java/org/jclouds/s3/handlers/ParseS3ErrorFromXmlContentTest.java b/apis/s3/src/test/java/org/jclouds/s3/handlers/ParseS3ErrorFromXmlContentTest.java index 8ce7e2541c..120c008571 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/handlers/ParseS3ErrorFromXmlContentTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/handlers/ParseS3ErrorFromXmlContentTest.java @@ -39,6 +39,7 @@ import org.jclouds.http.HttpResponse; import org.jclouds.http.functions.config.SaxParserModule; import org.jclouds.io.Payloads; import org.jclouds.rest.RequestSigner; +import org.jclouds.s3.reference.S3Headers; import org.jclouds.util.Strings2; import org.testng.annotations.Test; @@ -57,16 +58,17 @@ public class ParseS3ErrorFromXmlContentTest { @Test public void test404ContainerNotFoundExceptionPath() { assertCodeMakes("GET", URI - .create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404, "HTTP/1.1 404 Not Found", false, - "Monster.NotFound", ContainerNotFoundException.class); + .create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404, + "HTTP/1.1 404 Not Found", false, "Monster.NotFound", + ContainerNotFoundException.class); } - @Test public void test404KeyNotFoundExceptionPath() { assertCodeMakes("GET", URI - .create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404, "HTTP/1.1 404 Not Found", false, - "Monster.NotFound", KeyNotFoundException.class); + .create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404, + "HTTP/1.1 404 Not Found", false, "Monster.NotFound", + KeyNotFoundException.class); } private void assertCodeMakes(String method, URI uri, int statusCode, String message, final boolean virtualHost, @@ -77,7 +79,7 @@ public class ParseS3ErrorFromXmlContentTest { @Override protected void configure() { bind(RequestSigner.class).toInstance(createMock(RequestSigner.class)); - bindConstant().annotatedWith(Names.named(PROPERTY_HEADER_TAG)).to("amz"); + bindConstant().annotatedWith(Names.named(PROPERTY_HEADER_TAG)).to(S3Headers.DEFAULT_AMAZON_HEADERTAG); bindConstant().annotatedWith(Names.named(PROPERTY_S3_SERVICE_PATH)).to(SERVICE_PATH); bindConstant().annotatedWith(Names.named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS)).to(virtualHost); } diff --git a/apis/s3/src/test/java/org/jclouds/s3/options/CopyObjectOptionsTest.java b/apis/s3/src/test/java/org/jclouds/s3/options/CopyObjectOptionsTest.java index 46661ff8df..d147eaa2c3 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/options/CopyObjectOptionsTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/options/CopyObjectOptionsTest.java @@ -19,12 +19,19 @@ package org.jclouds.s3.options; +import static com.google.common.collect.Iterables.getOnlyElement; import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagDoesntMatch; import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagMatches; import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceModifiedSince; import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceUnmodifiedSince; import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideAcl; import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideMetadataWith; +import static org.jclouds.s3.reference.S3Headers.CANNED_ACL; +import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_MODIFIED_SINCE; +import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_NO_MATCH; +import static org.jclouds.s3.reference.S3Headers.DEFAULT_AMAZON_HEADERTAG; +import static org.jclouds.s3.reference.S3Headers.METADATA_DIRECTIVE; +import static org.jclouds.s3.reference.S3Headers.USER_METADATA_PREFIX; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @@ -33,13 +40,12 @@ import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Map; -import org.jclouds.s3.domain.CannedAccessPolicy; -import org.jclouds.s3.reference.S3Headers; import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.s3.domain.CannedAccessPolicy; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import com.google.common.collect.Maps; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; /** @@ -54,14 +60,10 @@ public class CopyObjectOptionsTest { private Date now; private String nowExpected; private Map goodMeta; - private Map badMeta; @BeforeMethod void setUp() { - goodMeta = Maps.newHashMap(); - goodMeta.put("x-amz-meta-adrian", "foo"); - badMeta = Maps.newHashMap(); - badMeta.put("x-google-meta-adrian", "foo"); + goodMeta = ImmutableMap.of(USER_METADATA_PREFIX + "adrian", "foo"); now = new Date(); nowExpected = new SimpleDateFormatDateService().rfc822DateFormat(now); etag = "mama"; @@ -70,8 +72,8 @@ public class CopyObjectOptionsTest { @Test void testGoodMetaStatic() { CopyObjectOptions options = overrideMetadataWith(goodMeta); - options.setMetadataPrefix("x-amz-meta-"); - options.setHeaderTag("amz"); + options.setMetadataPrefix(USER_METADATA_PREFIX); + options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG); assertGoodMeta(options); } @@ -85,17 +87,17 @@ public class CopyObjectOptionsTest { assert options.getMetadata() != null; Multimap headers = options.buildRequestHeaders(); assertEquals(headers.size(), 2); - assertEquals(headers.get("x-amz-metadata-directive").iterator().next(), "REPLACE"); + assertEquals(headers.get(METADATA_DIRECTIVE).iterator().next(), "REPLACE"); assertEquals(options.getMetadata().size(), 1); - assertEquals(headers.get("x-amz-meta-adrian").iterator().next(), "foo"); - assertEquals(options.getMetadata().get("x-amz-meta-adrian"), "foo"); + assertEquals(headers.get(USER_METADATA_PREFIX + "adrian").iterator().next(), "foo"); + assertEquals(options.getMetadata().get(USER_METADATA_PREFIX + "adrian"), "foo"); } @Test void testGoodMeta() { CopyObjectOptions options = new CopyObjectOptions(); - options.setHeaderTag("amz"); - options.setMetadataPrefix("x-amz-meta-"); + options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG); + options.setMetadataPrefix(USER_METADATA_PREFIX); options.overrideMetadataWith(goodMeta); assertGoodMeta(options); } @@ -272,25 +274,24 @@ public class CopyObjectOptionsTest { @Test void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException { CopyObjectOptions options = new CopyObjectOptions(); - options.setHeaderTag("amz"); + options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG); - options.setMetadataPrefix("x-amz-meta-"); + options.setMetadataPrefix(USER_METADATA_PREFIX); assert options.buildRequestHeaders() != null; } @Test void testBuildRequestHeaders() throws UnsupportedEncodingException { - CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag) - .overrideMetadataWith(goodMeta); - options.setHeaderTag("amz"); + CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag).overrideMetadataWith( + goodMeta); + options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG); - options.setMetadataPrefix("x-amz-meta-"); + options.setMetadataPrefix(USER_METADATA_PREFIX); Multimap headers = options.buildRequestHeaders(); - assertEquals(headers.get("x-amz-copy-source-if-modified-since").iterator().next(), - new SimpleDateFormatDateService().rfc822DateFormat(now)); - assertEquals(headers.get("x-amz-copy-source-if-none-match").iterator().next(), "\"" + etag - + "\""); + assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_MODIFIED_SINCE)), new SimpleDateFormatDateService() + .rfc822DateFormat(now)); + assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_NO_MATCH)), "\"" + etag + "\""); for (String value : goodMeta.values()) assertTrue(headers.containsValue(value)); @@ -311,13 +312,12 @@ public class CopyObjectOptionsTest { @Test void testBuildRequestHeadersACL() throws UnsupportedEncodingException { CopyObjectOptions options = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ); - options.setHeaderTag("amz"); + options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG); - options.setMetadataPrefix("x-amz-meta-"); + options.setMetadataPrefix(USER_METADATA_PREFIX); Multimap headers = options.buildRequestHeaders(); - assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(), - CannedAccessPolicy.AUTHENTICATED_READ.toString()); + assertEquals(headers.get(CANNED_ACL).iterator().next(), CannedAccessPolicy.AUTHENTICATED_READ.toString()); } } diff --git a/apis/s3/src/test/java/org/jclouds/s3/options/PutBucketOptionsTest.java b/apis/s3/src/test/java/org/jclouds/s3/options/PutBucketOptionsTest.java index 57a539f4f4..c55df4bd9c 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/options/PutBucketOptionsTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/options/PutBucketOptionsTest.java @@ -55,7 +55,7 @@ public class PutBucketOptionsTest { PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ); - options.setHeaderTag("amz"); + options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG); Multimap headers = options.buildRequestHeaders(); assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(), CannedAccessPolicy.AUTHENTICATED_READ.toString()); diff --git a/apis/s3/src/test/java/org/jclouds/s3/options/PutObjectOptionsTest.java b/apis/s3/src/test/java/org/jclouds/s3/options/PutObjectOptionsTest.java index d954ffaf9f..8f688c6b49 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/options/PutObjectOptionsTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/options/PutObjectOptionsTest.java @@ -54,7 +54,7 @@ public class PutObjectOptionsTest { void testBuildRequestHeaders() throws UnsupportedEncodingException { PutObjectOptions options = withAcl(CannedAccessPolicy.AUTHENTICATED_READ); - options.setHeaderTag("amz"); + options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG); Multimap headers = options.buildRequestHeaders(); assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),