Skip to main content
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Katie Atkinson and Aral Balkan
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Katie Atkinson ( @katieatkinson ) Aral Balkan ( @aral )

Amazon S3 Form Post - Invalid Policy: Invalid Simple-Condition: Value Must Be A String

By on
Tags:

Earlier, I posted an example of using Plupload to upload files directly to Amazon's Simple Storage Service (S3). One of the [many] snags that I ran into as I was putting the demo together, was a problem with the "success_action_status" field that I was providing in my Policy document. Amazon kept telling me that the value must be a string. Specifically, it kept returning the following XML error response:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
	<Code>InvalidPolicyDocument</Code>
	<Message>Invalid Policy: Invalid Simple-Condition: value must be a string.</Message>
	<RequestId>590A7E6E7931A592</RequestId>
	<HostId>4zvHpz/FB/rM4FREM9LB3YhJpfAQ05miS08t8ok6iZQTf2qfCeEXXh7jpvm9HJYm</HostId>
</Error>

This problem took me a great deal of head-scratching to figure out. In retrospect, the error message makes sense, if you really understand ColdFusion; but, if you're in the middle of trying to get something totally new to work (like uploading to Amazon S3 using Plupload), you can easily lose sight of what's happening.

The problem was not in my code; rather, it was a byproduct of the way ColdFusion serializes data into JavaScript Object Notation (JSON). If you look at my policy, there's nothing inherently wrong with it:

<cfscript>

	// Define the policy for Amazon S3 form POST.
	policy = {
		"expiration" = (
			dateFormat( expiration, "yyyy-mm-dd" ) & "T" &
			timeFormat( expiration, "HH:mm:ss" ) & "Z"
		),
		"conditions" = [
			{
				"bucket" = aws.bucket
			},
			{
				"acl" = "private"
			},
			{
				"success_action_status" = "201"
			},
			[ "starts-with", "$key", "pluploads/" ],
			[ "starts-with", "$Content-Type", "image/" ],
			[ "content-length-range", 0, 10485760 ]
		]
	};


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// Encode the policy as JavaScript Object Notation (JSON).
	serializedPolicy = serializeJson( policy );

</cfscript>

As you can see, I am defining the "success_action_status" parameter as the string, "201". Then, I am serializing it as JSON. So far so good.

The problem is, ColdFusion is a typeless language (to some degree). It tries to blur the line between simple values, swapping strings for numbers, numbers for booleans, strings for dates, and so on. This is great when processing incoming Form data and parsing date/time values; but, it's unfortunate when it comes to passing data out of ColdFusion and into a 3rd-party context.

When ColdFusion was serializing the Policy, it was actually converting the string, "201," into the number, 201. Then, when Amazon went to deserialize the data, it saw a number, not a string.

To work around this serialization problem, I ended up defining the policy with an invalid but easily identifiable string; then, I replaced that string after serialization:

<cfscript>

	// Define the policy for Amazon S3 form POST.
	policy = {
		"expiration" = (
			dateFormat( expiration, "yyyy-mm-dd" ) & "T" &
			timeFormat( expiration, "HH:mm:ss" ) & "Z"
		),
		"conditions" = [
			{
				"bucket" = aws.bucket
			},
			{
				"acl" = "private"
			},
			{
				"success_action_status" = "2xx"
			},
			[ "starts-with", "$key", "pluploads/" ],
			[ "starts-with", "$Content-Type", "image/" ],
			[ "content-length-range", 0, 10485760 ]
		]
	};


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	// Encode the policy as JavaScript Object Notation (JSON).
	serializedPolicy = serializeJson( policy );

	// When the policy is being serialized, ColdFusion will try to turn
	// "201" into the number 201. However, we NEED this value to be a
	// STRING. As such, we'll give the policy a non-numeric value and
	// then convert it to the appropriate 201 after serialization.
	serializedPolicy = replace( serializedPolicy, "2xx", "201" );

</cfscript>

With this code in place, it ensured that the serialized Policy contained a string representing the status code, "201." It's an unfortunate requirement (and I think something that ColdFusion is planning to fix in upcoming releases; but, until then, I hope that this can help someone else!

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel