Skip to content

Instantly share code, notes, and snippets.

@nathfy
Forked from christierney402/S3Wrapper.cfc
Last active October 27, 2016 07:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nathfy/1c55b79a0c021c06bff99f5c301a3bd0 to your computer and use it in GitHub Desktop.
Save nathfy/1c55b79a0c021c06bff99f5c301a3bd0 to your computer and use it in GitHub Desktop.
Amazon Web Services (AWS) S3 Wrapper for ColdFusion
/**
* Amazon S3 REST Wrapper
* Version Date: 2015-09-03
*
* Copyright 2015 CF Webtools | cfwebtools.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Derived from "Amazon S3 REST Wrapper" (http://amazons3.riaforge.org) by Joe Danziger (joe@ajaxcf.com)
* Requires Adobe ColdFusion 10+ equivalent (tested in Adobe ColdFusion 11)
*
* Currently uses Signature Version 2. (http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html)
* Needs to be upgraded to Version 4. (http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html)
* Updated for Lucee by Nathan Kelly
* usage: - download file:
* accesskey = s3Key; //stored in config
* secretKey = s3Token; //stored in config
* S3=createObject("component","components.s3.wrapper").init(accesskey,secretKey);
* data = S3.getObjectBinary(
* s3Bucket,
* folder & '/' & filename
* );
*
* header
* name="Content-Disposition"
* value="attachment;
* filename=#stVars.fileDetails.filename#"{}
* content
* variable="#data.filecontent#"
* type="application/x-unknown"
* deletefile="no"{}
* upload:
* S3=createObject("component","components.s3.wrapper").init(accesskey,secretKey);
* result = S3.putObject(
* bucketName = arguments.bucket,
* ACL = 'private',
* keyName = ServerFile,
* folderPath = folderPath,
* uploadDir = s3Folder &'/'
* );
*/
component accessors=true {
/**
* if using FW/1 & DI/1, arguments are populated using variables.framework.diConfig.constants in Application.cfc
*/
function init( required string S3AccessKey, required string S3SecretAccessKey ) {
variables.S3AccessKey = arguments.S3AccessKey;
variables.S3SecretAccessKey = arguments.S3SecretAccessKey;
return this;
}
/**
* NSA SHA-1 Algorithm to hash a message being sent to AWS using the secret key.
* Thanks to Adam Cameron and @jcberquist for help
*/
private binary function HMAC_SHA1( required string signKey, required string signMessage ) {
// returns in hex
var result = HMac( arguments.signMessage, arguments.signKey, 'HMACSHA1');
// convert to binary and return
return binaryDecode( result, 'hex' );
}
string function createSignature( required string stringIn) {
// replace "\n" with "chr(10)" to get a correct digest
var fixedData = replace( arguments.stringIn, "\n", chr(10), "all" );
// calculate the hash of the information
var digest = HMAC_SHA1(variables.S3SecretAccessKey, fixedData);
// fix the returned data to be a proper signature
var signature = toBase64(digest);
return signature;
}
/**
* puts an object into the bucket
* ex: putObject( bucketName = 'myBucket', ACL = 'private', keyName = 'test.jpg', uploadDir = 'D:\', serverSideEncryption = 'AES256');
* AWS S3 REST API: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
* AWS S3 Meta Data: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
* @ACL the canned ACL to apply to the object ( private | public-read | public-read-write | authenticated-read | bucket-owner-read | bucket-owner-full-control )
* @cacheControl cache the object in your CloudFront cache service for better performance. Additional fees may apply.
* @cacheDays how many days the object will stay in the CloudFront. Max: 100 years
* @contentType a standard MIME type describing the format of the contents
* @fileName file name to retrieve for upload (ex: myFile.jpg). If left empty it uses the keyName attribute
* @keyName uniquely identifies the object in the S3 bucket. Unicode characters whose UTF-8 encoding is at most 1024 bytes long. Safe: [0-9], [a-z], [A-Z], !, -, _, ., *, ', (, and ). "/" infers a folder in the S3 console.
* @folderPath Buckets can have folders (just filenames really) so provide the path you'd like here
* @serverSideEncryption server-side encryption algorithm to use when S3 creates an object ( none | AES256 )
* @storageClass ( STANDARD | REDUCED_REDUNDANCY (noncritical, reproducible data at lower levels of redundancy) )
* @uploadDir local path the fileName attribute is stored including trailing "/"
*/
object function putObject(
required string bucketName,
string contentType = 'binary/octet-stream',
boolean cacheControl = false,
numeric cacheDays = 30,
string ACL = 'public-read',
string storageClass = 'STANDARD',
required string keyName,
required string folderPath,
string fileName = arguments.keyName,
string uploadDir = expandPath('./'),
string serverSideEncryption = 'none;'
) {
var versionID = '';
var binaryFileData = '';
var dateTimeString = getHTTPTimeString( now() );
var cs = '';
var signature = '';
var httpService = '';
var cacheSeconds = cacheDays * 24 * 60 * 60;
var result = '';
// put HTTP-Verb, Content-MD5(none), Content-Type and Date into signature
cs = "PUT\n\n#arguments.contentType#\n#dateTimeString#\n";
// add CanonicalizedAmzHeaders sorted lexicographically seperated by a new line
cs &= "x-amz-acl:#arguments.acl#\n";
if(arguments.serverSideEncryption == 'AES256') {
cs &= "x-amz-server-side-encryption:AES256\n";
}
cs &= "x-amz-storage-class:#arguments.storageClass#\n";
// add CanonicalizedResource
cs &= "/#arguments.bucketName#/#folderPath##arguments.keyName#";
urlString = "https://#arguments.bucketName#.s3.amazonaws.com/#folderPath##arguments.keyName#";
// hash the signature
signature = createSignature(cs);
// read the file data into a variable
// TODO: might need also use "ToBase64" per CF docs
binaryFileData = fileReadBinary( arguments.uploadDir & arguments.fileName );
// send the file to amazon
httpService = new http();
httpService.setMethod('PUT');
httpService.setURL("#urlString#");
httpService.addParam( type = 'header', name = 'Authorization', value = 'AWS #variables.S3AccessKey#:#signature#' );
httpService.addParam( type = 'header', name = 'Content-Type', value = arguments.contentType );
httpService.addParam( type = 'header', name = 'Date', value = dateTimeString );
httpService.addParam( type = 'header', name = 'x-amz-acl', value = arguments.acl );
httpService.addParam( type = 'header', name = 'x-amz-storage-class', value = arguments.storageClass );
if(arguments.serverSideEncryption == 'AES256') {
httpService.addParam( type = 'header', name = 'x-amz-server-side-encryption', value = 'AES256' );
}
if (arguments.cacheControl) {
httpService.addParam( type = 'header', name = 'Cache-Control', value = 'max-age=#cacheSeconds#' );
}
httpService.addParam( type = 'body', value = binaryFileData);
result = httpService.send().getPrefix();
result.urlString = urlString;
result.key = variables.S3AccessKey;
return result;
}
/**
* returns a link to an object
* if you generate a link to a file that doesn't exist, an XML error will be returned with the code "AccessDenied", instead
* of a not found error, upon accessing the URL
*/
string function getObjectLink( required string bucketName, required string keyName, string minutesValid = 1 ) {
var timedAmazonLink = "";
var epochTime = dateDiff( "s", DateConvert("utc2Local", "January 1 1970 00:00"), now() ) + (arguments.minutesValid * 60);
// create a canonical string to send
var cs = "GET\n\n\n#epochTime#\n/#arguments.bucketName#/#arguments.keyName#";
// hash the signature
var signature = createSignature(cs);
timedAmazonLink = "https://#arguments.bucketName#.s3.amazonaws.com/#arguments.keyName#?AWSAccessKeyId=#URLEncodedFormat(variables.S3AccessKey)#&Expires=#epochTime#&Signature=#URLEncodedFormat(signature)#";
return timedAmazonLink;
}
/**
* Download the file. Returns full file path.
* @file file name to be given to saved file
* @path physical path to store file including trailing slash (ex: c:\myfiles\)
*/
string function getObject( required string bucketName, required string keyName, string path = expandPath('./') ) {
var httpService = new http();
httpService.setMethod('GET');
httpService.setURL( getObjectLink( bucketName = arguments.bucketName, keyName = arguments.keyName ) );
httpService.setPath(arguments.path);
httpService.setFile(arguments.keyName);
httpService.send();
return arguments.path & arguments.keyName;
}
/**
* Download the file. Returns full file path.
* this works in CF but not Lucee
* @file file name to be given to saved file
* @path physical path to store file including trailing slash (ex: c:\myfiles\)
*/
function getObjectBinaryCF( required string bucketName, required string keyName, string path = expandPath('./') ) {
var httpService = new http();
httpService.setMethod('GET');
httpService.setURL( getObjectLink( bucketName = arguments.bucketName, keyName = arguments.keyName ) );
httpService.getasbinary(true);
httpService.setPath(arguments.path);
httpService.setFile(arguments.keyName);
result = httpService.send().getPrefix();
return result.filecontent;
}
function getObjectBinary( required string bucketName, required string keyName, string path = expandPath('./') ) {
http
method = "GET"
url="#getObjectLink( bucketName = arguments.bucketName, keyName = arguments.keyName )#"
getasbinary ="yes"{}
return cfhttp;
}
/**
* Deletes an object
*/
void function deleteObject( required string bucketName, required string keyName ) {
var httpService = '';
var dateTimeString = GetHTTPTimeString( Now() );
// create a canonical string to send based on operation requested
var cs = "DELETE\n\n\n#dateTimeString#\n/#arguments.bucketName#/#arguments.keyName#";
// create a proper signature
var signature = createSignature(cs);
// delete the object via REST
httpService = new http();
httpService.setMethod('DELETE');
httpService.setURL('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.keyName#');
httpService.addParam( type = 'header', name = 'Date', value = dateTimeString );
httpService.addParam( type = 'header', name = 'Authorization', value = 'AWS #variables.S3AccessKey#:#signature#' );
httpService.send();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment