// This software code is made available "AS IS" without warranties of any
// kind. You may copy, display, modify and redistribute the software
// code either by itself or as incorporated into your code; provided that
// you do not remove any proprietary notices. Your use of this software
// code is at your own risk and you waive any claim against Amazon
// Digital Services, Inc. or its affiliates with respect to your use of
// this software code. (c) 2006 Amazon Digital Services, Inc. or its
// affiliates.
// Copyright (c) 2006 SilvaSoft, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// author: http://www.silvasoftinc.com
// author: Dominic Da Silva (dominic.dasilva@gmail.com)
// version: 1.1
// date: 04/09/2006
package com.amazon.s3;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.silvasoftinc.s3.S3StreamObject;
/**
* An interface into the S3 system. It is initially configured with
* authentication and connection parameters and exposes methods to access and
* manipulate S3 data.
*/
public class AWSAuthConnection {
private String awsAccessKeyId;
private String awsSecretAccessKey;
private boolean isSecure;
private String server;
private int port;
public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey) {
this(awsAccessKeyId, awsSecretAccessKey, true);
}
public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey,
boolean isSecure) {
this(awsAccessKeyId, awsSecretAccessKey, isSecure, Utils.DEFAULT_HOST);
}
public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey,
boolean isSecure, String server) {
this(awsAccessKeyId, awsSecretAccessKey, isSecure, server,
isSecure ? Utils.SECURE_PORT : Utils.INSECURE_PORT);
}
/**
* Create a new interface to interact with S3 with the given credential and
* connection parameters
*
* @param awsAccessKeyId
* The your user key into AWS
* @param awsSecretAccessKey
* The secret string used to generate signatures for
* authentication.
* @param isSecure
* True if the data should be encrypted on the wire on the way to
* or from S3.
* @param server
* Which host to connect to. Usually, this will be
* s3.amazonaws.com
* @param port
* Which port to use.
*/
public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey,
boolean isSecure, String server, int port) {
this.awsAccessKeyId = awsAccessKeyId;
this.awsSecretAccessKey = awsSecretAccessKey;
this.isSecure = isSecure;
this.server = server;
this.port = port;
}
/**
* Creates a new bucket.
*
* @param bucket
* The name of the bucket to create.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response createBucket(String bucket, Map headers)
throws MalformedURLException, IOException {
return new Response(makeRequest("PUT", bucket, headers));
}
/**
* Lists the contents of a bucket.
*
* @param bucket
* The name of the bucket to create.
* @param prefix
* All returned keys will start with this string (can be null).
* @param marker
* All returned keys will be lexographically greater than this
* string (can be null).
* @param maxKeys
* The maximum number of keys to return (can be null).
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public ListBucketResponse listBucket(String bucket, String prefix,
String marker, Integer maxKeys, Map headers)
throws MalformedURLException, IOException {
String path = Utils.pathForListOptions(bucket, prefix, marker, maxKeys);
return new ListBucketResponse(makeRequest("GET", path, headers));
}
/**
* Deletes a bucket.
*
* @param bucket
* The name of the bucket to delete.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response deleteBucket(String bucket, Map headers)
throws MalformedURLException, IOException {
return new Response(makeRequest("DELETE", bucket, headers));
}
/**
* Writes an object to S3.
*
* @param bucket
* The name of the bucket to which the object will be added.
* @param key
* The name of the key to use.
* @param object
* An S3Object containing the data to write.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response put(String bucket, String key, S3Object object, Map headers)
throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
HttpURLConnection request = makeRequest("PUT", bucket + pathSep
+ Utils.urlencode(key), headers, object);
request.setDoOutput(true);
request.getOutputStream().write(
object.data == null ? new byte[] {} : object.data);
return new Response(request);
}
/**
* Writes an object to S3.
*
* @param bucket
* The name of the bucket to which the object will be added.
* @param key
* The name of the key to use.
* @param object
* An S3StreamObject containing the data stream to write.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response putStream(String bucket, String key, S3StreamObject object,
Map headers) throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
HttpURLConnection request = makeStreamRequest("PUT", bucket + pathSep
+ Utils.urlencode(key), headers, object);
request.setDoOutput(true);
if (object.length != 0) {
request.setFixedLengthStreamingMode((int) object.length);
}
byte[] buf = new byte[1024];
int bytesRead = 0;
try (OutputStream out = request.getOutputStream()) {
while ((bytesRead = object.stream.read(buf)) > 0) {
out.write(buf, 0, bytesRead);
}
}
return new Response(request);
}
/**
* Reads an object from S3.
*
* @param bucket
* The name of the bucket where the object lives.
* @param key
* The name of the key to use.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public GetResponse get(String bucket, String key, Map headers)
throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
return new GetResponse(makeRequest("GET", bucket + pathSep
+ Utils.urlencode(key), headers));
}
/**
* Reads an object from S3 using streaming.
*
* @param bucket
* The name of the bucket where the object lives.
* @param key
* The name of the key to use.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public GetStreamResponse getStream(String bucket, String key, Map headers)
throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
return new GetStreamResponse(makeRequest("GET", bucket + pathSep
+ Utils.urlencode(key), headers));
}
/**
* Reads a BitTorrent file (.torrent) for an object from S3.
*
* @param bucket
* The name of the bucket where the object lives.
* @param key
* The name of the key to use.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public GetResponse getTorrent(String bucket, String key, Map headers)
throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
return new GetResponse(makeRequest("GET", bucket + pathSep
+ Utils.urlencode(key) + "?torrent", headers));
}
/**
* Deletes an object from S3.
*
* @param bucket
* The name of the bucket where the object lives.
* @param key
* The name of the key to use.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response delete(String bucket, String key, Map headers)
throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
return new Response(makeRequest("DELETE", bucket + pathSep
+ Utils.urlencode(key), headers));
}
/**
* Get the ACL for a given bucket
*
* @param bucket
* The name of the bucket where the object lives.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public GetResponse getBucketACL(String bucket, Map headers)
throws MalformedURLException, IOException {
return getACL(bucket, "", headers);
}
/**
* Get the ACL for a given object (or bucket, if key is null).
*
* @param bucket
* The name of the bucket where the object lives.
* @param key
* The name of the key to use.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public GetResponse getACL(String bucket, String key, Map headers)
throws MalformedURLException, IOException {
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
return new GetResponse(makeRequest("GET", bucket + pathSep
+ Utils.urlencode(key) + "?acl", headers));
}
/**
* Write a new ACL for a given bucket
*
* @param aclXMLDoc
* The xml representation of the ACL as a String
* @param bucket
* The name of the bucket where the object lives.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response putBucketACL(String bucket, String aclXMLDoc, Map headers)
throws MalformedURLException, IOException {
return putACL(bucket, "", aclXMLDoc, headers);
}
/**
* Write a new ACL for a given object
*
* @param aclXMLDoc
* The xml representation of the ACL as a String
* @param bucket
* The name of the bucket where the object lives.
* @param key
* The name of the key to use.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public Response putACL(String bucket, String key, String aclXMLDoc,
Map headers) throws MalformedURLException, IOException {
S3Object object = new S3Object(aclXMLDoc.getBytes(), null);
boolean isEmptyKey = (key == null) || (key.length() == 0)
|| (key.trim().length() == 0);
String pathSep = isEmptyKey ? "" : "/";
if (key == null)
key = "";
HttpURLConnection request = makeRequest("PUT", bucket + pathSep
+ Utils.urlencode(key) + "?acl", headers, object);
request.setDoOutput(true);
request.getOutputStream().write(
object.data == null ? new byte[] {} : object.data);
return new Response(request);
}
/**
* List all the buckets created by this account.
*
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @return the response object
* @throws MalformedURLException
* @throws IOException
*/
public ListAllMyBucketsResponse listAllMyBuckets(Map headers)
throws MalformedURLException, IOException {
return new ListAllMyBucketsResponse(makeRequest("GET", "", headers));
}
/**
* Make a new HttpURLConnection without passing an S3Object parameter.
*/
private HttpURLConnection makeRequest(String method, String resource,
Map headers) throws MalformedURLException, IOException {
return makeRequest(method, resource, headers, null);
}
/**
* Make a new HttpURLConnection.
*
* @param method
* The HTTP method to use (GET, PUT, DELETE)
* @param resource
* The resource name (bucketName + "/" + key).
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @param object
* The S3Object that is to be written (can be null).
*/
private HttpURLConnection makeRequest(String method, String resource,
Map headers, S3Object object) throws MalformedURLException,
IOException {
URL url = makeURL(resource);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(3600000); // 1hr
connection.setRequestMethod(method);
addHeaders(connection, headers);
if (object != null)
addMetadataHeaders(connection, object.metadata);
addAuthHeader(connection, method, resource);
return connection;
}
/**
* Make a new HttpURLConnection.
*
* @param method
* The HTTP method to use (GET, PUT, DELETE)
* @param resource
* The resource name (bucketName + "/" + key).
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @param object
* The S3StreamObject that is to be written (can be null).
*/
private HttpURLConnection makeStreamRequest(String method, String resource,
Map headers, S3StreamObject object) throws MalformedURLException,
IOException {
URL url = makeURL(resource);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(3600000); // 1hr
connection.setRequestMethod(method);
addHeaders(connection, headers);
if (object != null)
addMetadataHeaders(connection, object.metadata);
addAuthHeader(connection, method, resource);
return connection;
}
/**
* Add the given headers to the HttpURLConnection.
*
* @param connection
* The HttpURLConnection to which the headers will be added.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
*/
private void addHeaders(HttpURLConnection connection, Map headers) {
addHeaders(connection, headers, "");
}
/**
* Add the given metadata fields to the HttpURLConnection.
*
* @param connection
* The HttpURLConnection to which the headers will be added.
* @param metadata
* A Map of String to List of Strings representing the s3
* metadata for this resource.
*/
private void addMetadataHeaders(HttpURLConnection connection, Map metadata) {
addHeaders(connection, metadata, Utils.METADATA_PREFIX);
}
/**
* Add the given headers to the HttpURLConnection with a prefix before the
* keys.
*
* @param connection
* The HttpURLConnection to which the headers will be added.
* @param headers
* A Map of String to List of Strings representing the http
* headers to pass (can be null).
* @param prefix
* The string to prepend to each key before adding it to the
* connection.
*/
private void addHeaders(HttpURLConnection connection, Map headers,
String prefix) {
if (headers != null) {
for (Iterator i = headers.keySet().iterator(); i.hasNext();) {
String key = (String) i.next();
for (Iterator j = ((List) headers.get(key)).iterator(); j
.hasNext();) {
String value = (String) j.next();
connection.addRequestProperty(prefix + key, value);
}
}
}
}
/**
* Add the appropriate Authorization header to the HttpURLConnection.
*
* @param connection
* The HttpURLConnection to which the header will be added.
* @param method
* The HTTP method to use (GET, PUT, DELETE)
* @param resource
* The resource name (bucketName + "/" + key).
*/
private void addAuthHeader(HttpURLConnection connection, String method,
String resource) {
if (connection.getRequestProperty("Date") == null) {
connection.setRequestProperty("Date", httpDate());
}
if (connection.getRequestProperty("Content-Type") == null) {
connection.setRequestProperty("Content-Type", "");
}
String canonicalString = Utils.makeCanonicalString(method, resource,
connection.getRequestProperties());
String encodedCanonical = Utils.encode(awsSecretAccessKey,
canonicalString, false);
connection.setRequestProperty("Authorization", "AWS "
+ awsAccessKeyId + ":" + encodedCanonical);
}
/**
* Create a new URL object for a given resource.
*
* @param resource
* The resource name (bucketName + "/" + key).
*/
private URL makeURL(String resource) throws MalformedURLException {
String protocol = isSecure ? "https" : "http";
return new URL(protocol, server, port, "/" + resource);
}
/**
* Generate an rfc822 date for use in the Date HTTP header.
* @return date as string
*/
public static String httpDate() {
final String DateFormat = "EEE, dd MMM yyyy HH:mm:ss ";
SimpleDateFormat format = new SimpleDateFormat(DateFormat, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format.format(new Date()) + "GMT";
}
}