Last active
February 9, 2019 07:30
-
-
Save christierney402/3f6f14ba86049af75361 to your computer and use it in GitHub Desktop.
Amazon Web Services (AWS) S3 Wrapper for ColdFusion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 ([email protected]) | |
* 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) | |
*/ | |
component accessors=true { | |
/** | |
* if using FW/1 & DI/1, arguments are populated using variables.framework.diConfig.constants in Application.cfc | |
*/ | |
S3 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. | |
* @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 "/" | |
*/ | |
void 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, | |
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#/#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('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.keyName#'); | |
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(); | |
} | |
/** | |
* 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; | |
} | |
/** | |
* 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