Skip to content

Instantly share code, notes, and snippets.

@smrchy
Created March 17, 2011 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save smrchy/874365 to your computer and use it in GitHub Desktop.
Save smrchy/874365 to your computer and use it in GitHub Desktop.
Amazon Webservices CFC for Coldfusion
<!---
Parts of this are based on Tim Dawe's
http://amazonsig.riaforge.org
and
Joe Danziger's Amazon S3 REST Wrapper
http://amazons3.riaforge.org/
Written by Patrick Liess
Twitter: @smrchy
http://www.tcs.de
--->
<cfcomponent>
<cffunction name="init" output="false" hint="Returns an instance of the CFC initialized.">
<cfargument name="awsAccessKeyId" type="string" required="true" />
<cfargument name="secretAccessKey" type="string" required="true" />
<cfscript>
This.awsAccessKeyId = Arguments.awsAccessKeyId;
This.secretAccessKey = Arguments.secretAccessKey;
This.SQSVersion = "2009-02-01";
This.SQSserviceUrl = "http://queue.amazonaws.com/";
This.CFVersion = "2010-11-01";
return This;
</cfscript>
</cffunction>
<!--- CloudFront --->
<cffunction name="CFInvalidation" output="false" returntype="any">
<cfargument name="distributionid" type="string" required="yes">
<cfargument name="patharray" type="array" required="yes">
<cfset var i = 0>
<cfset var dateTimeString = GetHTTPTimeString(Now())>
<cfset var cs = "#dateTimeString#">
<cfset var signature = createSignature(cs)>
<cfset var thexml = '<InvalidationBatch>'>
<cfloop index="i" array="#Arguments.patharray#">
<cfset thexml = thexml & '<Path>#i#</Path>'>
</cfloop>
<cfset thexml = thexml & '<CallerReference>#CreateUUID()#</CallerReference></InvalidationBatch>'>
<cfhttp method="POST" url="https://cloudfront.amazonaws.com/#This.CFVersion#/distribution/#Arguments.distributionid#/invalidation">
<cfhttpparam type="header" name="Date" value="#dateTimeString#">
<cfhttpparam type="header" name="Content-Type" value="text/xml">
<cfhttpparam type="header" name="Authorization" value="AWS #This.awsAccessKeyId#:#signature#">
<cfhttpparam type="body" value="#thexml#">
</cfhttp>
<cfreturn cfhttp>
</cffunction>
<!--- S3 --->
<cffunction name="S3createSignedURL" output="false" returntype="string">
<cfargument name="bucketName" type="string" required="yes">
<cfargument name="fileKey" type="string" required="yes">
<cfargument name="minutesValid" type="string" required="false" default="60">
<cfargument name="secure" type="boolean" required="false" default="false">
<cfscript>
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);
// Create the timed link for the image
var protocol = 'http://';
if (arguments.secure) {
protocol = 'https://';
}
var timedAmazonLink = protocol & arguments.bucketName&'.s3.amazonaws.com/' & arguments.fileKey & '?AWSAccessKeyId=' & URLEncodedFormat(This.awsAccessKeyId) & '&Expires=' & epochTime & '&Signature=' & URLEncodedFormat(signature);
return timedAmazonLink;
</cfscript>
</cffunction>
<cffunction name="S3deleteObject" output="false" returntype="struct">
<cfargument name="bucketName" type="string" required="yes">
<cfargument name="fileKey" type="string" required="yes">
<cfset var dateTimeString = GetHTTPTimeString(Now())>
<cfset var cs = "DELETE\n\n\n#dateTimeString#\n/#arguments.bucketName#/#arguments.fileKey#">
<cfset var signature = createSignature(cs)>
<cfhttp method="DELETE" url="http://s3.amazonaws.com/#arguments.bucketName#/#arguments.fileKey#">
<cfhttpparam type="header" name="Date" value="#dateTimeString#">
<cfhttpparam type="header" name="Authorization" value="AWS #This.awsAccessKeyId#:#signature#">
</cfhttp>
<!--- We return the full struct so we can check the cfhttp.statuscode for errors --->
<cfreturn cfhttp>
</cffunction>
<cffunction name="S3putObject" access="public" output="false" returntype="string">
<cfargument name="bucketName" type="string" required="yes">
<cfargument name="filekey" type="string" required="yes">
<cfargument name="filebinary" type="binary" required="yes">
<cfargument name="contentType" type="string" required="yes">
<cfargument name="ACL" type="string" required="no" default="public-read">
<cfargument name="HTTPtimeout" type="numeric" required="no" default="300">
<cfargument name="cacheControl" type="boolean" required="no" default="true">
<cfargument name="cacheDays" type="numeric" required="no" default="300">
<cfset var dateTimeString = GetHTTPTimeString(Now())>
<cfset var cacheSeconds = Arguments.cacheDays * 86400>
<cfset var cs = "PUT\n\n#arguments.contentType#\n#dateTimeString#\nx-amz-acl:#arguments.ACL#\n/#arguments.bucketName#/#arguments.filekey#">
<!--- Never allow cacheControl for secure images --->
<cfif arguments.ACL NEQ "public-read">
<cfset arguments.cacheControl = false>
</cfif>
<cfset var signature = createSignature(cs)>
<!--- Send the file to amazon. The "x-amz-acl" controls the access properties of the file --->
<cfhttp method="PUT" url="http://s3.amazonaws.com/#arguments.bucketName#/#arguments.fileKey#" timeout="#arguments.HTTPtimeout#">
<cfhttpparam type="header" name="Authorization" value="AWS #This.awsAccessKeyId#:#signature#">
<cfhttpparam type="header" name="Content-Type" value="#arguments.contentType#">
<cfhttpparam type="header" name="Date" value="#dateTimeString#">
<cfhttpparam type="header" name="x-amz-acl" value="#arguments.ACL#">
<cfhttpparam type="body" value="#Arguments.filebinary#">
<cfif arguments.cacheControl>
<cfhttpparam type="header" name="Cache-Control" value="max-age=#cacheSeconds#">
</cfif>
</cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<!--- SQS --->
<cffunction name="SQSgetQueueAttributes" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="name" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=GetQueueAttributes" &
"&AttributeName=" & Arguments.name &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<cffunction name="SQSdeleteMessage" output="false" returntype="struct">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="receipthandle" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=DeleteMessage" &
"&ReceiptHandle=" & URLEncodedFormat(Arguments.receipthandle,"utf-8") &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<!--- We return the full struct so we can check the cfhttp.statuscode for errors --->
<cfreturn cfhttp>
</cffunction>
<cffunction name="SQSreceiveMessage" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="amount" type="numeric" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=ReceiveMessage" &
"&AttributeName=None" &
"&MaxNumberOfMessages=" & Arguments.amount &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<cffunction name="SQSsendMessage" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="msg" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=SendMessage" &
"&MessageBody=" & URLEncodedFormat(Arguments.msg,"utf-8") &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<cffunction name="SQSsetQueueAttributes" output="false" returntype="string">
<cfargument name="queue" type="string" required="true"/>
<cfargument name="name" type="string" required="true"/>
<cfargument name="value" type="string" required="true"/>
<cfset var requrl= This.SQSserviceUrl & Arguments.queue &
"?Action=SetQueueAttributes" &
"&Attribute.Name=" & Arguments.name &
"&Attribute.Value=" & Arguments.value &
"&Version=" & This.SQSVersion>
<cfset requrl = signRequest(requrl,"GET")>
<cfhttp method="GET" url="#requrl#" charset="UTF-8"></cfhttp>
<cfreturn cfhttp.FileContent>
</cffunction>
<!---- Util functions --->
<cffunction name="signRequest" returntype="string" output="false">
<cfargument name="request" required="yes" type="string">
<cfargument name="method" required="no" default="GET" type="string">
<cfscript>
var lc = structnew();
// Extract the URL part of the request and strip the protocol
lc.requesturl = listfirst(arguments.request, "?");
lc.requesturl = replacenocase(lc.requesturl, "http://", "");
// Split into host and path
lc.host = listfirst(lc.requesturl, "/");
lc.path = right(lc.requesturl, len(lc.requesturl) - len(lc.host));
// Process the query string parameters into a structure
lc.querystring = listlast(arguments.request, "?");
lc.strParams = structnew();
</cfscript>
<cfloop list="#lc.querystring#" index="i" delimiters="&">
<cfset lc.strParams[listfirst(i, "=")] = urldecode(listlast(i, "="))>
</cfloop>
<cfscript>
// Add the timestamp
if (not StructKeyExists(lc.strParams, "Timestamp")) {
lc.utcdate = dateconvert("local2Utc", now());
lc.strParams["Timestamp"] = dateformat(lc.utcdate, 'yyyy-mm-dd') & "T" & timeformat(lc.utcdate, 'HH:mm:ss') & "Z";
}
// Add the standard parameters
lc.strParams["AWSAccessKeyId"] = This.awsAccessKeyId;
lc.strParams["SignatureVersion"] = 2;
lc.strParams["SignatureMethod"] = "HmacSHA1";
// Sort the parameters
lc.keys = listsort(structkeylist(lc.strParams), "text");
// Generate a new query string including timestamp, with parameters in the correct order, encoding as we go
lc.qs = "";
</cfscript>
<cfloop list="#lc.keys#" index="i">
<cfset lc.qs = lc.qs & rfc3986EncodedFormat(i) & "=" & rfc3986EncodedFormat(lc.strParams[i]) & "&">
</cfloop>
<cfscript>
// Strip off the last &
lc.qs = left(lc.qs, len(lc.qs)-1);
// Build the string to sign
lc.stringToSign = arguments.method & chr(10);
lc.stringToSign = lc.stringToSign & lc.host & chr(10);
lc.stringToSign = lc.stringToSign & lc.path & chr(10);
lc.stringToSign = lc.stringToSign & lc.qs;
lc.signature = HMAC_SHA1(lc.stringToSign);
// Return the new request URL
return "http://" & lc.host & lc.path & "?" & lc.qs & "&Signature=" & urlencodedformat(tobase64(lc.signature));
</cfscript>
</cffunction>
<cffunction name="HMAC_SHA1" returntype="binary" access="private" output="false">
<cfargument name="signMessage" type="string" required="true" />
<cfscript>
var jMsg = JavaCast("string",arguments.signMessage).getBytes("iso-8859-1");
var jKey = JavaCast("string",This.secretAccessKey).getBytes("iso-8859-1");
var key = createObject("java","javax.crypto.spec.SecretKeySpec");
var mac = createObject("java","javax.crypto.Mac");
key = key.init(jKey,"HmacSHA1");
mac = mac.getInstance(key.getAlgorithm());
mac.init(key);
mac.update(jMsg);
return mac.doFinal();
</cfscript>
</cffunction>
<cffunction name="createSignature" returntype="string" access="public" output="false">
<cfargument name="stringIn" type="string" required="true" />
<cfset var fixedData = replace(arguments.stringIn,"\n","#chr(10)#","all")>
<cfset var digest = HMAC_SHA1(fixedData)>
<cfset var signature = ToBase64("#digest#")>
<cfreturn signature>
</cffunction>
<cffunction name="rfc3986EncodedFormat" returntype="string" output="false">
<cfargument name="text" required="yes" type="string">
<cfset var lc = structnew()>
<cfset lc.objNet = createObject("java","java.net.URLEncoder")>
<cfset lc.encodedText = lc.objNet.encode(arguments.text, 'utf-8').replace("+", "%20").replace("*", "%2A").replace("%7E", "~")>
<cfreturn lc.encodedText>
</cffunction>
</cfcomponent>
@CFPro
Copy link

CFPro commented Nov 28, 2011

Can someone explain the usage syntax on this CFC?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment