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:
Adrian Cole 2011-02-26 21:55:22 -08:00
parent 71eb7ea8b7
commit 01ef7140db
12 changed files with 368 additions and 296 deletions

View File

@ -32,6 +32,7 @@ import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCK
import java.util.Properties; import java.util.Properties;
import org.jclouds.PropertiesBuilder; import org.jclouds.PropertiesBuilder;
import org.jclouds.s3.reference.S3Headers;
/** /**
* Builds properties used in S3 Connections * Builds properties used in S3 Connections
@ -44,7 +45,7 @@ public class S3PropertiesBuilder extends PropertiesBuilder {
Properties properties = super.defaultProperties(); Properties properties = super.defaultProperties();
properties.setProperty(PROPERTY_API_VERSION, S3AsyncClient.VERSION); properties.setProperty(PROPERTY_API_VERSION, S3AsyncClient.VERSION);
properties.setProperty(PROPERTY_AUTH_TAG, "AWS"); 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_SERVICE_PATH, "/");
properties.setProperty(PROPERTY_S3_VIRTUAL_HOST_BUCKETS, "true"); properties.setProperty(PROPERTY_S3_VIRTUAL_HOST_BUCKETS, "true");
properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true"); properties.setProperty(PROPERTY_RELAX_HOSTNAME, "true");
@ -67,8 +68,8 @@ public class S3PropertiesBuilder extends PropertiesBuilder {
protected void setMetaPrefix() { protected void setMetaPrefix() {
if (properties.getProperty(PROPERTY_USER_METADATA_PREFIX) == null) { if (properties.getProperty(PROPERTY_USER_METADATA_PREFIX) == null) {
properties.setProperty(PROPERTY_USER_METADATA_PREFIX, properties.setProperty(PROPERTY_USER_METADATA_PREFIX, String.format("x-%s-meta-", properties
String.format("x-%s-meta-", properties.getProperty(PROPERTY_HEADER_TAG))); .getProperty(PROPERTY_HEADER_TAG)));
} }
} }

View File

@ -20,13 +20,17 @@
package org.jclouds.s3.filters; package org.jclouds.s3.filters;
import static com.google.common.base.Preconditions.checkArgument; 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_CREDENTIAL;
import static org.jclouds.Constants.PROPERTY_IDENTITY; 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_AUTH_TAG;
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_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_SERVICE_PATH;
import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS; 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.lang.annotation.Annotation;
import java.util.Arrays; import java.util.Arrays;
@ -43,8 +47,6 @@ import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.s3.Bucket;
import org.jclouds.s3.reference.S3Headers;
import org.jclouds.crypto.Crypto; import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.CryptoStreams; import org.jclouds.crypto.CryptoStreams;
import org.jclouds.date.TimeStamp; import org.jclouds.date.TimeStamp;
@ -53,43 +55,44 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire; import org.jclouds.http.internal.SignatureWire;
import org.jclouds.http.utils.ModifyRequest;
import org.jclouds.io.InputSuppliers; import org.jclouds.io.InputSuppliers;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.rest.RequestSigner; import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.internal.GeneratedHttpRequest; 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.annotations.VisibleForTesting;
import com.google.common.base.Predicate; 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.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap; 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.SortedSetMultimap;
import com.google.common.collect.TreeMultimap; import com.google.common.collect.TreeMultimap;
/** /**
* Signs the S3 request. * 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 * @author Adrian Cole
* *
*/ */
@Singleton @Singleton
public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSigner { 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 }; private final String[] firstHeadersToSign = new String[] { HttpHeaders.DATE };
/** Prefix for general Amazon headers: x-amz- */ public static Set<String> SIGNED_PARAMETERS = ImmutableSet.of("acl", "torrent", "logging", "location", "policy",
public static final String AMAZON_PREFIX = "x-amz-"; "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<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; private final SignatureWire signatureWire;
private final String accessKey; private final String accessKey;
private final String secretKey; private final String secretKey;
@ -133,31 +136,27 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
} }
HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) { HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
request = ModifyRequest.replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":" request = replaceHeader(request, HttpHeaders.AUTHORIZATION, authTag + " " + accessKey + ":" + signature);
+ signature);
return request; return request;
} }
HttpRequest replaceDateHeader(HttpRequest request) { HttpRequest replaceDateHeader(HttpRequest request) {
Builder<String, String> builder = ImmutableMap.builder(); request = replaceHeader(request, HttpHeaders.DATE, timeStampProvider.get());
String date = timeStampProvider.get();
builder.put(HttpHeaders.DATE, date);
request = ModifyRequest.replaceHeaders(request, Multimaps.forMap(builder.build()));
return request; return request;
} }
public String createStringToSign(HttpRequest request) { public String createStringToSign(HttpRequest request) {
utils.logRequest(signatureLog, request, ">>"); utils.logRequest(signatureLog, request, ">>");
SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create(); SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
// re-sign the request // re-sign the request
appendMethod(request, buffer); appendMethod(request, buffer);
appendPayloadMetadata(request, buffer); appendPayloadMetadata(request, buffer);
appendHttpHeaders(request, canonicalizedHeaders); appendHttpHeaders(request, canonicalizedHeaders);
// Remove default date timestamp if "x-amz-date" is set. // Remove default date timestamp if "x-amz-date" is set.
if (canonicalizedHeaders.containsKey(S3Headers.ALTERNATE_DATE)) { if (canonicalizedHeaders.containsKey("x-" + headerTag + "-date")) {
canonicalizedHeaders.put("date", ""); canonicalizedHeaders.removeAll("date");
} }
appendAmzHeaders(canonicalizedHeaders, buffer); appendAmzHeaders(canonicalizedHeaders, buffer);
@ -172,7 +171,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
String calculateSignature(String toSign) throws HttpException { String calculateSignature(String toSign) throws HttpException {
String signature = sign(toSign); String signature = sign(toSign);
if (signatureWire.enabled()) if (signatureWire.enabled())
signatureWire.input(Strings2.toInputStream(signature)); signatureWire.input(toInputStream(signature));
return signature; return signature;
} }
@ -196,35 +195,35 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
for (Entry<String, String> header : canonicalizedHeaders.entries()) { for (Entry<String, String> header : canonicalizedHeaders.entries()) {
String key = header.getKey(); String key = header.getKey();
if (key.startsWith("x-" + headerTag + "-")) { 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) { 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( buffer.append(
utils.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata() request.getPayload() == null ? utils.valueOrEmpty(request.getFirstHeaderOrNull("Content-MD5")) : utils
.getContentMD5())).append("\n"); .valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
.getContentMD5())).append("\n");
buffer.append( buffer.append(
utils.valueOrEmpty(request.getPayload() == null ? request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE) 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) for (String header : firstHeadersToSign)
buffer.append(valueOrEmpty(request.getHeaders().get(header))).append("\n"); buffer.append(valueOrEmpty(request.getHeaders().get(header))).append("\n");
} }
@VisibleForTesting @VisibleForTesting
void appendHttpHeaders(HttpRequest request, void appendHttpHeaders(HttpRequest request, SortedSetMultimap<String, String> canonicalizedHeaders) {
SortedSetMultimap<String, String> canonicalizedHeaders) {
Multimap<String, String> headers = request.getHeaders(); Multimap<String, String> headers = request.getHeaders();
for (Entry<String, String> header : headers.entries()) { for (Entry<String, String> header : headers.entries()) {
if (header.getKey() == null) if (header.getKey() == null)
continue; continue;
String key = header.getKey().toString() String key = header.getKey().toString().toLowerCase(Locale.getDefault());
.toLowerCase(Locale.getDefault());
// Ignore any headers that are not particularly interesting. // Ignore any headers that are not particularly interesting.
if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase("Content-MD5") 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()); canonicalizedHeaders.put(key, header.getValue());
} }
} }
@ -238,12 +237,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
String bucketName = null; String bucketName = null;
for (int i = 0; i < request.getJavaMethod().getParameterAnnotations().length; i++) { for (int i = 0; i < request.getJavaMethod().getParameterAnnotations().length; i++) {
if (Iterables.any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]), if (any(Arrays.asList(request.getJavaMethod().getParameterAnnotations()[i]), ANNOTATIONTYPE_BUCKET)) {
new Predicate<Annotation>() {
public boolean apply(Annotation input) {
return input.annotationType().equals(Bucket.class);
}
})) {
bucketName = (String) request.getArgs().get(i); bucketName = (String) request.getArgs().get(i);
break; break;
} }
@ -261,20 +255,14 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
// ...however, there are a few exceptions that must be included in the // ...however, there are a few exceptions that must be included in the
// signed URI. // signed URI.
if (request.getEndpoint().getQuery() != null) { if (request.getEndpoint().getQuery() != null) {
SortedSetMultimap<String, String> sortedParams = TreeMultimap.create(); Multimap<String, String> params = parseQueryToMap(request.getEndpoint().getQuery());
String[] params = request.getEndpoint().getQuery().split("&");
for (String param : params) {
String[] paramNameAndValue = param.split("=");
sortedParams.put(paramNameAndValue[0], paramNameAndValue.length == 2 ? paramNameAndValue[1] : null);
}
char separator = '?'; char separator = '?';
for (Entry<String, String> param: sortedParams.entries()) { for (String paramName : Ordering.natural().sortedCopy(params.keySet())) {
String paramName = param.getKey();
// Skip any parameters that aren't part of the canonical signed string // 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); toSign.append(separator).append(paramName);
String paramValue = param.getValue(); String paramValue = get(params.get(paramName), 0);
if (paramValue != null) { if (paramValue != null) {
toSign.append("=").append(paramValue); toSign.append("=").append(paramValue);
} }

View File

@ -21,18 +21,21 @@ package org.jclouds.s3.functions;
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX; 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.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.ws.rs.core.HttpHeaders; 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.domain.BlobMetadata;
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders; import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
import org.jclouds.crypto.CryptoStreams; import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.rest.InvocationContext; 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.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
@ -45,19 +48,23 @@ import com.google.common.base.Function;
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, MutableObjectMetadata>, public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, MutableObjectMetadata>,
InvocationContext<ParseObjectMetadataFromHeaders> { InvocationContext<ParseObjectMetadataFromHeaders> {
private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser; private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser;
private final BlobToObjectMetadata blobToObjectMetadata; private final BlobToObjectMetadata blobToObjectMetadata;
private final String userMdPrefix; private final String userMdPrefix;
@Inject @Inject
public ParseObjectMetadataFromHeaders(ParseSystemAndUserMetadataFromHeaders blobMetadataParser, 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.blobMetadataParser = blobMetadataParser;
this.blobToObjectMetadata = blobToObjectMetadata; this.blobToObjectMetadata = blobToObjectMetadata;
this.userMdPrefix = userMdPrefix; 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 * parses the http response headers to create a new
* {@link org.jclouds.s3.domain.internal.MutableObjectMetadata} object. * {@link org.jclouds.s3.domain.internal.MutableObjectMetadata} object.
@ -65,13 +72,20 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
public MutableObjectMetadata apply(HttpResponse from) { public MutableObjectMetadata apply(HttpResponse from) {
BlobMetadata base = blobMetadataParser.apply(from); BlobMetadata base = blobMetadataParser.apply(from);
MutableObjectMetadata to = blobToObjectMetadata.apply(base); MutableObjectMetadata to = blobToObjectMetadata.apply(base);
addETagTo(from, to); addETagTo(from, to);
if (to.getContentMetadata().getContentMD5() == null && to.getETag() != null) { if (to.getContentMetadata().getContentMD5() == null && to.getETag() != null) {
byte[] md5 = CryptoStreams.hex(to.getETag().replaceAll("\"", "")); Matcher md5Matcher = MD5_FROM_ETAG.matcher(to.getETag());
// it is possible others will look at the http payload directly if (md5Matcher.find()) {
from.getPayload().getContentMetadata().setContentMD5(md5); byte[] md5 = CryptoStreams.hex(md5Matcher.group(1));
to.getContentMetadata().setContentMD5(md5); // 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)); to.setCacheControl(from.getFirstHeaderOrNull(HttpHeaders.CACHE_CONTROL));
return to; return to;
} }
@ -95,4 +109,8 @@ public class ParseObjectMetadataFromHeaders implements Function<HttpResponse, Mu
return this; return this;
} }
public ParseObjectMetadataFromHeaders setKey(String key) {
blobMetadataParser.setName(key);
return this;
}
} }

View File

@ -23,6 +23,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; 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.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.io.UnsupportedEncodingException;
import java.util.Date; import java.util.Date;
@ -32,14 +39,13 @@ import java.util.Map.Entry;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; 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.DateService;
import org.jclouds.date.internal.SimpleDateFormatDateService; import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.http.options.BaseHttpRequestOptions; import org.jclouds.http.options.BaseHttpRequestOptions;
import org.jclouds.s3.domain.CannedAccessPolicy;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
/** /**
@ -99,7 +105,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) { public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
this.acl = checkNotNull(acl, "acl"); this.acl = checkNotNull(acl, "acl");
if (!acl.equals(CannedAccessPolicy.PRIVATE)) if (!acl.equals(CannedAccessPolicy.PRIVATE))
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString()); this.replaceHeader(CANNED_ACL, acl.toString());
return this; return this;
} }
@ -124,7 +130,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* @see CopyObjectOptions#ifSourceModifiedSince(Date) * @see CopyObjectOptions#ifSourceModifiedSince(Date)
*/ */
public String getIfModifiedSince() { 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) * @see CopyObjectOptions#ifSourceUnmodifiedSince(Date)
*/ */
public String getIfUnmodifiedSince() { 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) * @see CopyObjectOptions#ifSourceETagMatches(String)
*/ */
public String getIfMatch() { 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) * @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
*/ */
public String getIfNoneMatch() { 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) { public CopyObjectOptions ifSourceModifiedSince(Date ifModifiedSince) {
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()"); checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifModifiedSince()");
checkState(getIfUnmodifiedSince() == null, checkState(getIfUnmodifiedSince() == null, "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
"ifUnmodifiedSince() is not compatible with ifModifiedSince()"); replaceHeader(COPY_SOURCE_IF_MODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifModifiedSince,
replaceHeader("x-amz-copy-source-if-modified-since", dateService "ifModifiedSince")));
.rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince")));
return this; return this;
} }
@ -206,12 +211,10 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* {@link #ifSourceModifiedSince(Date)} * {@link #ifSourceModifiedSince(Date)}
*/ */
public CopyObjectOptions ifSourceUnmodifiedSince(Date ifUnmodifiedSince) { public CopyObjectOptions ifSourceUnmodifiedSince(Date ifUnmodifiedSince) {
checkState(getIfNoneMatch() == null, checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()");
"ifETagDoesntMatch() is not compatible with ifUnmodifiedSince()"); checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifUnmodifiedSince()");
checkState(getIfModifiedSince() == null, replaceHeader(COPY_SOURCE_IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(ifUnmodifiedSince,
"ifModifiedSince() is not compatible with ifUnmodifiedSince()"); "ifUnmodifiedSince")));
replaceHeader("x-amz-copy-source-if-unmodified-since", dateService
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince")));
return this; return this;
} }
@ -226,12 +229,9 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* hash representing the payload * hash representing the payload
*/ */
public CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException { public CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
checkState(getIfNoneMatch() == null, checkState(getIfNoneMatch() == null, "ifETagDoesntMatch() is not compatible with ifETagMatches()");
"ifETagDoesntMatch() is not compatible with ifETagMatches()"); checkState(getIfModifiedSince() == null, "ifModifiedSince() is not compatible with ifETagMatches()");
checkState(getIfModifiedSince() == null, replaceHeader(COPY_SOURCE_IF_MATCH, String.format("\"%1$s\"", checkNotNull(eTag, "eTag")));
"ifModifiedSince() is not compatible with ifETagMatches()");
replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", checkNotNull(eTag,
"eTag")));
return this; return this;
} }
@ -246,13 +246,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* @throws UnsupportedEncodingException * @throws UnsupportedEncodingException
* if there was a problem converting this into an S3 eTag string * if there was a problem converting this into an S3 eTag string
*/ */
public CopyObjectOptions ifSourceETagDoesntMatch(String eTag) public CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()"); checkState(getIfMatch() == null, "ifETagMatches() is not compatible with ifETagDoesntMatch()");
Preconditions.checkState(getIfUnmodifiedSince() == null, Preconditions.checkState(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()"); "ifUnmodifiedSince() is not compatible with ifETagDoesntMatch()");
replaceHeader("x-amz-copy-source-if-none-match", String.format("\"%1$s\"", checkNotNull(eTag, replaceHeader(COPY_SOURCE_IF_NO_MATCH, String.format("\"%s\"", checkNotNull(eTag, "ifETagDoesntMatch")));
"ifETagDoesntMatch")));
return this; return this;
} }
@ -260,18 +258,17 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
public Multimap<String, String> buildRequestHeaders() { public Multimap<String, String> buildRequestHeaders() {
checkState(headerTag != null, "headerTag should have been injected!"); checkState(headerTag != null, "headerTag should have been injected!");
checkState(metadataPrefix != null, "metadataPrefix 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()) { 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) { if (metadata != null) {
returnVal.put(METADATA_DIRECTIVE.replace(DEFAULT_AMAZON_HEADERTAG, headerTag), "REPLACE");
for (String key : metadata.keySet()) { for (String key : metadata.keySet()) {
returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata returnVal.put(key.startsWith(metadataPrefix) ? key : metadataPrefix + key, metadata.get(key));
.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) * @see CopyObjectOptions#ifSourceETagMatches(String)
*/ */
public static CopyObjectOptions ifSourceETagMatches(String eTag) public static CopyObjectOptions ifSourceETagMatches(String eTag) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceETagMatches(eTag); return options.ifSourceETagMatches(eTag);
} }
@ -320,8 +316,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* @see CopyObjectOptions#ifSourceETagDoesntMatch(String) * @see CopyObjectOptions#ifSourceETagDoesntMatch(String)
*/ */
public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag) public static CopyObjectOptions ifSourceETagDoesntMatch(String eTag) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceETagDoesntMatch(eTag); return options.ifSourceETagDoesntMatch(eTag);
} }

