Amazon S3 Form Post - Invalid Policy: Invalid Simple-Condition: Value Must Be A String
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