awssum-amazon-s3
Version:
AwsSum plugin for Amazon Simple Storage Service (S3).
270 lines (223 loc) • 9.27 kB
JavaScript
// --------------------------------------------------------------------------------------------------------------------
//
// s3.js - class for AWS Simple Storage Service
//
// Copyright (c) 2011 AppsAttic Ltd - http://www.appsattic.com/
// Written by Andrew Chilton <chilts@appsattic.com>
//
// License: http://opensource.org/licenses/MIT
//
// --------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// requires
// built-ins
var util = require('util');
var crypto = require('crypto');
// dependencies
var _ = require('underscore');
var dateformat = require('dateformat');
// our own
var awssum = require('awssum');
var amazon = require('awssum-amazon');
var operations = require('./config.js');
// --------------------------------------------------------------------------------------------------------------------
// package variables
var MARK = 's3: ';
// From: http://docs.amazonwebservices.com/general/latest/gr/rande.html
var endPoint = {};
endPoint[amazon.US_EAST_1] = "s3.amazonaws.com";
endPoint[amazon.US_WEST_1] = "s3-us-west-1.amazonaws.com";
endPoint[amazon.US_WEST_2] = "s3-us-west-2.amazonaws.com";
endPoint[amazon.EU_WEST_1] = "s3-eu-west-1.amazonaws.com";
endPoint[amazon.AP_SOUTHEAST_1] = "s3-ap-southeast-1.amazonaws.com";
endPoint[amazon.AP_SOUTHEAST_2] = "s3-ap-southeast-2.amazonaws.com";
endPoint[amazon.AP_NORTHEAST_1] = "s3-ap-northeast-1.amazonaws.com";
endPoint[amazon.US_GOV_WEST_1] = "s3-us-gov-west-1.amazonaws.com";
endPoint[amazon.SA_EAST_1] = "s3-sa-east-1.amazonaws.com";
// From: http://docs.amazonwebservices.com/general/latest/gr/rande.html#s3_region
var locationConstraint = {};
locationConstraint[amazon.US_EAST_1] = "";
locationConstraint[amazon.US_WEST_1] = "us-west-1";
locationConstraint[amazon.EU_WEST_1] = "EU";
locationConstraint[amazon.AP_SOUTHEAST_1] = "ap-southeast-1";
locationConstraint[amazon.AP_SOUTHEAST_2] = "ap-southeast-2";
locationConstraint[amazon.AP_NORTHEAST_1] = "ap-northeast-1";
// US_GOV_WEST_1 not defined for public consumption
locationConstraint[amazon.SA_EAST_1] = "sa-east-1";
var version = '2011-10-04';
// List from: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/RESTAuthentication.html
var validSubresource = {
acl : true,
cors : true,
'delete' : true,
lifecycle : true,
location : true,
logging : true,
notification : true,
restore : true,
partNumber : true,
policy : true,
requestPayment : true,
tagging : true,
torrent : true,
uploadId : true,
uploads : true,
versionId : true,
versioning : true,
versions : true,
website : true,
// For http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html
'response-content-type' : true,
'response-content-language' : true,
'response-expires' : true,
'response-cache-control' : true,
'response-content-disposition' : true,
'response-content-encoding' : true,
};
// --------------------------------------------------------------------------------------------------------------------
// constructor
var S3 = function(opts) {
var self = this;
// call the superclass for initialisation
S3.super_.call(this, opts);
// check the region is valid
if ( ! endPoint[opts.region] ) {
throw MARK + "invalid region '" + opts.region + "'";
}
return self;
};
// inherit from Amazon
util.inherits(S3, amazon.Amazon);
// --------------------------------------------------------------------------------------------------------------------
// methods we need to implement from awssum.js/amazon.js
S3.prototype.host = function() {
return endPoint[this.region()];
};
S3.prototype.version = function() {
return version;
};
S3.prototype.locationConstraint = function() {
return locationConstraint[this.region()];
};
// From: http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTCommonRequestHeaders.html
//
// Just the date for this service.
S3.prototype.addCommonOptions = function(options, args) {
var self = this;
// always add a Date header
options.headers.Date = dateformat(new Date(), "UTC:ddd, dd mmm yyyy HH:MM:ss Z");
// make the strToSign, create the signature and sign it
var strToSign = self.strToSign(options, args);
var signature = self.signature(strToSign);
options.headers.Authorization = 'AWS ' + self.accessKeyId() + ':' + signature;
};
// From: http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
//
// Returns a strToSign for this request.
S3.prototype.strToSign = function(options, args) {
var self = this;
// start creating the string we need to sign
var strToSign = '';
strToSign += options.method + "\n";
// add the following headers (if available)
_.each(['Content-MD5', 'Content-Type', 'Date'], function(hdrName) {
if ( _.isString( options.headers[hdrName] ) ) {
strToSign += options.headers[hdrName];
}
strToSign += "\n";
});
// grep out all of the x-amz-* headers
var amzHeaders = _(options.headers)
.chain()
.keys()
// .map( function(hdr) { return h.toLowerCase(); } )
.select( function(hdr) {
return hdr.toLowerCase().match(/^x-amz-/) ? true : false;
})
.sortBy( function(hdr) { return hdr; } )
.value();
// add the x-amz-* headers to the strToSign in the correct order
_.each(amzHeaders, function(hdr) {
strToSign += hdr.toLowerCase() + ':';
// concat all the headers and their values together (removing leading and trailing whitespace)
var headerValue;
if ( _.isArray(options.headers[hdr]) ) {
headerValue = _(options.headers[hdr])
.chain()
.map(function(val) { return val.replace(/^\s+|\s+$/g, ''); })
.value()
.join(',');
}
else {
headerValue = options.headers[hdr].replace(/^\s+|\s+$/g, '');
}
// condense all whitespace into a single space
headerValue.replace(/\s+/g, ' ');
strToSign += headerValue + "\n";
});
// add the CanonicalizedResource (bucket, path (defined by ObjectName) and sub-resource (defined by params))
if ( _.isUndefined(args.BucketName) ) {
strToSign += '/';
}
else {
strToSign += '/' + args.BucketName + '/';
}
if ( ! _.isUndefined(args.ObjectName) ) {
strToSign += awssum.esc(args.ObjectName);
}
// add the sub-resources (such as versioning, location, acl, torrent, versionid) but not things like max-keys,
// prefix or other query parameters
if ( options.params.length ) {
strToSign += _(options.params)
.chain()
.filter(function(pair) { return validSubresource[pair.name]; } )
.sortBy(function(pair) { return pair.name; } )
.map(function(pair){
return _.isUndefined(pair.value) ? pair.name : pair.name + '=' + pair.value;
})
.reduce(function(memo, pairStr) {
return memo === '' ? '?' + pairStr : memo + '&' + pairStr;
}, '')
.value()
;
}
// console.log('StrToSign :', strToSign + '(Ends)');
return strToSign;
};
// From: http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
//
// Returns a signature for this request.
S3.prototype.signature = function(strToSign) {
var self = this;
// sign the request string
var signature = crypto
.createHmac('sha1', this.secretAccessKey())
.update(strToSign)
.digest('base64');
// console.log('Signature :', signature);
return signature;
};
// Whenever anything goes wrong with S3, it'll give back XML, even for those operations which usually respond with
// "204 - No Content"
S3.prototype.extractBodyWhenError = function(options) {
return 'xml';
};
// --------------------------------------------------------------------------------------------------------------------
// operations on the service
_.each(operations, function(operation, operationName) {
S3.prototype[operationName] = awssum.makeOperation(operation);
});
// --------------------------------------------------------------------------------------------------------------------
// exports
// endpoints
exports.US_EAST_1 = amazon.US_EAST_1;
exports.US_WEST_1 = amazon.US_WEST_1;
exports.US_WEST_2 = amazon.US_WEST_2;
exports.EU_WEST_1 = amazon.EU_WEST_1;
exports.AP_SOUTHEAST_1 = amazon.AP_SOUTHEAST_1;
exports.AP_SOUTHEAST_2 = amazon.AP_SOUTHEAST_2;
exports.AP_NORTHEAST_1 = amazon.AP_NORTHEAST_1;
exports.SA_EAST_1 = amazon.SA_EAST_1;
exports.US_GOV_WEST = amazon.US_GOV_WEST;
exports.S3 = S3;
// --------------------------------------------------------------------------------------------------------------------