View File

@ -19,28 +19,30 @@
package org.jclouds.s3.options; 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 java.util.Map.Entry;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; 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.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 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. * Contains options supported in the REST API for the PUT object operation.
* <p/> * <p/>
* <h2> * <h2>
* Usage</h2> The recommended way to instantiate a PutObjectOptions object is to * Usage</h2> The recommended way to instantiate a PutObjectOptions object is to statically import
* statically import PutObjectOptions.Builder.* and invoke a static creation * PutObjectOptions.Builder.* and invoke a static creation method followed by an instance mutator
* method followed by an instance mutator (if needed): * (if needed):
* <p/> * <p/>
* <code> * <code>
* import static org.jclouds.s3.commands.options.PutObjectOptions.Builder.* * import static org.jclouds.s3.commands.options.PutObjectOptions.Builder.*
@ -50,60 +52,62 @@ 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)); * Future<Boolean> publicly readable = connection.putObject("bucketName",new S3Object("key","value"), withAcl(CannedAccessPolicy.PUBLIC_READ));
* <code> * <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 * @author Adrian Cole
* *
*/ */
public class PutObjectOptions extends BaseHttpRequestOptions { 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 CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
private String headerTag;
@Inject private String headerTag;
public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) {
this.headerTag = headerTag;
}
@Override @Inject
public Multimap<String, String> buildRequestHeaders() { public void setHeaderTag(@Named(PROPERTY_HEADER_TAG) String headerTag) {
checkState(headerTag != null, "headerTag should have been injected!"); this.headerTag = headerTag;
Multimap<String, String> returnVal = LinkedHashMultimap.create(); }
for (Entry<String, String> 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;
}
/** @Override
* @see PutObjectOptions#withAcl(CannedAccessPolicy) public Multimap<String, String> buildRequestHeaders() {
*/ checkState(headerTag != null, "headerTag should have been injected!");
public CannedAccessPolicy getAcl() { ImmutableMultimap.Builder<String, String> returnVal = ImmutableMultimap.<String, String> builder();
return acl; for (Entry<String, String> 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) * @see PutObjectOptions#withAcl(CannedAccessPolicy)
*/ */
public static PutObjectOptions withAcl(CannedAccessPolicy acl) { public CannedAccessPolicy getAcl() {
PutObjectOptions options = new PutObjectOptions(); return acl;
return options.withAcl(acl); }
}
} public static class Builder {
/**
* @see PutObjectOptions#withAcl(CannedAccessPolicy)
*/
public static PutObjectOptions withAcl(CannedAccessPolicy acl) {
PutObjectOptions options = new PutObjectOptions();
return options.withAcl(acl);
}
}
} }

