November 26, 2014

Here's a quick example showing how to upload a file directly to Amazon S3 (bypassing your server). The tricky part in getting this to work is that you don't want to allow anyone to upload a file anywhere on your S3. To accomplish this you can create an AWS Access Control Policy, base64 encode it, and then sign it using HMAC-SHA1 with your AWS Secret Key. A policy is a JSON string that might look like this:

{ "expiration": "2014-11-26T13:23:00.000Z",
  "conditions": [
    {"bucket": "example-bucket-name" },
    ["eq", "$key", "image.jpg"],
    {"acl": "public-read" },
    {"redirect": "https://example.com/upload-complete.cfm" },
    ["starts-with", "$Content-Type", "image/"]
  ]
}

To generate this policy dynamically we might do something like this:

<cfset expDate = DateConvert("local2utc", now())>
<cfset expDate = DateAdd("n", 15, expDate)><!--- policy expires in 15 minutes --->
<cfset fileName = CreateUUID() & ".jpg">
<cfoutput>
<cfsavecontent name="jsonPolicy">
{ "expiration": "#DateFormat(expDate, "yyyy-mm-dd")#T#TimeFormat(expDate, "HH:mm")#:00.000Z",
  "conditions": [
    {"bucket": "example-bucket-name" },
    ["eq", "$key", "#JSStringFormat(fileName)#"],
    {"acl": "public-read" },
    {"redirect": "https://example.com/upload-complete.cfm" },
    ["content-length-range", 1, 1048576],
    ["starts-with", "$Content-Type", "image/"]
  ]
}
</cfsavecontent>
</cfoutput>
<cfset b64Policy = toBase64(Trim(jsonPolicy), "utf-8")>
<cfset signature = HMac(b64Policy, variables.awsSecretKey, "HMACSHA1", "utf-8")>
<!--- convert signature from hex to base64 --->
<cfset signature = binaryEncode( binaryDecode( signature, "hex" ), "base64")>

Because we are using the HMac function you must be on CF10+ or Railo 4.1+ if you are on an older version you will need to find a third party hmac implementation.

Next you create a form that posts directly to Amazon S3:

<form action="https://example-bucket-name.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
    <input type="hidden" name="key" value="#EncodeForHTMLAttribute(fileName)#" /
    <input type="hidden" name="acl" value="public-read" />
    <input type="hidden" name="redirect" value="https://example.com/upload-complete.cfm" >
    <input type="hidden" name="AWSAccessKeyId " value="#EncodeForHTMLAttribute(variables.awsAccessKeyID)#" />
    <input type="hidden" name="Policy" value="#b64Policy#" />
    <input type="hidden" name="Signature" value="#signature#" />
    File: <input type="file" name="file" />
    <input type="submit" name="submit" value="Upload to Amazon S3" />
</form>

According to the S3 documentation there are some conditions in which the redirect will not happen:

Please note that the redirect is not guaranteed to be followed. It is possible that an upload would succeed, but that a networking problem on the end-users network prevents them from following the redirect. It is also possible that in certain failure conditions, that a file is actually uploaded but you are not notified about the upload.

To get around this uncertainty you can setup Event Notifications for the S3 bucket you are uploading to.

The other thing to note is that if an error occurs the redirect will not happen and the user will be presented with an XML error message. To handle that more gracefully you could upload the file via AJAX, and then handle the error condition within JavaScript.

October 28, 2014

As you may know I run a few doc shortcut sites, cfdocs.org for CFML has been going quite well since publishing the source on github with a bunch of community contributions.

The other site I use alot is JavaDocs.org, I recently updated it to point to Java SE Version 8, and JEE Version 7. I also added package shortcuts to it, so you can hit urls like javadocs.org/javax.servlet and get a summary of all the classes / interfaces in that package.

I hope you find the sites handy.