Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Amazon S3 Content Type won't be set when doing a POST request

AccessDenied Invalid according to Policy: Extra input fields: content-type

When I have this

------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="acl"

public-read
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="awsaccesskeyid"

my-access-key-id
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="bucket"

my-bucket
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="Content-Type"

image/png
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="key"

images/anime_girl.png
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="policy"

my-policy
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="signature"

my-signature
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="success_action_status"

201
------WebKitFormBoundaryZIsnhgiAKpAVIsBT
Content-Disposition: form-data; name="file"; filename="anime_girl.png"
Content-Type: image/png

However, if I completely omit the Content Type in the code below, the file gets uploaded but doesn't have the right content type.

        "acl" => "public-read",
        "awsaccesskeyid" => $s3->getAwsAccessKeyId(),
        "bucket" => $s3->getBucket(),
        "Content-Type" => $inputs['type'],
        "key" => "images/" . $inputs['name'],
        "policy" => $s3->getPolicy(true),
        "signature" => $s3->getSignedPolicy(),
        "success_action_status" => "201"

I've also tried adding the content type into the policy and I get this error

AccessDenied Invalid according to Policy: Policy Condition failed: ["starts-with", "Content-Type", "image/png"]

I tried few other recommendations and get this error

AccessDenied Invalid according to Policy: Policy Condition failed: ["eq", "Content-Type", "image/png"]

This is my cors configuration

<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>

So how can I specify the content type in another way or fix this?

EDIT

When I omit any type of content-type (I don't specify any on the backend) I get a request payload like Ben's

------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="acl"

public-read
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="awsaccesskeyid"

xxxx
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="bucket"

xxxxx
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="key"

images/Elf-Anime-Warrior-Wallpaper.jpg
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="policy"

xxxx
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="signature"

0BbpI0AP0wL3S1cBfo9n9tOp+N8=
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="success_action_status"

201
------WebKitFormBoundaryUjxIo0gCBYg7AqJt
Content-Disposition: form-data; name="file"; filename="Elf-Anime-Warrior-Wallpaper.jpg"
Content-Type: image/jpeg


------WebKitFormBoundaryUjxIo0gCBYg7AqJt--

The issue is that in the bucket, the file has the content type as binary/octet-stream and so the image isn't viewed in the browser but instead it's downloaded (which is not what i want). So I'm not sure why Amazon is not setting the content type appropriately when my request payload looks like the above.

like image 220
user1952811 Avatar asked Jul 28 '14 00:07

user1952811


1 Answers

According to Amazon S3 documentation, you can indeed pass the Content-Type as a form field, but you need to allow that in your policy.

So you'll need something like this:

Policy:

{
  "expiration": "2007-12-01T12:00:00.000Z",
  "conditions": [
    {"acl": "public-read" },
    {"bucket": "johnsmith" },
    ["starts-with", "$key", "user/eric/"],
    ["starts-with", "$Content-Type", ""],
    ...
  ]
}

The relevant line here is ["starts-with", "$Content-Type", ""]. The documentation states that a condition of type "match any" should be a "starts-with" with an empty value. If you want to restrict the content type to something, you should use ["starts-with", "$Content-Type", "image/"], for example.

Form:

(example taken from S3 documentation, change values accordingly).

<form action="http://examplebucket.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
  <input type="hidden" name="key" value="user/user1/${filename}">
  <input type="hidden" name="acl" value="public-read">
  <input type="hidden" name="success_action_redirect" value="http://examplebucket.s3.amazonaws.com/successful_upload.html">
  <input type="hidden" name="Content-Type" value="binary/octet-stream">
  <input type="hidden" name="Policy" value='<Base64-encoded policy string>'>
  <input type="hidden" name="x-amz-meta-uuid" value="14365123651274">
  <input type="hidden" name="X-Amz-Signature" value="<signature-value>">
  <input type="hidden" name="X-Amz-Credential" value="AKIAIOSFODNN7EXAMPLE/20130806/us-east-1/s3/aws4_request">
  <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256">
  <input type="hidden" name="X-Amz-Date" value="20130806T000000Z">

  Tags for File: 
  <input type="input" name="x-amz-meta-tag" value=""><br>
  File: 
  <input type="file" name="file"><br>

  <input type="submit" name="submit" value="Upload to Amazon S3">
</form>

As you already did in your first payload, you did set a "Content-Type" field for the form. This, together with the updated policy, should work.

As a side note though, if you know beforehand the type of the file you are uploading, you should set both the policy and the form field to that type. But if you are uploading files of different types, you set the Content-Type condition to "match any" in the policy and you can write a simple javascript intercepting the submit event of the form to dynamically change the Content-Type field (here written with the help of jQuery):

$("#s3-form").on("submit", function() {
  var contentTypeField = $("input[name='Content-Type']", this),
      fileField = $("input[type='file']", this),
      selectedFiles = fileField.get(0).files;

  if (files.length > 0) {
    // we're uploading a single file, so get the type for the first selected file
    contentTypeField.val(selectedFiles[0].type);
  }
});
like image 161
13k Avatar answered Oct 19 '22 09:10

13k