View File

@ -30,83 +30,105 @@ package org.jclouds.s3.reference;
*/ */
public interface S3Headers { 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 HEADER_PREFIX = "x-" + DEFAULT_AMAZON_HEADERTAG + "-";
public static final String AMAZON_PREFIX = "x-amz-";
/** /**
* The canned ACL to apply to the object. Options include private, public-read, * 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 * public-read-write, and authenticated-read. For more information, see REST Access Control
* Policy. * 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";
/** Amazon's alternative date header: x-amz-date */
public static final String ALTERNATE_DATE = "x-amz-date";
/** Prefix for user metadata: x-amz-meta- */ public static final String AMZ_ETAG = HEADER_PREFIX + "meta-object-eTag";
public static final String USER_METADATA_PREFIX = "x-amz-meta-";
/** 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"; * Instruction file header to be placed in the metadata of instruction files
*/
/** Initialization vector (IV) header that is used in the symmetric and envelope encryption mechanisms */ public static final String CRYPTO_INSTRUCTION_FILE = HEADER_PREFIX + "crypto-instr-file";
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";
} }

View File

@ -36,6 +36,7 @@ import org.jclouds.s3.domain.AccessControlList;
import org.jclouds.s3.domain.CannedAccessPolicy; import org.jclouds.s3.domain.CannedAccessPolicy;
import org.jclouds.s3.domain.S3Object; import org.jclouds.s3.domain.S3Object;
import org.jclouds.s3.options.PutObjectOptions; import org.jclouds.s3.options.PutObjectOptions;
import org.jclouds.s3.reference.S3Headers;
import org.testng.annotations.DataProvider; import org.testng.annotations.DataProvider;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -72,13 +73,13 @@ public class RequestAuthorizeSignatureTest extends BaseS3AsyncClientTest<S3Async
*/ */
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000) @Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 10000)
void testIdempotent(HttpRequest request) { void testIdempotent(HttpRequest request) {
filter.filter(request); request = filter.filter(request);
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION); String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE); String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
int iterations = 1; int iterations = 1;
while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) { while (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
date = request.getFirstHeaderOrNull(HttpHeaders.DATE); date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
filter.filter(request); request = filter.filter(request);
if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) if (request.getFirstHeaderOrNull(HttpHeaders.DATE).equals(date))
assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format( assert signature.equals(request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION)) : String.format(
"sig: %s != %s on attempt %s", signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION), "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); filter.appendHttpHeaders(request, canonicalizedHeaders);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
filter.appendAmzHeaders(canonicalizedHeaders, builder); 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 { private HttpRequest putObject() throws NoSuchMethodException {
S3Object object = blobToS3Object.apply(BindBlobToMultipartFormTest.TEST_BLOB); 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, HttpRequest request = processor.createRequest(S3AsyncClient.class.getMethod("putObject", String.class,
S3Object.class, PutObjectOptions[].class), "bucket", object); S3Object.class, PutObjectOptions[].class), "bucket", object);
return request; return request;

View File

@ -19,31 +19,34 @@
package org.jclouds.s3.functions; package org.jclouds.s3.functions;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock; 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 static org.testng.Assert.assertEquals;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import javax.ws.rs.core.HttpHeaders; 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.MutableObjectMetadata;
import org.jclouds.s3.domain.ObjectMetadata.StorageClass; import org.jclouds.s3.domain.ObjectMetadata.StorageClass;
import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl; import org.jclouds.s3.domain.internal.MutableObjectMetadataImpl;
import org.jclouds.blobstore.domain.MutableBlobMetadata; import org.jclouds.s3.reference.S3Headers;
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.testng.annotations.BeforeTest; import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap; 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 * @author Adrian Cole
@ -52,65 +55,79 @@ import com.google.common.collect.ImmutableMultimap;
public class ParseObjectMetadataFromHeadersTest { public class ParseObjectMetadataFromHeadersTest {
@Test @Test
void testNormal() throws Exception { void testNormalParsesETagIntoMD5AndMetadataHeaders() throws Exception {
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of( 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().setContentLength(1025l);
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition"); http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
http.getPayload().getContentMetadata().setContentEncoding("encoding"); 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); 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); assertEquals(response, expects);
} }
@Test @Test
void testAmzEtag() throws Exception { void testMultipartDoesntAttemptToParseETagIntoMD5() throws Exception {
HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of( 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().setContentLength(1025l);
http.getPayload().getContentMetadata().setContentDisposition("contentDisposition"); http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
http.getPayload().getContentMetadata().setContentEncoding("encoding"); http.getPayload().getContentMetadata().setContentEncoding("encoding");
ParseObjectMetadataFromHeaders parser = new ParseObjectMetadataFromHeaders(blobParser(http, null), http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
blobToObjectMetadata, "x-amz-meta-");
MutableObjectMetadata response = parser.apply(http); 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); assertEquals(response, expects);
} }
Date now = new Date(); @Test
Map<String, String> userMetadata = ImmutableMap.of("foo", "bar"); void testAmzEtagStillParsesToMD5AndDoesntMistakeAmzEtagForUserMetadata() throws Exception {
private MutableObjectMetadataImpl expects;
BlobToObjectMetadata blobToObjectMetadata;
private ParseSystemAndUserMetadataFromHeaders blobParser(HttpResponse response, String etag) { HttpResponse http = new HttpResponse(400, "boa", Payloads.newStringPayload(""), ImmutableMultimap.of(
ParseSystemAndUserMetadataFromHeaders parser = createMock(ParseSystemAndUserMetadataFromHeaders.class); S3Headers.USER_METADATA_PREFIX + "foo", "bar", HttpHeaders.LAST_MODIFIED, lastModified,
MutableBlobMetadata md = new MutableBlobMetadataImpl(); HttpHeaders.CACHE_CONTROL, "cacheControl", S3Headers.AMZ_ETAG, "\"abcd\""));
md.getContentMetadata().setContentType("type"); http.getPayload().getContentMetadata().setContentLength(1025l);
md.setETag(etag); http.getPayload().getContentMetadata().setContentDisposition("contentDisposition");
md.setName("key"); http.getPayload().getContentMetadata().setContentEncoding("encoding");
md.setLastModified(now); http.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
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;
}
@BeforeTest MutableObjectMetadata response = parser.apply(http);
void setUp() {
blobToObjectMetadata = new BlobToObjectMetadata(); MutableObjectMetadataImpl expects = new MutableObjectMetadataImpl();
expects = new MutableObjectMetadataImpl();
expects.setCacheControl("cacheControl"); expects.setCacheControl("cacheControl");
expects.getContentMetadata().setContentDisposition("contentDisposition"); expects.getContentMetadata().setContentDisposition("contentDisposition");
expects.getContentMetadata().setContentEncoding("encoding"); expects.getContentMetadata().setContentEncoding("encoding");
expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd")); expects.getContentMetadata().setContentMD5(CryptoStreams.hex("abcd"));
expects.getContentMetadata().setContentType("type"); expects.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
expects.getContentMetadata().setContentLength(1025l); expects.getContentMetadata().setContentLength(1025l);
expects.setETag("\"abcd\""); expects.setETag("\"abcd\"");
expects.setKey("key"); expects.setKey("key");
@ -118,5 +135,29 @@ public class ParseObjectMetadataFromHeadersTest {
expects.setOwner(null); expects.setOwner(null);
expects.setStorageClass(StorageClass.STANDARD); expects.setStorageClass(StorageClass.STANDARD);
expects.setUserMetadata(userMetadata); 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");
} }
} }

View File

@ -39,6 +39,7 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.config.SaxParserModule; import org.jclouds.http.functions.config.SaxParserModule;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.rest.RequestSigner; import org.jclouds.rest.RequestSigner;
import org.jclouds.s3.reference.S3Headers;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -57,16 +58,17 @@ public class ParseS3ErrorFromXmlContentTest {
@Test @Test
public void test404ContainerNotFoundExceptionPath() { public void test404ContainerNotFoundExceptionPath() {
assertCodeMakes("GET", URI assertCodeMakes("GET", URI
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404, "HTTP/1.1 404 Not Found", false, .create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/"), 404,
"<Error><Code>Monster.NotFound</Code></Error>", ContainerNotFoundException.class); "HTTP/1.1 404 Not Found", false, "<Error><Code>Monster.NotFound</Code></Error>",
ContainerNotFoundException.class);
} }
@Test @Test
public void test404KeyNotFoundExceptionPath() { public void test404KeyNotFoundExceptionPath() {
assertCodeMakes("GET", URI assertCodeMakes("GET", URI
.create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404, "HTTP/1.1 404 Not Found", false, .create("http://partnercloud.eucalyptus.com:8773/services/Walrus/adriancole-blobstore58/apples"), 404,
"<Error><Code>Monster.NotFound</Code></Error>", KeyNotFoundException.class); "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, private void assertCodeMakes(String method, URI uri, int statusCode, String message, final boolean virtualHost,
@ -77,7 +79,7 @@ public class ParseS3ErrorFromXmlContentTest {
@Override @Override
protected void configure() { protected void configure() {
bind(RequestSigner.class).toInstance(createMock(RequestSigner.class)); 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_SERVICE_PATH)).to(SERVICE_PATH);
bindConstant().annotatedWith(Names.named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS)).to(virtualHost); bindConstant().annotatedWith(Names.named(PROPERTY_S3_VIRTUAL_HOST_BUCKETS)).to(virtualHost);
} }

View File

@ -19,12 +19,19 @@
package org.jclouds.s3.options; 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.ifSourceETagDoesntMatch;
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceETagMatches; 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.ifSourceModifiedSince;
import static org.jclouds.s3.options.CopyObjectOptions.Builder.ifSourceUnmodifiedSince; 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.overrideAcl;
import static org.jclouds.s3.options.CopyObjectOptions.Builder.overrideMetadataWith; 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.assertEquals;
import static org.testng.Assert.assertNull; import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertTrue;
@ -33,13 +40,12 @@ import java.io.UnsupportedEncodingException;
import java.util.Date; import java.util.Date;
import java.util.Map; 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.date.internal.SimpleDateFormatDateService;
import org.jclouds.s3.domain.CannedAccessPolicy;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.Maps; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
/** /**
@ -54,14 +60,10 @@ public class CopyObjectOptionsTest {
private Date now; private Date now;
private String nowExpected; private String nowExpected;
private Map<String, String> goodMeta; private Map<String, String> goodMeta;
private Map<String, String> badMeta;
@BeforeMethod @BeforeMethod
void setUp() { void setUp() {
goodMeta = Maps.newHashMap(); goodMeta = ImmutableMap.of(USER_METADATA_PREFIX + "adrian", "foo");
goodMeta.put("x-amz-meta-adrian", "foo");
badMeta = Maps.newHashMap();
badMeta.put("x-google-meta-adrian", "foo");
now = new Date(); now = new Date();
nowExpected = new SimpleDateFormatDateService().rfc822DateFormat(now); nowExpected = new SimpleDateFormatDateService().rfc822DateFormat(now);
etag = "mama"; etag = "mama";
@ -70,8 +72,8 @@ public class CopyObjectOptionsTest {
@Test @Test
void testGoodMetaStatic() { void testGoodMetaStatic() {
CopyObjectOptions options = overrideMetadataWith(goodMeta); CopyObjectOptions options = overrideMetadataWith(goodMeta);
options.setMetadataPrefix("x-amz-meta-"); options.setMetadataPrefix(USER_METADATA_PREFIX);
options.setHeaderTag("amz"); options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
assertGoodMeta(options); assertGoodMeta(options);
} }
@ -85,17 +87,17 @@ public class CopyObjectOptionsTest {
assert options.getMetadata() != null; assert options.getMetadata() != null;
Multimap<String, String> headers = options.buildRequestHeaders(); Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.size(), 2); 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(options.getMetadata().size(), 1);
assertEquals(headers.get("x-amz-meta-adrian").iterator().next(), "foo"); assertEquals(headers.get(USER_METADATA_PREFIX + "adrian").iterator().next(), "foo");
assertEquals(options.getMetadata().get("x-amz-meta-adrian"), "foo"); assertEquals(options.getMetadata().get(USER_METADATA_PREFIX + "adrian"), "foo");
} }
@Test @Test
void testGoodMeta() { void testGoodMeta() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
options.setHeaderTag("amz"); options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
options.setMetadataPrefix("x-amz-meta-"); options.setMetadataPrefix(USER_METADATA_PREFIX);
options.overrideMetadataWith(goodMeta); options.overrideMetadataWith(goodMeta);
assertGoodMeta(options); assertGoodMeta(options);
} }
@ -272,25 +274,24 @@ public class CopyObjectOptionsTest {
@Test @Test
void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException { void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); 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; assert options.buildRequestHeaders() != null;
} }
@Test @Test
void testBuildRequestHeaders() throws UnsupportedEncodingException { void testBuildRequestHeaders() throws UnsupportedEncodingException {
CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag) CopyObjectOptions options = ifSourceModifiedSince(now).ifSourceETagDoesntMatch(etag).overrideMetadataWith(
.overrideMetadataWith(goodMeta); goodMeta);
options.setHeaderTag("amz"); options.setHeaderTag(DEFAULT_AMAZON_HEADERTAG);
options.setMetadataPrefix("x-amz-meta-"); options.setMetadataPrefix(USER_METADATA_PREFIX);
Multimap<String, String> headers = options.buildRequestHeaders(); Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get("x-amz-copy-source-if-modified-since").iterator().next(), assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_MODIFIED_SINCE)), new SimpleDateFormatDateService()
new SimpleDateFormatDateService().rfc822DateFormat(now)); .rfc822DateFormat(now));
assertEquals(headers.get("x-amz-copy-source-if-none-match").iterator().next(), "\"" + etag assertEquals(getOnlyElement(headers.get(COPY_SOURCE_IF_NO_MATCH)), "\"" + etag + "\"");
+ "\"");
for (String value : goodMeta.values()) for (String value : goodMeta.values())
assertTrue(headers.containsValue(value)); assertTrue(headers.containsValue(value));
@ -311,13 +312,12 @@ public class CopyObjectOptionsTest {
@Test @Test
void testBuildRequestHeadersACL() throws UnsupportedEncodingException { void testBuildRequestHeadersACL() throws UnsupportedEncodingException {
CopyObjectOptions options = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ); 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(); Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(), assertEquals(headers.get(CANNED_ACL).iterator().next(), CannedAccessPolicy.AUTHENTICATED_READ.toString());
CannedAccessPolicy.AUTHENTICATED_READ.toString());
} }
} }

View File

@ -55,7 +55,7 @@ public class PutBucketOptionsTest {
PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ); PutBucketOptions options = withBucketAcl(CannedAccessPolicy.AUTHENTICATED_READ);
options.setHeaderTag("amz"); options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
Multimap<String, String> headers = options.buildRequestHeaders(); Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(), assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
CannedAccessPolicy.AUTHENTICATED_READ.toString()); CannedAccessPolicy.AUTHENTICATED_READ.toString());

View File

@ -54,7 +54,7 @@ public class PutObjectOptionsTest {
void testBuildRequestHeaders() throws UnsupportedEncodingException { void testBuildRequestHeaders() throws UnsupportedEncodingException {
PutObjectOptions options = withAcl(CannedAccessPolicy.AUTHENTICATED_READ); PutObjectOptions options = withAcl(CannedAccessPolicy.AUTHENTICATED_READ);
options.setHeaderTag("amz"); options.setHeaderTag(S3Headers.DEFAULT_AMAZON_HEADERTAG);
Multimap<String, String> headers = options.buildRequestHeaders(); Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(), assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),