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(),