// 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.
using System;
using System.Collections;
using System.Net;
using System.Text;
using System.Web;
using System.IO;
namespace Extf.Net.S3
{
/// 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 bool isSecure;
private string server;
private int port;
private CallingFormat callingFormat;
public AWSAuthConnection( string awsAccessKeyId, string awsSecretAccessKey ):
this( awsAccessKeyId, awsSecretAccessKey, true, CallingFormat.REGULAR )
{
}
public AWSAuthConnection(string awsAccessKeyId, string awsSecretAccessKey, CallingFormat format )
:
this(awsAccessKeyId, awsSecretAccessKey, true, format )
{
}
public AWSAuthConnection(string awsAccessKeyId, string awsSecretAccessKey, bool isSecure)
:
this( awsAccessKeyId, awsSecretAccessKey, isSecure, Utils.Host, CallingFormat.REGULAR )
{
}
public AWSAuthConnection(string awsAccessKeyId, string awsSecretAccessKey, bool isSecure, CallingFormat format) :
this( awsAccessKeyId, awsSecretAccessKey, isSecure, Utils.Host, format )
{
}
public AWSAuthConnection( string awsAccessKeyId, string awsSecretAccessKey, bool isSecure,
string server, CallingFormat format ) :
this(awsAccessKeyId, awsSecretAccessKey, isSecure, server,
isSecure ? Utils.SecurePort : Utils.InsecurePort, format )
{
}
public AWSAuthConnection( string awsAccessKeyId, string awsSecretAccessKey, bool isSecure,
string server ) :
this( awsAccessKeyId, awsSecretAccessKey, isSecure, server,
isSecure ? Utils.SecurePort : Utils.InsecurePort, CallingFormat.REGULAR )
{
}
public AWSAuthConnection( string awsAccessKeyId, string awsSecretAccessKey, bool isSecure,
string server, int port) :
this( awsAccessKeyId, awsSecretAccessKey, isSecure, server,
port, CallingFormat.REGULAR )
{
}
public AWSAuthConnection( string awsAccessKeyId, string awsSecretAccessKey, bool isSecure,
string server, int port, CallingFormat format )
{
this.awsAccessKeyId = awsAccessKeyId;
this.awsSecretAccessKey = awsSecretAccessKey;
this.isSecure = isSecure;
this.server = server;
this.port = port;
this.callingFormat = format;
}
///
/// Creates a new bucket.
///
/// The name of the bucket to create
/// A Map of string to string representing the headers to pass (can be null)
public Response createBucket(string bucket) {
S3Object obj = new S3Object("", null);
WebRequest request = makeRequest("PUT", bucket, "", null, obj);
request.ContentLength = 0;
request.GetRequestStream().Close();
return new Response(request);
}
public Response createBucket( string bucket, SortedList headers )
{
S3Object obj = new S3Object("", null);
WebRequest request = makeRequest("PUT", bucket, "", headers, obj);
request.ContentLength = 0;
request.GetRequestStream().Close();
return new Response(request);
}
///
/// Lists the contents of a bucket.
///
/// The name of the bucket to list
/// All returned keys will start with this string (can be null)
/// All returned keys will be lexographically greater than this string (can be null)
/// The maximum number of keys to return (can be 0)
/// A Map of string to string representing HTTP headers to pass.
public ListBucketResponse listBucket( string bucket, string prefix, string marker,
int maxKeys, SortedList headers ) {
return listBucket( bucket, prefix, marker, maxKeys, null, headers );
}
///
/// Lists the contents of a bucket.
///
/// The name of the bucket to list
/// All returned keys will start with this string (can be null)
/// All returned keys will be lexographically greater than this string (can be null)
/// The maximum number of keys to return (can be 0)
/// A Map of string to string representing HTTP headers to pass.
/// Keys that contain a string between the prefix and the first
/// occurrence of the delimiter will be rolled up into a single element.
public ListBucketResponse listBucket( string bucket, string prefix, string marker,
int maxKeys, string delimiter, SortedList headers ) {
SortedList query = Utils.queryForListOptions(prefix, marker, maxKeys, delimiter);
return new ListBucketResponse( makeRequest( "GET", bucket, "", query, headers, null ) );
}
///
/// Deletes an empty Bucket.
///
/// The name of the bucket to delete
/// A map of string to string representing the HTTP headers to pass (can be null)
///
public Response deleteBucket( string bucket, SortedList headers )
{
return new Response( makeRequest( "DELETE", bucket, "", headers ) );
}
///
/// Writes an object to S3.
///
/// The name of the bucket to which the object will be added.
/// The name of the key to use
/// An S3Object containing the data to write.
/// A map of string to string representing the HTTP headers to pass (can be null)
public Response put(string bucket, string key, S3Object obj) {
WebRequest request = makeRequest("PUT", bucket, encodeKeyForSignature(key), null, obj);
request.ContentLength = obj.Bytes.Length;
using (Stream _stream = request.GetRequestStream()) {
_stream.Write(obj.Bytes, 0, obj.Bytes.Length);
return new Response(request);
}
}
///
/// Writes an object to S3.
///
/// The name of the bucket to which the object will be added.
/// The name of the key to use
/// An S3Object containing the data to write.
/// A map of string to string representing the HTTP headers to pass (can be null)
public Response put( string bucket, string key, S3Object obj, SortedList headers )
{
WebRequest request = makeRequest("PUT", bucket, encodeKeyForSignature(key), headers, obj);
request.ContentLength = obj.Bytes.Length;
using (Stream _stream = request.GetRequestStream()) {
_stream.Write(obj.Bytes, 0, obj.Bytes.Length);
return new Response(request);
}
}
// NOTE: The Syste.Net.Uri class does modifications to the URL.
// For example, if you have two consecutive slashes, it will
// convert these to a single slash. This could lead to invalid
// signatures as best and at worst keys with names you do not
// care for.
private static string encodeKeyForSignature(string key)
{
return key.Replace("%2f", "/");
}
///
/// Reads an object from S3
///
/// The name of the bucket where the object lives
/// The name of the key to use
/// A Map of string to string representing the HTTP headers to pass (can be null)
public GetResponse get( string bucket, string key, SortedList headers )
{
return new GetResponse(makeRequest("GET", bucket, encodeKeyForSignature(key), headers));
}
public GetResponse get(string bucket, string key) {
return new GetResponse(makeRequest("GET", bucket, encodeKeyForSignature(key), null));
}
///
/// Delete an object from S3.
///
/// The name of the bucket where the object lives.
/// The name of the key to use.
/// A map of string to string representing the HTTP headers to pass (can be null)
///
public Response delete( string bucket, string key, SortedList headers )
{
return new Response(makeRequest("DELETE", bucket, encodeKeyForSignature(key), headers));
}
///
/// Get the logging xml document for a given bucket
///
/// The name of the bucket
/// A map of string to string representing the HTTP headers to pass (can be null)
public GetResponse getBucketLogging(string bucket, SortedList headers)
{
SortedList query = new SortedList();
query.Add("logging", "");
return new GetResponse(makeRequest("GET", bucket, "", query, headers, null));
}
///
/// Write a new logging xml document for a given bucket
///
/// The name of the bucket to enable/disable logging on
/// The xml representation of the logging configuration as a String.
/// A map of string to string representing the HTTP headers to pass (can be null)
public Response putBucketLogging(string bucket, string loggingXMLDoc, SortedList headers)
{
S3Object obj = new S3Object(loggingXMLDoc, null);
SortedList query = new SortedList();
query.Add("logging", "");
WebRequest request = makeRequest("PUT", bucket, "", query, headers, obj);
request.ContentLength = loggingXMLDoc.Length;
request.GetRequestStream().Write(obj.Bytes, 0, obj.Bytes.Length);
request.GetRequestStream().Close();
return new Response(request);
}
///
/// Get the ACL for a given bucket.
///
/// The the bucket to get the ACL from.
/// A map of string to string representing the HTTP headers to pass (can be null)
public GetResponse getBucketACL(string bucket, SortedList headers)
{
return getACL(bucket, null, headers);
}
///
/// Get the ACL for a given object
///
/// The name of the bucket where the object lives
/// The name of the key to use.
/// A map of string to string representing the HTTP headers to pass (can be null)
public GetResponse getACL( string bucket, string key, SortedList headers )
{
SortedList query = new SortedList();
query.Add("acl", "");
if (key == null) key = "";
return new GetResponse(makeRequest("GET", bucket, encodeKeyForSignature(key), query, headers, null));
}
///
/// Write a new ACL for a given bucket
///
/// The name of the bucket to change the ACL.
/// An XML representation of the ACL as a string.
/// A map of string to string representing the HTTP headers to pass (can be null)
public Response putBucketACL(string bucket, string aclXMLDoc, SortedList headers)
{
return putACL(bucket, null, aclXMLDoc, headers);
}
///
/// Write a new ACL for a given object
///
/// The name of the bucket where the object lives or the
/// name of the bucket to change the ACL if key is null.
/// The name of the key to use; can be null.
/// An XML representation of the ACL as a string.
/// A map of string to string representing the HTTP headers to pass (can be null)
public Response putACL(string bucket, string key, string aclXMLDoc, SortedList headers)
{
S3Object obj = new S3Object( aclXMLDoc, null );
if ( key == null ) key = "";
SortedList query = new SortedList();
query.Add("acl", "");
WebRequest request = makeRequest("PUT", bucket, encodeKeyForSignature(key), query, headers, obj);
request.ContentLength = aclXMLDoc.Length;
request.GetRequestStream().Write(obj.Bytes, 0, obj.Bytes.Length);
request.GetRequestStream().Close();
return new Response(request);
}
///
/// List all the buckets created by this account.
///
/// A map of string to string representing the HTTP headers to pass (can be null)
public ListAllMyBucketsResponse listAllMyBuckets( SortedList headers )
{
return new ListAllMyBucketsResponse(makeRequest("GET", "", "", headers));
}
///
/// Make a new WebRequest without an S3Object.
///
private WebRequest makeRequest( string method, string bucket, string key, SortedList headers )
{
return makeRequest( method, bucket, key, new SortedList(), headers, null );
}
///
/// Make a new WebRequest with an S3Object.
///
private WebRequest makeRequest(string method, string bucket, string key, SortedList headers, S3Object obj)
{
return makeRequest(method, bucket, key, new SortedList(), headers, obj);
}
///
/// Make a new WebRequest
///
/// The HTTP method to use (GET, PUT, DELETE)
/// The bucket name for this request
/// The key this request is for
/// A map of string to string representing the HTTP headers to pass (can be null)
/// S3Object that is to be written (can be null).
private WebRequest makeRequest( string method, string bucket, string key, SortedList query, SortedList headers, S3Object obj )
{
StringBuilder url = new StringBuilder();
url.Append( isSecure ? "https://" : "http://" );
url.Append( Utils.buildUrlBase(server, port, bucket, callingFormat) );
if ( key != null && key.Length != 0 ) {
url.Append( key );
}
// build the query string parameter
url.Append( Utils.convertQueryListToQueryString( query ) );
WebRequest req = WebRequest.Create( url.ToString() );
if ( req is HttpWebRequest ) {
HttpWebRequest httpReq = req as HttpWebRequest;
httpReq.AllowWriteStreamBuffering = false;
}
req.Method = method;
addHeaders( req, headers );
if ( obj != null ) addMetadataHeaders( req, obj.Metadata );
addAuthHeader( req, bucket, key, query );
return req;
}
///
/// Add the given headers to the WebRequest
///
/// Web request to add the headers to.
/// A map of string to string representing the HTTP headers to pass (can be null)
private void addHeaders( WebRequest req, SortedList headers )
{
addHeaders( req, headers, "" );
}
///
/// Add the given metadata fields to the WebRequest.
///
/// Web request to add the headers to.
/// A map of string to string representing the S3 metadata for this resource.
private void addMetadataHeaders( WebRequest req, SortedList metadata )
{
addHeaders( req, metadata, Utils.METADATA_PREFIX );
}
///
/// Add the given headers to the WebRequest with a prefix before the keys.
///
/// WebRequest to add the headers to.
/// Headers to add.
/// String to prepend to each before ebfore adding it to the WebRequest
private void addHeaders( WebRequest req, SortedList headers, string prefix )
{
if ( headers != null )
{
foreach ( string key in headers.Keys )
{
if (prefix.Length == 0 && key.Equals("Content-Type"))
{
req.ContentType = headers[key] as string;
}
else
{
req.Headers.Add(prefix + key, headers[key] as string);
}
}
}
}
///
/// Add the appropriate Authorization header to the WebRequest
///
/// Request to add the header to
/// The resource name (bucketName + "/" + key)
private void addAuthHeader( WebRequest request, string bucket, string key, SortedList query )
{
if ( request.Headers[Utils.ALTERNATIVE_DATE_HEADER] == null )
{
request.Headers.Add(Utils.ALTERNATIVE_DATE_HEADER, Utils.getHttpDate());
}
string canonicalString = Utils.makeCanonicalString( bucket, key, query, request );
string encodedCanonical = Utils.encode( awsSecretAccessKey, canonicalString, false );
request.Headers.Add( "Authorization", "AWS " + awsAccessKeyId + ":" + encodedCanonical );
}
}
}