amazon-archives / aws-sdk-unity

ARCHIVED: The aws sdk for unity is now distributed as a part of aws sdk for dotnet:
https://github.com/aws/aws-sdk-net
Other
105 stars 43 forks source link

Bug: PostObject does not support PostObject.Metadata.Add() #62

Closed citiusaltius closed 9 years ago

citiusaltius commented 9 years ago

Hi,

Thanks so much for the developer preview so far!

I've got two questions:

  1. I'm coming to an issue uploading to S3 when I'm using metadata. For note, the example shown here - uploads without a problem (cognito-id changed here):
        // Assign 5 MB memorystream max
        MemoryStream ms = new MemoryStream(5000000);
        StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
        sw.Write("test test");
        sw.Flush();
        ms.Seek (0, SeekOrigin.Begin);

        Debug.Log ("reader body:\n" + System.Text.Encoding.UTF8.GetString(ms.ToArray()));

        var request = new PostObjectRequest()
        {
            Bucket = ManagerData.bucket,
            Key = "us-east-1:1111111-1111-1111-1111-111111111111/uploads/conversations/12345/635679327928576222_us-east-1:1111111-1111-1111-1111-111111111111",
            InputStream = ms,
            CannedACL = Amazon.S3.S3CannedACL.Private
        };
        Debug.Log ("\nMaking HTTP post call");

        ManagerData.s3client.PostObjectAsync(request, (responseObj) =>
                               {
            if (responseObj.Exception == null)
            {
                Debug.Log(string.Format("\nobject {0} posted to bucket {1}", responseObj.Request.Key, responseObj.Request.Bucket));
            }
            else
            {
                Debug.Log ("\nException while posting the result object");
                Debug.Log ( string.Format("\n receieved error {0}", responseObj.Response.HttpStatusCode.ToString()) );
            }
        });

However, when I add a metadata value like so - request.Metadata.Add ("test", "value");, shown below:

        // Assign 5 MB memorystream max
        MemoryStream ms = new MemoryStream(5000000);
        StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
        sw.Write("test test");
        sw.Flush();
        ms.Seek (0, SeekOrigin.Begin);

        Debug.Log ("reader body:\n" + System.Text.Encoding.UTF8.GetString(ms.ToArray()));

        var request = new PostObjectRequest()
        {
            Bucket = ManagerData.bucket,
            Key = "us-east-1:1111111-1111-1111-1111-111111111111/uploads/conversations/12345/635679327928576222_us-east-1:1111111-1111-1111-1111-111111111111",
            InputStream = ms,
            CannedACL = Amazon.S3.S3CannedACL.Private
        };

        // adding metadata here
        request.Metadata.Add ("test", "value");

        Debug.Log ("\nMaking HTTP post call");

        ManagerData.s3client.PostObjectAsync(request, (responseObj) =>
                               {
            if (responseObj.Exception == null)
            {
                Debug.Log(string.Format("\nobject {0} posted to bucket {1}", responseObj.Request.Key, responseObj.Request.Bucket));
            }
            else
            {
                Debug.Log ("\nException while posting the result object");
                Debug.Log ( string.Format("\n receieved error {0}", responseObj.Response.HttpStatusCode.ToString()) );
            }
        });

All of a sudden, I get a '403 Forbidden'.

  1. I would like to use SSE-KMS and provide my key. However, I seem to have issues specifying metadata - how do I go about doing this?

What's going on here? Am I adding metadata incorrectly?

Thanks so much!

karthiksaligrama commented 9 years ago

can you paste the curl response here?

citiusaltius commented 9 years ago

I don't actually get a curl line, so I don't get a curl response. I've added a few debug statements to show my request: (Access Key and S3 Key changed here, of course)

--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="key"

us-east-1:1111111-1111-1111-1111-111111111111/uploads/conversations/12345/635679327928576222_us-east-1:1111111-1111-1111-1111-111111111111
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="acl"

private
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="Content-Type"

application/octet-stream
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="x-amz-meta-test"

value
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="x-amz-security-token"

AQoDYXdzEOT//////////wEa4APsKJatubZromn/OBH0I/WICuFPesqQKy90FsdSYJRbAYBsGER/GugdlkD/5o67L4aAhdJC7IdgA4gtUFDjXJZIMr4qinpvGxDH5EfgkUZMQS3OxdgzcuQLzXwwAfwyQxuyxu3zgtqDHCWNOxiy1LdfvYJMhlv1ML24j2Qp2jchubyuR/7Jl7Fj6n6SSYlFdteKdLxSB6gzbjYtbQp16Nu7U2tvrVd47T23N0LmHS3dE+Qcsrl2PMUwF81vlOPVoFh7wBue6dBWm4WU+KyCUtCVh8NQIwR4+s2CRyMmUxNjgbCNQ4ToG55/6UlLbd8RryD4oLPky3wfjVgwkc0nYCJTDfWhjfG7SBXkRjGM6XE30bcKRlEj0Vd+NRjOW/68xxwurxU0bFWPsTPFdfPWTqAI917Yzkvp4Zaka/xzhuOpsgbGuXKyxUUn1k6QqnaRAqZu8mYvbuNThJvV1f2FKNCpvoVi9naLP3IGWC5QVvL7NFN6mDgJ82JIJTOyq4Q98GntA6kwNSfgYte+wrWWf6F0eloWLPXhNwJu7qF0Sk8oItWghy5GeZYdgS/3l4MIjJh30EtIOmTq5xX8UNQUMwys5rIFLSPUahB4rJ8HbqGRCYXb+T3mUUBXW3jVeImoctgg9siPqwU=
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="AWSAccessKeyId"

ASIAINUMHFVALIDKEY
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="Policy"

eyJleHBpcmF0aW9uIjoiMjAxNS0wNS0yN1QwMzoxMTo1MFoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJoaXBhYWNoYXRwcm90b3R5cGUifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzLWVhc3QtMToxNWRiYmEzYy1mMmNjLTRmNWItYjQwMy1lYWU3NGNmZWJmYzMvdXBsb2Fkcy9jb252ZXJzYXRpb25zLzEyMzQ1LyJdLHsiYWNsIjoicHJpdmF0ZSJ9LFsiZXEiLCIkQ29udGVudC1UeXBlIiwiYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtIl0seyJ4LWFtei1zZWN1cml0eS10b2tlbiI6IkFRb0RZWGR6RU9ULy8vLy8vLy8vL3dFYTRBUHNLSmF0dWJacm9tbi9PQkgwSS9XSUN1RlBlc3FRS3k5MEZzZFNZSlJiQVlCc0dFUi9HdWdkbGtELzVvNjdMNGFBaGRKQzdJZGdBNGd0VUZEalhKWklNcjRxaW5wdkd4REg1RWZna1VaTVFTM094ZGd6Y3VRTHpYd3dBZnd5UXh1eXh1M3pndHFESENXTk94aXkxTGRmdllKTWhsdjFNTDI0ajJRcDJqY2h1Ynl1Ui83Smw3Rmo2bjZTU1lsRmR0ZUtkTHhTQjZnemJqWXRiUXAxNk51N1UydHZyVmQ0N1QyM04wTG1IUzNkRStRY3NybDJQTVV3RjgxdmxPUFZvRmg3d0J1ZTZkQldtNFdVK0t5Q1V0Q1ZoOE5RSXdSNCtzMkNSeU1tVXhOamdiQ05RNFRvRzU1LzZVbExiZDhScnlENG9MUGt5M3dmalZnd2tjMG5ZQ0pURGZXaGpmRzdTQlhrUmpHTTZYRTMwYmNLUmxFajBWZCtOUmpPVy82OHh4d3VyeFUwYkZXUHNUUEZkZlBXVHFBSTkxN1l6a3ZwNFpha2EveHpodU9wc2diR3VYS3l4VVVuMWs2UXFuYVJBcVp1OG1ZdmJ1TlRoSnZWMWYyRktOQ3B2b1ZpOW5hTFAzSUdXQzVRVnZMN05GTjZtRGdKODJKSUpUT3lxNFE5OEdudEE2a3dOU2ZnWXRlK3dyV1dmNkYwZWxvV0xQWGhOd0p1N3FGMFNrOG9JdFdnaHk1R2VaWWRnUy8zbDRNSWpKaDMwRXRJT21UcTV4WDhVTlFVTXd5czVySUZMU1BVYWhCNHJKOEhicUdSQ1lYYitUM21VVUJYVzNqVmVJbW9jdGdnOXNpUHF3VT0ifV19
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="Signature"

evgmh7kWJ3inTLOHgOgpxNWQhKg=
--awov3ShCJ0qmjWFKz5erxwzz
Content-Disposition: form-data; name="file"

test test
--awov3ShCJ0qmjWFKz5erxwzz--

I get an error here:

recieved error response
UnityEngine.Debug:Log(Object)
Amazon.Runtime.Internal.Util.UnityDebugLogger:DebugFormat(String, Object[]) (at Assets/AWSSDK/src/Core/Amazon.Runtime/Internal/Util/_unity/UnityDebugLogger.cs:71)
Amazon.Runtime.Internal.Util.Logger:DebugFormat(String, Object[]) (at Assets/AWSSDK/src/Core/Amazon.Runtime/Internal/Util/Logger.cs:174)
Amazon.Runtime.Internal.Transform.UnityWebResponseData:.ctor(WWW) (at Assets/AWSSDK/src/Core/Amazon.Runtime/Internal/Transform/_unity/UnityWebResponseData.cs:60)
Amazon.Runtime.Internal.<InvokeRequest>c__Iterator3:MoveNext() (at Assets/AWSSDK/src/Core/Amazon.Runtime/Pipeline/_unity/UnityMainThreadDispatcher.cs:134)

One of my debug lines give me this HttpStatusCode:

Response: Forbidden

Length of my response is 294, but I can't seem to read it, as any effort to access responseObj.response.OpenResponse() throws an exception because it's null. Do you have any other suggestions to get you that curl URL?

citiusaltius commented 9 years ago

Just found the exact response problem:

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Invalid according to Policy: Extra input fields: x-amz-meta-test</Message><RequestId>7F015EBF3CD551C1</RequestId><HostId>CtGbz2rzl3cZ2TOb3jDMZtjexG/axvrHarSmjUQzYiVM/2u6xuwMyhTVy1uyErY/8fPRA8h9G7c=</HostId></Error>
UnityEngine.Debug:Log(Object)
Amazon.Runtime.Internal.Transform.UnityWebResponseData:.ctor(WWW) (at Assets/AWSSDK/src/Core/Amazon.Runtime/Internal/Transform/_unity/UnityWebResponseData.cs:69)
Amazon.Runtime.Internal.<InvokeRequest>c__Iterator3:MoveNext() (at Assets/AWSSDK/src/Core/Amazon.Runtime/Pipeline/_unity/UnityMainThreadDispatcher.cs:134)

Seems like adding metadata doesn't directly add a new policy option. What would be the best way to resolve this - to manually create a policy for every upload, or..? Thanks!

karthiksaligrama commented 9 years ago

Thanks. The post Object api in aws sdk for unity provides a default signed policy which doesn't add metadata content.

In this case its best to create a custom policy during every upload.

You can take a look @ CreateSignedPolicy(PostObjectRequest request) and write your own customize the policyString as per your requirement in your class and set the signed policy request parameter.

You can refer to S3 Browser Post Upload Examples to create a custom policy string.

We plan to add a policy builder support which would make this more simpler.

citiusaltius commented 9 years ago

Thanks - I could push a commit if you'd like, but here's what's worked for me (could either do a separate function or just modify the signed policy function):

        // Create signed policy
        StringBuilder metadata_add_policy = new StringBuilder();
        foreach (string key in por.Metadata.Keys) {
            string key_lower = key.ToLower();
            Debug.Log ("-metadata: " + key_lower + ":\t" + por.Metadata[key]);
            metadata_add_policy.Append(",\n{\"");
            if(key.StartsWith("x-amz") == true)
            {
                metadata_add_policy.Append(key_lower + "\": \"" + por.Metadata[key]);
            }
            else 
            {
                metadata_add_policy.Append("x-amz-meta-" + key_lower + "\": \"" + por.Metadata[key]);
            }
            metadata_add_policy.Append("\"}");
        }

        Debug.Log ("metadata policy addition:\n" + metadata_add_policy.ToString ());

        string policyString = "";
        if (por.Key.LastIndexOf ('/') == -1) {
            policyString = "{\"expiration\": \"" 
                + System.DateTime.UtcNow.AddHours (24).ToString ("yyyy-MM-ddTHH:mm:ssZ") 
                + "\",\"conditions\": " 
                + "["
                + "{\"bucket\": \"" + por.Bucket + "\"},"
                + "[\"starts-with\", \"$key\", \"" + "\"],"
                + "{\"acl\": \"private\"},"
                + "[\"eq\", \"$Content-Type\", " + "\"" + por.ContentType + "\"" + "]"
                + metadata_add_policy.ToString ()
                + "]}";
        } else {
            policyString = "{\"expiration\": \"" 
                + System.DateTime.UtcNow.AddHours (24).ToString ("yyyy-MM-ddTHH:mm:ssZ") 
                + "\",\"conditions\": "
                + "["
                + "{\"bucket\": \"" + por.Bucket + "\"},"
                + "[\"starts-with\", \"$key\", \"" + por.Key.Substring (0, por.Key.LastIndexOf ('/')) + "/\"],"
                + "{\"acl\": \"private\"},"
                + "[\"eq\", \"$Content-Type\", " + "\"" + por.ContentType + "\"" + "]"
                + metadata_add_policy.ToString ()
                + "]}";
        }
        Debug.Log ("policy string:\n" + policyString);
        por.SignedPolicy = Amazon.S3.Util.S3PostUploadSignedPolicy.GetSignedPolicy(policyString, ManagerData.s3client.Credentials);
