Skip to content

Instantly share code, notes, and snippets.

@malpaso
Forked from christierney402/S3Wrapper.cfc
Created September 7, 2016 02:37
Show Gist options
  • Select an option

  • Save malpaso/9e9595d92053b827ad1721950b82b745 to your computer and use it in GitHub Desktop.

Select an option

Save malpaso/9e9595d92053b827ad1721950b82b745 to your computer and use it in GitHub Desktop.

Revisions

  1. @christierney402 christierney402 revised this gist Sep 3, 2015. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion S3Wrapper.cfc
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /**
    * Amazon S3 REST Wrapper
    * Version Date: 2015-09-01
    * Version Date: 2015-09-03
    *
    * Copyright 2015 CF Webtools | cfwebtools.com
    *
    @@ -198,5 +198,7 @@ component accessors=true {
    httpService.addParam( type = 'header', name = 'Date', value = dateTimeString );
    httpService.addParam( type = 'header', name = 'Authorization', value = 'AWS #variables.S3AccessKey#:#signature#' );

    httpService.send();

    }
    }
  2. @christierney402 christierney402 revised this gist Sep 1, 2015. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions S3Wrapper.cfc
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /**
    * Amazon S3 REST Wrapper
    * Version Date: 2015-08-19
    * Version Date: 2015-09-01
    *
    * Copyright 2015 CF Webtools | cfwebtools.com
    *
    @@ -75,12 +75,11 @@ component accessors=true {
    * @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
    * @uploadDir local path the fileName attribute is stored including trailing "/"
    */
    void function putObject(
    required string bucketName,
    string contentType = 'binary/octet-stream',
    numeric HTTPTimeout = 300,
    boolean cacheControl = false,
    numeric cacheDays = 30,
    string ACL = 'public-read',
  3. @christierney402 christierney402 revised this gist Aug 26, 2015. 1 changed file with 20 additions and 16 deletions.
    36 changes: 20 additions & 16 deletions S3Wrapper.cfc
    Original file line number Diff line number Diff line change
    @@ -64,15 +64,18 @@ component accessors=true {

    /**
    * puts an object into the bucket
    * ex: putObject( bucketName = 'myBucket', ACL = 'private', objectName = 'test.jpg', uploadDir = 'D:\', serverSideEncryption = 'AES256');
    * 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
    * @objectName file name (ex: myFile.jpg)
    * @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
    */
    void function putObject(
    required string bucketName,
    @@ -82,7 +85,8 @@ component accessors=true {
    numeric cacheDays = 30,
    string ACL = 'public-read',
    string storageClass = 'STANDARD',
    required string objectName,
    required string keyName,
    string fileName = arguments.keyName,
    string uploadDir = expandPath('./'),
    string serverSideEncryption = 'none;'
    ) {
    @@ -104,20 +108,20 @@ component accessors=true {
    }
    cs &= "x-amz-storage-class:#arguments.storageClass#\n";
    // add CanonicalizedResource
    cs &= "/#arguments.bucketName#/#arguments.objectName#";
    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 & objectName );
    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.objectName#');
    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 );
    @@ -140,17 +144,17 @@ component accessors=true {
    * 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 objectName, string minutesValid = 1 ) {
    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.objectName#";
    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.objectName#?AWSAccessKeyId=#URLEncodedFormat(variables.S3AccessKey)#&Expires=#epochTime#&Signature=#URLEncodedFormat(signature)#";
    timedAmazonLink = "https://#arguments.bucketName#.s3.amazonaws.com/#arguments.keyName#?AWSAccessKeyId=#URLEncodedFormat(variables.S3AccessKey)#&Expires=#epochTime#&Signature=#URLEncodedFormat(signature)#";

    return timedAmazonLink;
    }
    @@ -160,29 +164,29 @@ component accessors=true {
    * @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 objectName, string path = expandPath('./') ) {
    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, objectName = arguments.objectName ) );
    httpService.setURL( getObjectLink( bucketName = arguments.bucketName, keyName = arguments.keyName ) );
    httpService.setPath(arguments.path);
    httpService.setFile(arguments.objectName);
    httpService.setFile(arguments.keyName);

    httpService.send();

    return arguments.path & arguments.objectName;
    return arguments.path & arguments.keyName;
    }

    /**
    * Deletes an object
    */
    void function deleteObject( required string bucketName, required string objectName ) {
    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.objectName#";
    var cs = "DELETE\n\n\n#dateTimeString#\n/#arguments.bucketName#/#arguments.keyName#";

    // create a proper signature
    var signature = createSignature(cs);
    @@ -191,7 +195,7 @@ component accessors=true {
    httpService = new http();

    httpService.setMethod('DELETE');
    httpService.setURL('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.objectName#');
    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#' );

  4. @christierney402 christierney402 revised this gist Aug 19, 2015. 1 changed file with 14 additions and 18 deletions.
    32 changes: 14 additions & 18 deletions S3Wrapper.cfc
    Original file line number Diff line number Diff line change
    @@ -137,54 +137,52 @@ component accessors=true {

    /**
    * 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 fileKey,
    string minutesValid = 1
    ){
    string function getObjectLink( required string bucketName, required string objectName, 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.fileKey#";
    var cs = "GET\n\n\n#epochTime#\n/#arguments.bucketName#/#arguments.objectName#";

    // create a proper signature
    // hash the signature
    var signature = createSignature(cs);

    timedAmazonLink = "https://#arguments.bucketName#.s3.amazonaws.com/#arguments.fileKey#?AWSAccessKeyId=#URLEncodedFormat(variables.S3AccessKey)#&Expires=#epochTime#&Signature=#URLEncodedFormat(signature)#";
    timedAmazonLink = "https://#arguments.bucketName#.s3.amazonaws.com/#arguments.objectName#?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 (ex: c:\myfiles)
    * @path physical path to store file including trailing slash (ex: c:\myfiles\)
    */
    string function getObject( required string bucketName, required string fileKey, require string path ) {
    string function getObject( required string bucketName, required string objectName, string path = expandPath('./') ) {
    var httpService = new http();

    httpService.setMethod('GET');
    httpService.setURL( getObjectLink( bucketName = arguments.bucketName, fileKey = arguments.fileKey ) );
    httpService.setURL( getObjectLink( bucketName = arguments.bucketName, objectName = arguments.objectName ) );
    httpService.setPath(arguments.path);
    httpService.setFile(arguments.file);
    httpService.setFile(arguments.objectName);

    httpService.send();

    return arguments.path & '\' & arguments.file;
    return arguments.path & arguments.objectName;
    }

    /**
    * Deletes an object
    */
    void function deleteObject( required string bucketName, required string fileKey ) {
    void function deleteObject( required string bucketName, required string objectName ) {

    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.fileKey#";
    var cs = "DELETE\n\n\n#dateTimeString#\n/#arguments.bucketName#/#arguments.objectName#";

    // create a proper signature
    var signature = createSignature(cs);
    @@ -193,11 +191,9 @@ component accessors=true {
    httpService = new http();

    httpService.setMethod('DELETE');
    httpService.setURL('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.bucketName#/#arguments.fileKey#');
    httpService.setURL('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.objectName#');
    httpService.addParam( type = 'header', name = 'Date', value = dateTimeString );
    httpService.addParam( type = 'header', name = 'Authorization', value = 'AWS #variables.S3AccessKey#:#signature#' );

    httpService.send();

    }
    }
  5. @christierney402 christierney402 revised this gist Aug 19, 2015. 1 changed file with 0 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions S3Wrapper.cfc
    Original file line number Diff line number Diff line change
    @@ -118,7 +118,6 @@ component accessors=true {

    httpService.setMethod('PUT');
    httpService.setURL('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.objectName#');
    //httpService.setURL('http://playground11.local/tests3.cfm');

    httpService.addParam( type = 'header', name = 'Authorization', value = 'AWS #variables.S3AccessKey#:#signature#' );
    httpService.addParam( type = 'header', name = 'Content-Type', value = arguments.contentType );
    @@ -134,8 +133,6 @@ component accessors=true {
    httpService.addParam( type = 'body', value = binaryFileData);

    result = httpService.send();

    writeDump(result); abort;
    }

    /**
  6. @christierney402 christierney402 created this gist Aug 19, 2015.
    206 changes: 206 additions & 0 deletions S3Wrapper.cfc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,206 @@
    /**
    * Amazon S3 REST Wrapper
    * Version Date: 2015-08-19
    *
    * 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)
    */

    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', objectName = 'test.jpg', uploadDir = 'D:\', serverSideEncryption = 'AES256');
    * AWS S3 REST API: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.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
    * @objectName file name (ex: myFile.jpg)
    * @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) )
    */
    void function putObject(
    required string bucketName,
    string contentType = 'binary/octet-stream',
    numeric HTTPTimeout = 300,
    boolean cacheControl = false,
    numeric cacheDays = 30,
    string ACL = 'public-read',
    string storageClass = 'STANDARD',
    required string objectName,
    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.objectName#";

    // 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 & objectName );

    // send the file to amazon
    httpService = new http();

    httpService.setMethod('PUT');
    httpService.setURL('https://#arguments.bucketName#.s3.amazonaws.com/#arguments.objectName#');
    //httpService.setURL('http://playground11.local/tests3.cfm');

    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();

    writeDump(result); abort;
    }

    /**
    * returns a link to an object
    */
    string function getObjectLink(
    required string bucketName,
    required string fileKey,
    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.fileKey#";

    // create a proper signature
    var signature = createSignature(cs);

    timedAmazonLink = "https://#arguments.bucketName#.s3.amazonaws.com/#arguments.fileKey#?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 (ex: c:\myfiles)
    */
    string function getObject( required string bucketName, required string fileKey, require string path ) {
    var httpService = new http();

    httpService.setMethod('GET');
    httpService.setURL( getObjectLink( bucketName = arguments.bucketName, fileKey = arguments.fileKey ) );
    httpService.setPath(arguments.path);
    httpService.setFile(arguments.file);

    httpService.send();

    return arguments.path & '\' & arguments.file;
    }

    /**
    * Deletes an object
    */
    void function deleteObject( required string bucketName, required string fileKey ) {

    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.fileKey#";

    // 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.bucketName#/#arguments.fileKey#');
    httpService.addParam( type = 'header', name = 'Date', value = dateTimeString );
    httpService.addParam( type = 'header', name = 'Authorization', value = 'AWS #variables.S3AccessKey#:#signature#' );

    httpService.send();

    }
    }