mirror of https://github.com/apache/jclouds.git
simplified request authorize syntax, fixed a couple issues in the signature relating to spaces. normalized to constants, rewrote metadata test to be easier to maintain
This commit is contained in:
parent
71eb7ea8b7
commit
01ef7140db
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,41 +55,42 @@ 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 <a href= "http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?RESTAuthentication.html" />
|
||||
* @see <a href=
|
||||
* "http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?RESTAuthentication.html"
|
||||
* />
|
||||
* @author Adrian Cole
|
||||
*
|
||||
*/
|
||||
@Singleton
|
||||
public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSigner {
|
||||
private static final Predicate<Annotation> ANNOTATIONTYPE_BUCKET = new Predicate<Annotation>() {
|
||||
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<String> 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",
|
||||
public static Set<String> 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;
|
||||
|
@ -133,16 +136,12 @@ 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<String, String> 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;
|
||||
}
|
||||
|
||||
|
@ -156,8 +155,8 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -202,9 +201,11 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
|||
}
|
||||
|
||||
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()
|
||||
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)
|
||||
|
@ -214,17 +215,15 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void appendHttpHeaders(HttpRequest request,
|
||||
SortedSetMultimap<String, String> canonicalizedHeaders) {
|
||||
void appendHttpHeaders(HttpRequest request, SortedSetMultimap<String, String> canonicalizedHeaders) {
|
||||
Multimap<String, String> headers = request.getHeaders();
|
||||
for (Entry<String, String> 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<Annotation>() {
|
||||
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<String, String> 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<String, String> params = parseQueryToMap(request.getEndpoint().getQuery());
|
||||
char separator = '?';
|
||||
for (Entry<String, String> 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -58,6 +61,10 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
|
|||
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<HttpResponse, Mu
|
|||
public MutableObjectMetadata apply(HttpResponse from) {
|
||||
BlobMetadata base = blobMetadataParser.apply(from);
|
||||
MutableObjectMetadata to = blobToObjectMetadata.apply(base);
|
||||
|
||||
addETagTo(from, to);
|
||||
if (to.getContentMetadata().getContentMD5() == null && to.getETag() != null) {
|
||||
byte[] md5 = CryptoStreams.hex(to.getETag().replaceAll("\"", ""));
|
||||
Matcher md5Matcher = MD5_FROM_ETAG.matcher(to.getETag());
|
||||
if (md5Matcher.find()) {
|
||||
byte[] md5 = CryptoStreams.hex(md5Matcher.group(1));
|
||||
// it is possible others will look at the http payload directly
|
||||
if (from.getPayload() != null)
|
||||
from.getPayload().getContentMetadata().setContentMD5(md5);
|
||||
to.getContentMetadata().setContentMD5(md5);
|
||||
}
|
||||
}
|
||||
// amz has an etag, but matches syntax for usermetadata
|
||||
to.getUserMetadata().remove("object-etag");
|
||||
to.setCacheControl(from.getFirstHeaderOrNull(HttpHeaders.CACHE_CONTROL));
|
||||
return to;
|
||||
}
|
||||
|
@ -95,4 +109,8 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
|
|||
return this;
|
||||
}
|
||||
|
||||
public ParseObjectMetadataFromHeaders setKey(String key) {
|
||||
blobMetadataParser.setName(key);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,13 @@ 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.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
||||
import static org.jclouds.s3.reference.S3Headers.CANNED_ACL;
|
||||
import static org.jclouds.s3.reference.S3Headers.COPY_SOURCE_IF_MATCH;
|
||||
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.COPY_SOURCE_IF_UNMODIFIED_SINCE;
|
||||
import static org.jclouds.s3.reference.S3Headers.DEFAULT_AMAZON_HEADERTAG;
|
||||
import static org.jclouds.s3.reference.S3Headers.METADATA_DIRECTIVE;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Date;
|
||||
|
@ -32,14 +39,13 @@ 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.date.DateService;
|
||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||
import org.jclouds.http.options.BaseHttpRequestOptions;
|
||||
import org.jclouds.s3.domain.CannedAccessPolicy;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
/**
|
||||
|
@ -99,7 +105,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
|
||||
this.acl = checkNotNull(acl, "acl");
|
||||
if (!acl.equals(CannedAccessPolicy.PRIVATE))
|
||||
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString());
|
||||
this.replaceHeader(CANNED_ACL, acl.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -124,7 +130,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* @see CopyObjectOptions#ifSourceModifiedSince(Date)
|
||||
*/
|
||||
public String getIfModifiedSince() {
|
||||
return getFirstHeaderOrNull("x-amz-copy-source-if-modified-since");
|
||||
return getFirstHeaderOrNull(COPY_SOURCE_IF_MODIFIED_SINCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +147,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* @see CopyObjectOptions#ifSourceUnmodifiedSince(Date)
|
||||
*/
|
||||
public String getIfUnmodifiedSince() {
|
||||
return getFirstHeaderOrNull("x-amz-copy-source-if-unmodified-since");
|
||||
return getFirstHeaderOrNull(COPY_SOURCE_IF_UNMODIFIED_SINCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,7 +162,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* @see CopyObjectOptions#ifSourceETagMatches(String)
|
||||
*/
|
||||
public String getIfMatch() {
|
||||
return getFirstHeaderOrNull("x-amz-copy-source-if-match");
|
||||
return getFirstHeaderOrNull(COPY_SOURCE_IF_MATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,7 +177,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
|
||||
*/
|
||||
public String getIfNoneMatch() {
|
||||
return getFirstHeaderOrNull("x-amz-copy-source-if-none-match");
|
||||
return getFirstHeaderOrNull(COPY_SOURCE_IF_NO_MATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,10 +198,9 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
*/
|
||||
public CopyObjectOptions ifSourceModifiedSince(Date ifModifiedSince) {
|
||||
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()");
|
||||
checkState(getIfUnmodifiedSince() == null,
|
||||
"ifUnmodifiedSince() is not compatible with ifModifiedSince()");
|
||||
replaceHeader("x-amz-copy-source-if-modified-since", dateService
|
||||
.rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince")));
|
||||
checkState(getIfUnmodifiedSince() == null, "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
|
||||
replaceHeader(COPY_SOURCE_IF_MODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifModifiedSince,
|
||||
"ifModifiedSince")));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -206,12 +211,10 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* {@link #ifSourceModifiedSince(Date)}
|
||||
*/
|
||||
public CopyObjectOptions ifSourceUnmodifiedSince(Date ifUnmodifiedSince) {
|
||||
checkState(getIfNoneMatch() == null,
|
||||
"ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
|
||||
checkState(getIfModifiedSince() == null,
|
||||
"ifModifiedSince() is not compatible with ifUnmodifiedSince()");
|
||||
replaceHeader("x-amz-copy-source-if-unmodified-since", dateService
|
||||
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince")));
|
||||
checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
|
||||
checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifUnmodifiedSince()");
|
||||
replaceHeader(COPY_SOURCE_IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifUnmodifiedSince,
|
||||
"ifUnmodifiedSince")));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -226,12 +229,9 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* hash representing the payload
|
||||
*/
|
||||
public CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
|
||||
checkState(getIfNoneMatch() == null,
|
||||
"ifETagDoesntMatch() is not compatible with ifETagMatches()");
|
||||
checkState(getIfModifiedSince() == null,
|
||||
"ifModifiedSince() is not compatible with ifETagMatches()");
|
||||
replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", checkNotNull(eTag,
|
||||
"eTag")));
|
||||
checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifETagMatches()");
|
||||
checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifETagMatches()");
|
||||
replaceHeader(COPY_SOURCE_IF_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "eTag")));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -246,13 +246,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
* @throws UnsupportedEncodingException
|
||||
* if there was a problem converting this into an S3 eTag string
|
||||
*/
|
||||
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag)
|
||||
throws UnsupportedEncodingException {
|
||||
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
|
||||
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()");
|
||||
Preconditions.checkState(getIfUnmodifiedSince() == null,
|
||||
"ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()");
|
||||
replaceHeader("x-amz-copy-source-if-none-match", String.format("\"%1$s\"", checkNotNull(eTag,
|
||||
"ifETagDoesntMatch")));
|
||||
replaceHeader(COPY_SOURCE_IF_NO_MATCH, String.format("\"%s\"", checkNotNull(eTag, "ifETagDoesntMatch")));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -260,18 +258,17 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
|
|||
public Multimap<String, String> buildRequestHeaders() {
|
||||
checkState(headerTag != null, "headerTag should have been injected!");
|
||||
checkState(metadataPrefix != null, "metadataPrefix should have been injected!");
|
||||
Multimap<String, String> returnVal = LinkedHashMultimap.create();
|
||||
ImmutableMultimap.Builder<String, String> returnVal = ImmutableMultimap.<String, String> builder();
|
||||
for (Entry<String, String> 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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <p/>
|
||||
* <h2>
|
||||
* Usage</h2> 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</h2> 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):
|
||||
* <p/>
|
||||
* <code>
|
||||
* import static org.jclouds.s3.commands.options.PutObjectOptions.Builder.*
|
||||
|
@ -50,7 +52,8 @@ import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
|||
* Future<Boolean> publicly readable = connection.putObject("bucketName",new S3Object("key","value"), withAcl(CannedAccessPolicy.PUBLIC_READ));
|
||||
* <code>
|
||||
*
|
||||
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectPUT.html?"
|
||||
* @see <a
|
||||
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectPUT.html?"
|
||||
* />
|
||||
*
|
||||
* @author Adrian Cole
|
||||
|
@ -71,12 +74,13 @@ public class PutObjectOptions extends BaseHttpRequestOptions {
|
|||
@Override
|
||||
public Multimap<String, String> buildRequestHeaders() {
|
||||
checkState(headerTag != null, "headerTag should have been injected!");
|
||||
Multimap<String, String> returnVal = LinkedHashMultimap.create();
|
||||
ImmutableMultimap.Builder<String, String> returnVal = ImmutableMultimap.<String, String> builder();
|
||||
for (Entry<String, String> entry : headers.entries()) {
|
||||
returnVal.put(entry.getKey().replace("amz", headerTag), entry.getValue());
|
||||
returnVal.put(entry.getKey().replace(DEFAULT_AMAZON_HEADERTAG, headerTag), entry.getValue());
|
||||
}
|
||||
return returnVal;
|
||||
return returnVal.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default ACL (private) with the specified one.
|
||||
*
|
||||
|
@ -85,7 +89,7 @@ public class PutObjectOptions extends BaseHttpRequestOptions {
|
|||
public PutObjectOptions withAcl(CannedAccessPolicy acl) {
|
||||
this.acl = checkNotNull(acl, "acl");
|
||||
if (!acl.equals(CannedAccessPolicy.PRIVATE))
|
||||
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString());
|
||||
this.replaceHeader(CANNED_ACL, acl.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 CANNED_ACL = HEADER_PREFIX + "acl";
|
||||
|
||||
public static final String AMZ_MD5 = "x-amz-meta-object-eTag";
|
||||
public static final String AMZ_ETAG = HEADER_PREFIX + "meta-object-eTag";
|
||||
|
||||
/** Amazon's alternative date header: x-amz-date */
|
||||
public static final String ALTERNATE_DATE = "x-amz-date";
|
||||
/**
|
||||
* Amazon's alternative date header
|
||||
*/
|
||||
public static final String ALTERNATE_DATE = HEADER_PREFIX + "date";
|
||||
|
||||
/** Prefix for user metadata: x-amz-meta- */
|
||||
public static final String USER_METADATA_PREFIX = "x-amz-meta-";
|
||||
/**
|
||||
* Prefix for user metadata
|
||||
*/
|
||||
public static final String USER_METADATA_PREFIX = HEADER_PREFIX + "meta-";
|
||||
|
||||
/** version ID header */
|
||||
public static final String VERSION_ID = "x-amz-version-id";
|
||||
/**
|
||||
* version ID header
|
||||
*/
|
||||
public static final String VERSION_ID = HEADER_PREFIX + "version-id";
|
||||
|
||||
/** Multi-Factor Authentication header */
|
||||
public static final String MFA = "x-amz-mfa";
|
||||
/**
|
||||
* Multi-Factor Authentication header
|
||||
*/
|
||||
public static final String MFA = HEADER_PREFIX + "mfa";
|
||||
|
||||
/** response header for a request's AWS request ID */
|
||||
public static final String REQUEST_ID = "x-amz-request-id";
|
||||
/**
|
||||
* response header for a request's AWS request ID
|
||||
*/
|
||||
public static final String REQUEST_ID = HEADER_PREFIX + "request-id";
|
||||
|
||||
/** response header for a request's extended debugging ID */
|
||||
public static final String EXTENDED_REQUEST_ID = "x-amz-id-2";
|
||||
/**
|
||||
* response header for a request's extended debugging ID
|
||||
*/
|
||||
public static final String EXTENDED_REQUEST_ID = HEADER_PREFIX + "id-2";
|
||||
|
||||
/** request header indicating how to handle metadata when copying an object */
|
||||
public static final String METADATA_DIRECTIVE = "x-amz-metadata-directive";
|
||||
/**
|
||||
* request header indicating how to handle metadata when copying an object
|
||||
*/
|
||||
public static final String METADATA_DIRECTIVE = HEADER_PREFIX + "metadata-directive";
|
||||
|
||||
/** DevPay token header */
|
||||
public static final String SECURITY_TOKEN = "x-amz-security-token";
|
||||
/**
|
||||
* DevPay token header
|
||||
*/
|
||||
public static final String SECURITY_TOKEN = HEADER_PREFIX + "security-token";
|
||||
|
||||
/** Header describing what class of storage a user wants */
|
||||
public static final String STORAGE_CLASS = "x-amz-storage-class";
|
||||
/**
|
||||
* Header describing what class of storage a user wants
|
||||
*/
|
||||
public static final String STORAGE_CLASS = HEADER_PREFIX + "storage-class";
|
||||
|
||||
/** ETag matching constraint header for the copy object request */
|
||||
public static final String COPY_SOURCE_IF_MATCH = "x-amz-copy-source-if-match";
|
||||
/**
|
||||
* ETag matching constraint header for the copy object request
|
||||
*/
|
||||
public static final String COPY_SOURCE_IF_MATCH = HEADER_PREFIX + "copy-source-if-match";
|
||||
|
||||
/** 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";
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/** 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";
|
||||
/**
|
||||
* 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 copy object request */
|
||||
public static final String COPY_SOURCE_IF_MODIFIED_SINCE = "x-amz-copy-source-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";
|
||||
|
||||
/** Range header for the get object request */
|
||||
public static final String RANGE = "Range";
|
||||
/**
|
||||
* Encrypted symmetric key header that is used in the envelope encryption mechanism
|
||||
*/
|
||||
public static final String CRYPTO_KEY = HEADER_PREFIX + "key";
|
||||
|
||||
/** Modified since constraint header for the get object request */
|
||||
public static final String GET_OBJECT_IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||
/**
|
||||
* Initialization vector (IV) header that is used in the symmetric and envelope encryption
|
||||
* mechanisms
|
||||
*/
|
||||
public static final String CRYPTO_IV = HEADER_PREFIX + "iv";
|
||||
|
||||
/** Unmodified since constraint header for the get object request */
|
||||
public static final String GET_OBJECT_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
|
||||
/**
|
||||
* JSON-encoded description of encryption materials used during encryption
|
||||
*/
|
||||
public static final String MATERIALS_DESCRIPTION = HEADER_PREFIX + "matdesc";
|
||||
|
||||
/** ETag matching constraint header for the get object request */
|
||||
public static final String GET_OBJECT_IF_MATCH = "If-Match";
|
||||
|
||||
/** ETag non-matching constraint header for the get object request */
|
||||
public static final String GET_OBJECT_IF_NONE_MATCH = "If-None-Match";
|
||||
|
||||
/** 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";
|
||||
}
|
||||
|
|
|
@ -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<S3Async
|
|||
*/
|
||||
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000)
|
||||
void testIdempotent(HttpRequest request) {
|
||||
filter.filter(request);
|
||||
request = filter.filter(request);
|
||||
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
|
||||
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
||||
int iterations = 1;
|
||||
while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
|
||||
date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
|
||||
filter.filter(request);
|
||||
request = filter.filter(request);
|
||||
if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date))
|
||||
assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format(
|
||||
"sig: %s != %s on attempt %s", signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION),
|
||||
|
@ -135,13 +136,13 @@ public class RequestAuthorizeSignatureTest extends BaseS3AsyncClientTest<S3Async
|
|||
filter.appendHttpHeaders(request, canonicalizedHeaders);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
filter.appendAmzHeaders(canonicalizedHeaders, builder);
|
||||
assertEquals(builder.toString(), "x-amz-meta-x-amz-adrian: foo\n");
|
||||
assertEquals(builder.toString(), S3Headers.USER_METADATA_PREFIX + "adrian:foo\n");
|
||||
}
|
||||
|
||||
private HttpRequest putObject() throws NoSuchMethodException {
|
||||
S3Object object = blobToS3Object.apply(BindBlobToMultipartFormTest.TEST_BLOB);
|
||||
|
||||
object.getMetadata().getUserMetadata().put("x-amz-Adrian", "foo");
|
||||
object.getMetadata().getUserMetadata().put("Adrian", "foo");
|
||||
HttpRequest request = processor.createRequest(S3AsyncClient.class.getMethod("putObject", String.class,
|
||||
S3Object.class, PutObjectOptions[].class), "bucket", object);
|
||||
return request;
|
||||
|
|
|
@ -19,31 +19,34 @@
|
|||
|
||||
package org.jclouds.s3.functions;
|
||||
|
||||
import static org.easymock.EasyMock.expect;
|
||||
import static org.easymock.classextension.EasyMock.createMock;
|
||||
import static org.easymock.classextension.EasyMock.replay;
|
||||
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
|
||||
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.jclouds.s3.blobstore.functions.BlobToObjectMetadata;
|
||||
import org.jclouds.crypto.CryptoStreams;
|
||||
import org.jclouds.date.internal.SimpleDateFormatDateService;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.rest.RequestSigner;
|
||||
import org.jclouds.s3.domain.MutableObjectMetadata;
|
||||
import org.jclouds.s3.domain.ObjectMetadata.StorageClass;
|
||||
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl;
|
||||
import org.jclouds.blobstore.domain.MutableBlobMetadata;
|
||||
import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
|
||||
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
|
||||
import org.jclouds.crypto.CryptoStreams;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.io.Payloads;
|
||||
import org.jclouds.s3.reference.S3Headers;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
/**
|
||||
* @author Adrian Cole
|
||||
|
@ -52,65 +55,79 @@ import com.google.common.collect.ImmutableMultimap;
|
|||
public class ParseObjectMetadataFromHeadersTest {
|
||||
|
||||
@Test
|
||||
void testNormal() throws Exception {
|
||||
void testNormalParsesETagIntoMD5AndMetadataHeaders() throws Exception {
|
||||
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
||||
HttpHeaders.CACHE_CONTROL, "cacheControl"));
|
||||
S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
|
||||
HttpHeaders.ETAG, "\"abcd\"", HttpHeaders.CACHE_CONTROL, "cacheControl"));
|
||||
http.getPayload().getContentMetadata().setContentLength(1025l);
|
||||
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
||||
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
||||
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
ParseObjectMetadataFromHeaders parser = new ParseObjectMetadataFromHeaders(blobParser(http, "\"abcd\""),
|
||||
blobToObjectMetadata, "x-amz-meta-");
|
||||
MutableObjectMetadata response = parser.apply(http);
|
||||
|
||||
MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
|
||||
expects.setCacheControl("cacheControl");
|
||||
expects.getContentMetadata().setContentDisposition("contentDisposition");
|
||||
expects.getContentMetadata().setContentEncoding("encoding");
|
||||
expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
expects.getContentMetadata().setContentLength(1025l);
|
||||
expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
|
||||
expects.setETag("\"abcd\"");
|
||||
expects.setKey("key");
|
||||
expects.setLastModified(now);
|
||||
expects.setOwner(null);
|
||||
expects.setStorageClass(StorageClass.STANDARD);
|
||||
expects.setUserMetadata(userMetadata);
|
||||
assertEquals(response, expects);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAmzEtag() throws Exception {
|
||||
|
||||
void testMultipartDoesntAttemptToParseETagIntoMD5() throws Exception {
|
||||
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
|
||||
HttpHeaders.CACHE_CONTROL, "cacheControl", "x-amz-meta-object-eTag", "\"abcd\""));
|
||||
|
||||
S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
|
||||
HttpHeaders.ETAG, "\"abcd-1\"", HttpHeaders.CACHE_CONTROL, "cacheControl"));
|
||||
http.getPayload().getContentMetadata().setContentLength(1025l);
|
||||
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
|
||||
http.getPayload().getContentMetadata().setContentEncoding("encoding");
|
||||
ParseObjectMetadataFromHeaders parser = new ParseObjectMetadataFromHeaders(blobParser(http, null),
|
||||
blobToObjectMetadata, "x-amz-meta-");
|
||||
http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
MutableObjectMetadata response = parser.apply(http);
|
||||
|
||||
MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
|
||||
expects.setCacheControl("cacheControl");
|
||||
expects.getContentMetadata().setContentDisposition("contentDisposition");
|
||||
expects.getContentMetadata().setContentEncoding("encoding");
|
||||
expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
expects.getContentMetadata().setContentLength(1025l);
|
||||
expects.setETag("\"abcd-1\"");
|
||||
expects.setKey("key");
|
||||
expects.setLastModified(now);
|
||||
expects.setOwner(null);
|
||||
expects.setStorageClass(StorageClass.STANDARD);
|
||||
expects.setUserMetadata(userMetadata);
|
||||
assertEquals(response, expects);
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
Map<String, String> 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<String, String> 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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
"<Error><Code>Monster.NotFound</Code></Error>", ContainerNotFoundException.class);
|
||||
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404,
|
||||
"HTTP/1.1 404 Not Found", false, "<Error><Code>Monster.NotFound</Code></Error>",
|
||||
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,
|
||||
"<Error><Code>Monster.NotFound</Code></Error>", KeyNotFoundException.class);
|
||||
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404,
|
||||
"HTTP/1.1 404 Not Found", false, "<Error><Code>Monster.NotFound</Code></Error>",
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<String, String> goodMeta;
|
||||
private Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class PutBucketOptionsTest {
|
|||
|
||||
PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ);
|
||||
|
||||
options.setHeaderTag("amz");
|
||||
options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
|
||||
Multimap<String, String> headers = options.buildRequestHeaders();
|
||||
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
||||
CannedAccessPolicy.AUTHENTICATED_READ.toString());
|
||||
|
|
|
@ -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<String, String> headers = options.buildRequestHeaders();
|
||||
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
|
||||
|
|
Loading…
Reference in New Issue