citiusaltius commented 9 years ago

One further question:

Adding the x-amz-server-side​-encryption: aws:kms and x-amz-server-side-encryption-aws-kms-key-id: 11111111-1111-1111-1111-111111111111 with this request

--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="key"

us-east-1:11111111-1111-1111-1111-111111111111/uploads/conversations/12345/635682432322693886_us-east-1:11111111-1111-1111-1111-111111111111
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="acl"

private
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="Content-Type"

message
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-server-side​-encryption"

aws:kms
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-server-side-encryption-aws-kms-key-id"

11111111-1111-1111-1111-111111111111
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-meta-user_id"

us-east-1:15dbba3c-f2cc-4f5b-b403-eae74cfebfc3
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-meta-date"

5/26/2015 1:20:32 PM
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-meta-object_type"

message
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-meta-s3_key"

us-east-1:15dbba3c-f2cc-4f5b-b403-eae74cfebfc3/uploads/conversations/12345/635682432322693886_us-east-1:15dbba3c-f2cc-4f5b-b403-eae74cfebfc3
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="x-amz-security-token"

AQoDYXdzEO7//////////wEa4AMY/rz8s+NuVjofCptpO7T+sjCy2oVxbzRi/XrPpZQzAxetRuvjT+iE8vk74ETxF3Celzza2gb12oG3nsIY41R8wXLtH9f0sRs9llnLVCn2XbqwwgDohCIq3hE/fX3iIdhqirbaAXOebtgOcw2EFaQ/K9shnh+bAdQDa7uschbK5pZ0kH3hafrnVOB7SvooWa9m55IbWirh2wYUBr/lrOY5Ge1Tl7tc7heYKspqjBRUriHvwZnBCaDRKTnxEH6Ft0f2IFRPB87uYH0cpO7kOYZGaSklx9ulOVIklwGQUCNsRG5paIyAfpd8hok9fk8MVpE+l8caRWBcEJq0IiHNBN0Je4jVzZT0WSeJ7gsFe7LDLbstYT6qpN9yNObZSTiOSOJR3SFUISKF8a9lfa+WUme0SfOqiJ/yBZE/mJsjdjd/ZEPhW6PpHwu8X79KPjU0VCM6x+a6GYsgrQbBezS1ee2p9x/HjOzC6BwPoG3K7E7Bevnmv+r/HnL6n1bLuFlfeIN/PZmJois+80dHL+sGqjbiXpUR9V3hWld30fhAmRsTUHBSEI4/F8t2Cu91mftE4RAZ/cfLl5OrOBFjtsq9oW/71Gk8HaahznsT1mSQ9Erk0HQHEDMbcuTLitgp1vd1BtkgoeaRqwU=
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="AWSAccessKeyId"

ASIAJDCWILHV6C7PKA2Q
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="Policy"

eyJleHBpcmF0aW9uIjoiMjAxNS0wNS0yN1QxMzoyMDozMloiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJoaXBhYWNoYXRwcm90b3R5cGUifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzLWVhc3QtMToxNWRiYmEzYy1mMmNjLTRmNWItYjQwMy1lYWU3NGNmZWJmYzMvdXBsb2Fkcy9jb252ZXJzYXRpb25zLzEyMzQ1LyJdLHsiYWNsIjoicHJpdmF0ZSJ9LFsiZXEiLCIkQ29udGVudC1UeXBlIiwibWVzc2FnZSJdLHsieC1hbXotc2VydmVyLXNpZGXigIstZW5jcnlwdGlvbiI6ImF3czprbXMifSx7IngtYW16LXNlcnZlci1zaWRlLWVuY3J5cHRpb24tYXdzLWttcy1rZXktaWQiOiJkOWY4YTRhZC0zZDg2LTQ1NDEtYjZhZC1iOWNhM2E0ZWU2ZGIifSx7IngtYW16LW1ldGEtdXNlcl9pZCI6InVzLWVhc3QtMToxNWRiYmEzYy1mMmNjLTRmNWItYjQwMy1lYWU3NGNmZWJmYzMifSx7IngtYW16LW1ldGEtZGF0ZSI6IjUvMjYvMjAxNSAxOjIwOjMyIFBNIn0seyJ4LWFtei1tZXRhLW9iamVjdF90eXBlIjoibWVzc2FnZSJ9LHsieC1hbXotbWV0YS1zM19rZXkiOiJ1cy1lYXN0LTE6MTVkYmJhM2MtZjJjYy00ZjViLWI0MDMtZWFlNzRjZmViZmMzL3VwbG9hZHMvY29udmVyc2F0aW9ucy8xMjM0NS82MzU2ODI0MzIzMjI2OTM4ODZfdXMtZWFzdC0xOjE1ZGJiYTNjLWYyY2MtNGY1Yi1iNDAzLWVhZTc0Y2ZlYmZjMyJ9LHsieC1hbXotc2VjdXJpdHktdG9rZW4iOiJBUW9EWVhkekVPNy8vLy8vLy8vLy93RWE0QU1ZL3J6OHMrTnVWam9mQ3B0cE83VCtzakN5Mm9WeGJ6UmkvWHJQcFpRekF4ZXRSdXZqVCtpRTh2azc0RVR4RjNDZWx6emEyZ2IxMm9HM25zSVk0MVI4d1hMdEg5ZjBzUnM5bGxuTFZDbjJYYnF3d2dEb2hDSXEzaEUvZlgzaUlkaHFpcmJhQVhPZWJ0Z09jdzJFRmFRL0s5c2huaCtiQWRRRGE3dXNjaGJLNXBaMGtIM2hhZnJuVk9CN1N2b29XYTltNTVJYldpcmgyd1lVQnIvbHJPWTVHZTFUbDd0YzdoZVlLc3BxakJSVXJpSHZ3Wm5CQ2FEUktUbnhFSDZGdDBmMklGUlBCODd1WUgwY3BPN2tPWVpHYVNrbHg5dWxPVklrbHdHUVVDTnNSRzVwYUl5QWZwZDhob2s5Zms4TVZwRStsOGNhUldCY0VKcTBJaUhOQk4wSmU0alZ6WlQwV1NlSjdnc0ZlN0xETGJzdFlUNnFwTjl5Tk9iWlNUaU9TT0pSM1NGVUlTS0Y4YTlsZmErV1VtZTBTZk9xaUoveUJaRS9tSnNqZGpkL1pFUGhXNlBwSHd1OFg3OUtQalUwVkNNNngrYTZHWXNnclFiQmV6UzFlZTJwOXgvSGpPekM2QndQb0czSzdFN0Jldm5tdityL0huTDZuMWJMdUZsZmVJTi9QWm1Kb2lzKzgwZEhMK3NHcWpiaVhwVVI5VjNoV2xkMzBmaEFtUnNUVUhCU0VJNC9GOHQyQ3U5MW1mdEU0UkFaL2NmTGw1T3JPQkZqdHNxOW9XLzcxR2s4SGFhaHpuc1QxbVNROUVyazBIUUhFRE1iY3VUTGl0Z3AxdmQxQnRrZ29lYVJxd1U9In1dfQ==
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="Signature"

DpM7znO9rXtR3cX5ScpNUXq6eCc=
--/rsvLTYenUODoMvvkWqddQzz
Content-Disposition: form-data; name="file"

body test test
--/rsvLTYenUODoMvvkWqddQzz--

gives me an error of

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidArgument</Code><Message>Server Side Encryption with AWS KMS managed key requires HTTP header x-amz-server-side-encryption : aws:kms</Message><ArgumentName>x-amz-server-side-encryption</ArgumentName><ArgumentValue>null</ArgumentValue><RequestId>B0357E14E42BC6D9</RequestId><HostId>ThaDLC7NfemjIwEUUGu3FEvtIEPh+m25SOqHuPUywmFwoUKMF0lj8tsYTSYCktjumUMiCy2cKYg=</HostId></Error>

but I seem to be unable to specify headers effectively. Do you happen to have any suggestions?

karthiksaligrama commented 9 years ago

@citiusaltius can you post the complete code with server side encryption?

karthiksaligrama commented 9 years ago

Assuming your problem is resolved. Please reopen this issue and post your code if otherwise

citiusaltius commented 8 years ago

Hey,

Sorry, so I realized that I didn't reply to this. I'm still having the same issue with being unable to set the AWS:KMS header as such for 2.0.0.5, and this doesn't change in 2.1.0.1, either. Can you please advise? Thank you!

` Client = new AmazonS3Client( GlobalBehaviour.Creds, Defines.Global.StorageConfig);

        PostObjectRequest Req = new PostObjectRequest();
        Req.Bucket = Defines.Global.StorageBucket;
        Req.Key = "users/" + GlobalBehaviour.Creds.GetIdentityId() + "/uploads/";
        if (KeyPrefix.Length > 0)
        {
            Req.Key += KeyPrefix;
        }
        else if (KeyPrefix.Length == 0)
        {
            // do nothing if no key prefix
        }
        else
        {
            throw new UnityException("Cannot have a trailing slash for key prefix");
        }
        Req.Key += System.DateTime.UtcNow.Ticks + "|" + 
            GlobalBehaviour.Creds.DeviceId + "|" + 
            GlobalBehaviour.Creds.GetIdentityId();
        if (KeyPostfix.Length > 0)
        {
            Req.Key += KeyPostfix;
        }

        Utils.DebugBehaviour.WriteLine("Uploaded to:\n" + Req.Key);

        // Assign 5 MB memorystream max
        MemoryStream ms = new MemoryStream(5000000);
        StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
        sw.Write(Body);
        sw.Flush();
        Req.ContentType = ObjectType;

        Req.Metadata.Add("UserId", GlobalBehaviour.Creds.GetIdentityId());
        Req.Metadata.Add("DeviceId", GlobalBehaviour.DeviceId);
        Req.Metadata.Add("S3Key", Req.Key);

        Req.Metadata.Add("TimestampCreation", Defines.Global.UtcNowTimestampFormatAmz());
        Req.Metadata.Add("Type", ObjectType);
        Req.Metadata.Add("Subtype", ObjectSubtype);

        //AdditionalHeaders.Add(Amazon.Util.HeaderKeys.XAmzServerSideEncryptionHeader, "aws:kms");
        //AdditionalHeaders.Add(Amazon.Util.HeaderKeys.XAmzServerSideEncryptionAwsKmsKeyIdHeader, Defines.Global.EncryptionKey);

        // Add additional metadata if dictionary exists and is not empty
        if (Metadata != null && Metadata.Count > 0)
        {
            foreach (string key in Metadata.Keys)
            {
                if(Req.Metadata.ContainsKey(key) == false)
                {
                    Req.Metadata.Add(key, Metadata[key]);
                }
                else
                {
                    Utils.DebugBehaviour.WriteLine(key + " already exists with value \n" +
                        Req.Metadata[key] + "\n and cannot take on new value\n" + 
                        Metadata[key]);
                }

            }
        }

        if (IsEncrypted == true)
        {
            Req.Metadata.Add(Amazon.Util.HeaderKeys.XAmzServerSideEncryptionHeader, "aws:kms");
            Req.Metadata.Add(Amazon.Util.HeaderKeys.XAmzServerSideEncryptionAwsKmsKeyIdHeader, Defines.Global.StorageHipaaEncryptionKey);
        }

        // Create signed policy
        StringBuilder MetadataAddPolicy = new StringBuilder();
        foreach (string Key in Req.Metadata.Keys)
        {
            string KeyLower = Key.ToLower();
            MetadataAddPolicy.Append(",\n{\"");
            if (Key.StartsWith("x-amz") == true)
            {
                MetadataAddPolicy.Append(KeyLower + "\": \"" + Req.Metadata[Key]);
            }
            else
            {
                MetadataAddPolicy.Append("x-amz-meta-" + KeyLower + "\": \"" + Req.Metadata[Key]);
            }
            MetadataAddPolicy.Append("\"}");
        }

        string policyString = "";
        if (Req.Key.LastIndexOf('/') == -1)
        {
            policyString = "{\"expiration\": \""
                + System.DateTime.UtcNow.AddHours(24).ToString("yyyy-MM-ddTHH:mm:ssZ")
                + "\",\"conditions\": "
                + "["
                + "{\"bucket\": \"" + Req.Bucket + "\"},"
                + "[\"starts-with\", \"$key\", \"" + "\"],"
                + "{\"acl\": \"private\"},"
                + "[\"eq\", \"$Content-Type\", " + "\"" + Req.ContentType + "\"" + "]"
                + MetadataAddPolicy.ToString()
                + "]}";
        }
        else
        {
            policyString = "{\"expiration\": \""
                + System.DateTime.UtcNow.AddHours(24).ToString("yyyy-MM-ddTHH:mm:ssZ")
                + "\",\"conditions\": "
                + "["
                + "{\"bucket\": \"" + Req.Bucket + "\"},"
                + "[\"starts-with\", \"$key\", \"" + Req.Key.Substring(0, Req.Key.LastIndexOf('/')) + "/\"],"
                + "{\"acl\": \"private\"},"
                + "[\"eq\", \"$Content-Type\", " + "\"" + Req.ContentType + "\"" + "]"
                + MetadataAddPolicy.ToString()
                + "]}";
        }

        Debug.Log(policyString);

        Req.SignedPolicy = Amazon.S3.Util.S3PostUploadSignedPolicy.GetSignedPolicy(policyString, GlobalBehaviour.Creds);

        ms.Seek(0, SeekOrigin.Begin);
        Req.InputStream = ms;

        Dictionary<string, string> AdditionalHeaders = new Dictionary<string, string>();
        if (IsEncrypted == true)
        {

            AdditionalHeaders.Add(Amazon.Util.HeaderKeys.XAmzServerSideEncryptionHeader, "aws:kms");
             AdditionalHeaders.Add(Amazon.Util.HeaderKeys.XAmzServerSideEncryptionAwsKmsKeyIdHeader, Defines.Global.EncryptionKey);
            Client.PostObjectAsync(Req, Callback, AdditionalHeaders);
        }
        else
        {
            Client.PostObjectAsync(Req, Callback, new Dictionary<string, string>());
        }`

results in a policy of

{"expiration": "2016-01-29T18:43:18Z","conditions": [{"bucket": "projectpage"},["starts-with", "$key", "users/us-east-1:11111111-1111-1111-1111-111111111111/uploads/conversations/1111/"],{"acl": "private"},["eq", "$Content-Type", "NewConversationMessage"], {"x-amz-meta-userid": "us-east-1:11111111-1111-1111-1111-111111111111"}, {"x-amz-meta-deviceid": "fc2146a142def58893fffe1458376203516dd17c"}, {"x-amz-meta-s3key": "users/us-east-1:11111111-1111-1111-1111-111111111111/uploads/conversations/1111/635896033985005242|fc2146a142def58893fffe1458376203516dd17c|us-east-1:11111111-1111-1111-1111-111111111111"}, {"x-amz-meta-timestampcreation": "20160128T184318Z"}, {"x-amz-meta-type": "NewConversationMessage"}, {"x-amz-meta-subtype": "RawTextMessage"}, {"x-amz-meta-conversationid": "1111"}, {"x-amz-meta-test": "newValue"}, {"x-amz-meta-identityidsender": "1111"}, {"x-amz-server-side-encryption": "aws:kms"}, {"x-amz-server-side-encryption-aws-kms-key-id": "11111111-1111-1111-1111-111111111111"}]}

and a request response:

Exception while posting the result objectreceieved error BadRequest UnityEngine.Debug:Log(Object) Assets.Scripts.Utils.DebugBehaviour:WriteLine(String) (at Assets/Scripts/Utils/DebugBehaviour.cs:23) Assets.Scripts.DataStorage.Storage:postObjectAsyncComplete(AmazonServiceResult`2) (at Assets/Scripts/DataStorage/Storage.cs:291) Amazon.S3.cAnonStorey3C:<>m65(AmazonWebServiceRequest, AmazonWebServiceResponse, Exception, AsyncOptions) (at Assets/AWSSDK/src/Services/S3/Custom/_unity/AmazonS3Client.unity.cs:69)