Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Aws::S3::PresignedPost helper for creating browser upload forms. #752

Merged
merged 1 commit into from
Mar 30, 2015

Conversation

trevorrowe
Copy link
Member

Generate a presigend post from an Aws::S3::Bucket or Aws::S3::Object by calling their #presigned_post method. An Aws::S3::PresignedPost object is constructed and returned.

The rest of the comments below document using the presigned post class directly where you must manage your own credentials, region, and bucket name.

Basic Usage

You can apply constraints to the post object as options to {#initialize} or by calling methods such as {#key} and {#content_length_range}.

The following two examples are equivalent.

post = Aws::S3::PresignedPost.new(creds, region, bucket, {
  key: '/uploaded/object/key',
  content_length_range: 0..1024,
  acl: 'public-read',
  metadata: {
    'original-filename' => '${filename}'
  }
})
post.fields
#=> { ... }

post = Aws::S3::PresignedPost.new(creds, region, bucket).
  key('/uploaded/object/key').
  content_length_range(0..1024).
  acl('public-read').
  metadata('original-filename' => '${filename}').
  fields
#=> { ... }

HTML Forms

You can use a PresignedPost object to build an HTML form. It is recommended to use some helper to build the form tag and input tags that properly escapes values.

Form Tag

To upload a file to Amazon S3 using a browser, you need to create a post form. The #url method returns the value you should use as the form action.

<form action="<%= @post.url %>" method="post" enctype="multipart/form-data">
  ...
</form>

The follow attributes must be set on the form:

  • action - This must be the #url.
  • method - This must be post.
  • enctype - This must be multipart/form-data.

Form Fields

The #fields method returns a hash of form fields to render inside the form. Typically these are rendered as hidden input fields.

<% @post.fields.each do |name, value| %>
  <input type="hidden" name="<%= name %>" value="<%= value %>"/>
<% end %>

Lastly, the form must have a file field with the name file.

<input type="file" name="file"/>

Post Policy

When you construct a PresignedPost, you must specify every form field name that will be posted by the browser. If you omit a form field sent by the browser, Amazon S3 will reject the request. You can specify accepted form field values three ways:

  • Specify exactly what the value must be.
  • Specify what value the field starts with.
  • Specify the field may have any value.

Field Equals

You can specify that a form field must be a certain value. Simply pass an option like :content_type to the constructor, or call the associated method.

post = Aws::S3::PresignedPost.new(creds, region, bucket).
post.content_type('text/plain')

If any of the given values are changed by the user in the form, then Amazon S3 will reject the POST request.

Field Starts With

You can specify prefix values for many of the POST form fields. To specify a required prefix, use the :<fieldname>_starts_with option or call the associated #<field_name>_starts_with method.

post = Aws::S3::PresignedPost.new(creds, region, bucket, {
  key_starts_with: '/images/',
  content_type_starts_with: 'image/',
  # ...
})

When using starts with, the form must contain a field where the user can specify the value. The PresignedPost will not add a value for these fields.

Any Field Value

To white-list a form field to send any value, you can name that field with :allow_any or #allow_any.

post = Aws::S3::PresignedPost.new(creds, region, bucket, {
  key: 'object-key',
  allow_any: ['Filename'],
  # ...
})

Metadata

You can add rules for metadata fields using :metadata, #metadata, :metadata_starts_with and #metadata_starts_with. Unlike other form fields, you pass a hash value to these options/methods:

post = Aws::S3::PresignedPost.new(creds, region, bucket).
  key('/fixed/key').
  metadata(foo: 'bar')

post.fields['x-amz-meta-foo']
#=> 'bar'

The ${filename} Variable

The string ${filename} is automatically replaced with the name of the file provided by the user and is recognized by all form fields. It is not supported with starts_with conditions.

If the browser or client provides a full or partial path to the file, only the text following the last slash (/) or backslash () will be used (e.g., "C:\Program Files\directory1\file.txt" will be interpreted as "file.txt"). If no file or file name is provided, the variable is replaced with an empty string.

In the following example, we use ${filename} to store the original filename in the x-amz-meta- hash with the uploaded object.

post = Aws::S3::PresignedPost.new(creds, region, bucket, {
  key: '/fixed/key',
  metadata: {
    'original-filename': '${filename}'
  }
})

See #720.

`Aws::S3::PresignedPost` is a utility class that makes it possible
to upload a file from a web browser directly to Amazon S3.

See #720.
@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 4a3fd4b on presigned-post into * on master*.

@sariyamelody
Copy link

Haven't had a chance to look through the code yet, but presuming that it works as it should (and I believe it will), thanks a ton @trevorrowe!

@trevorrowe trevorrowe merged commit 4a3fd4b into master Mar 30, 2015
@arunthampi
Copy link

sweet, this is great! will you be cutting a new gem release soon which includes this feature @trevorrowe?

@trevorrowe
Copy link
Member Author

@arunthampi The plan is to tag and release v2.0.34 on Thursday. I try to do a weekly update each Thursday unless there is a high priority security fix, bug fix, or API update.

@arunthampi
Copy link

cool, thanks 👍

@arunthampi
Copy link

I tried using using Aws::S3::PresignedPost in my app (ref: 11ab665) with the jquery-file-upload plugin, I get a proper XML response but the file doesn't exist in the bucket. https://gist.github.com/arunthampi/f4e47fa5a1d2ed1b7abc

Followed the tutorial here: https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails, anything I'm missing?

@trevorrowe
Copy link
Member Author

Looking at the xml response, the object should be found at the given key from the XML: /uploads/9562e8df-0e1a-4043-ad53-930f96828dea/master.zip. Are you unable to HEAD or get this object after a successful POST?

@arunthampi
Copy link

Yes that's correct a HEAD or GET on this object returns a 403. The new signature format works for all regions yes?

@arunthampi
Copy link

Confirmed that the exact same JS code uploads the file for the v1 version of PresignedPost, but with v2, it returns a valid response but the file itself doesn't get uploaded.

@trevorrowe
Copy link
Member Author

I'm a bit confused. In your linked gist: https://gist.github.com/arunthampi/f4e47fa5a1d2ed1b7abc you show a 201 response. This indicates that your object was successfully uploaded to Amazon S3. If this is correct, then my suspicion is that you are extracting the key or location incorrectly from the response.

When you say that it works with the v1 PresignedPost, can you share a gist of how you are using that? Also, then you say it has a valid response but the file is not uploaded, can you share a gist of how you are checking for the object post-upload?

@arunthampi
Copy link

@trevorrowe
Copy link
Member Author

I think I see the problem. In your v1 code you set the key as: "uploads/#{SecureRandom.uuid}/${filename}". In your v2 example, you use key("/uploads/#{SecureRandom.uuid}/${filename}").

Notice in v2 there is an extra forward slash at the beginning of the key. Depending on the structure of your s3URL on line 37 you may have the incorrect number of slashes. This will really depend on if the S3 URL contains the bucket in the hostname or as part of the request URI.

@arunthampi
Copy link

doh! that was it, thanks much for your help!

@trevorrowe trevorrowe deleted the presigned-post branch June 11, 2015 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants