Skip to content

Instantly share code, notes, and snippets.

@Leigh-
Forked from christierney402/S3Wrapper.cfc
Last active August 28, 2018 12:15
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Leigh-/26993ed79c956c9309a9dfe40f1fce29 to your computer and use it in GitHub Desktop.
Save Leigh-/26993ed79c956c9309a9dfe40f1fce29 to your computer and use it in GitHub Desktop.
Amazon Web Services (AWS) S3 Wrapper for ColdFusion (Signature 4)
/**
* Amazon S3 REST Wrapper
* Version Date: 2016-04-12
*
* 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)
*
* Dependency on Sv4Util.cfc (Amazon Signature 4 Utility for ColdFusion)
* https://gist.github.com/Leigh-/26993ed79c956c9309a9dfe40f1fce29
*
* Changes: Modified to use Signature Version 4 (Alpha) - Leigh / cfsearching
* 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
*/
S3v4 function init( required string S3AccessKey, required string S3SecretAccessKey ) {
variables.S3AccessKey = arguments.S3AccessKey;
variables.S3SecretAccessKey = arguments.S3SecretAccessKey;
variables.Sv4Util = createObject('component', 'Sv4').init(arguments.S3AccessKey, arguments.S3SecretAccessKey);
variables.serviceName = 's3';
return this;
}
/**
* 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 )
* @bucketName the name of the target bucket
* @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 )
* @signedPayload - If true, include checksum (or hash) of request body in signature calculations. Default is true.
* @storageClass ( STANDARD | REDUCED_REDUNDANCY (noncritical, reproducible data at lower levels of redundancy) )
* @uploadDir local path the fileName attribute is stored including trailing "/"
*/
struct function putObject(
required string bucketName,
required string regionName,
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;',
boolean signedPayload = true
) {
var binaryFileData = '';
var props = '';
var httpService = '';
var cacheSeconds = arguments.cacheDays * 24 * 60 * 60;
var requestHeaders = '';
var requestHost = '';
var headerName = '';
// read the file data into a variable, sent as request body
// TODO: might need also use "ToBase64" per CF docs
binaryFileData = fileReadBinary( arguments.uploadDir & arguments.fileName );
// Generate host name
requestHost = getHostString( arguments.bucketName );
// Add desired headers. Mandatory headers (ie host and date) are added automatically
requestHeaders = { "X-Amz-Acl" = arguments.acl
, "Content-Type" = arguments.contentType
, "X-Amz-Storage-Class" = arguments.storageClass
};
// Optionally, encrypt (server side)
if (arguments.serverSideEncryption == 'AES256') {
requestHeaders["x-amz-server-side-encryption"] = "AES256";
}
// Optional, use cache control
if (arguments.cacheControl) {
requestHeaders["Cache-Control"] = "max-age=#cacheSeconds#";
}
// Get signature properties
props = variables.Sv4Util.generateSignatureData(
requestMethod = "PUT"
, hostName = requestHost
, regionName = arguments.regionName
, serviceName = variables.serviceName
, requestURI = arguments.keyName
, requestBody = binaryFileData
, requestHeaders = requestHeaders
, requestParams = {}
, signedPayload = true
);
// send the file to amazon
httpService = new http();
httpService.setMethod( props.requestMethod );
httpService.setURL( 'https://#props.hostName##props.canonicalURI#' );
httpService.addParam( type = 'header', name = 'Authorization', value=props.authorizationHeader );
for (headerName in props.requestHeaders) {
httpService.addParam( type='header', name=headerName, value=props.requestHeaders[ headerName ] );
}
httpService.addParam( type = 'body', value = binaryFileData);
return 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
*
* @bucketName the name of the target bucket
* @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.
* @regionName the name of the target region Example: "us-east-1"
* @minutesValid - The number of minutes the link should be valid
*/
string function getObjectLink(
required string bucketName
, required string keyName
, required string regionName
, string minutesValid = 1
) {
var props = '';
var expiresInSeconds = arguments.minutesValid * 60;
var params = { "X-Amz-Expires" = expiresInSeconds };
// Get signature properties
props = variables.Sv4Util.generateSignatureData(
requestMethod = "GET"
, hostName = getHostString( arguments.bucketName )
, regionName = arguments.regionName
, serviceName = variables.serviceName
, requestURI = arguments.keyName
, requestBody = "UNSIGNED-PAYLOAD"
, requestParams = params
, requestHeaders = {}
, signedPayload = false
);
return "https://#props.hostName##props.canonicalURI#?"& props.canonicalQueryString &"&X-Amz-Signature="& props.signature;
}
/**
* Download the specified file.
* @bucketName the name of the target bucket
* @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.
* @regionName the name of the target region Example: "us-east-1"
* @path physical path to store file including trailing slash (ex: c:\myfiles\)
* @returns Full file path.
*/
string function getObject(
required string bucketName
, required string keyName
, required string regionName
, string path = expandPath('./')
) {
var httpService = new http();
httpService.setMethod('GET');
httpService.setURL( getObjectLink( bucketName = arguments.bucketName, keyName = arguments.keyName, regionName = arguments.regionName ) );
httpService.setPath(arguments.path);
httpService.setFile(arguments.keyName);
httpService.send();
return arguments.path & arguments.keyName;
}
/**
* Deletes an object
* @bucketName the name of the target bucket
* @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.
* @regionName the name of the target region Example: "us-east-1"
*/
struct function deleteObject(
required string bucketName
, required string keyName
, required string regionName
) {
var props = '';
var httpService = '';
var headerName = '';
// Generate signature
props = variables.Sv4Util.generateSignatureData(
requestMethod = 'DELETE'
, hostName = getHostString( arguments.bucketName )
, regionName = arguments.regionName
, serviceName = variables.serviceName
, requestURI = '/'& arguments.keyName
, requestBody = 'UNSIGNED-PAYLOAD'
, requestHeaders = {}
, requestParams = {}
, signedPayload = true
);
// delete the object via REST
httpService = new http();
httpService.setMethod( props.requestMethod );
httpService.setURL('https://#props.hostName##props.canonicalURI#');
httpService.addParam( type = 'header', name = 'Authorization', value=props.authorizationHeader );
for (headerName in props.requestHeaders) {
httpService.addParam( type='header', name=headerName, value=props.requestHeaders[ headerName ] );
}
return httpService.send();
}
/**
* Construct AWS hostname string
* @returns host name
*/
private string function getHostString( string bucketName ) {
return arguments.bucketName &".s3.amazonaws.com";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment