class Aws::S3::PresignedPost

@note Normally you do not need to construct a {PresignedPost} yourself.

See {Bucket#presigned_post} and {Object#presigned_post}.

## Basic Usage

To generate a presigned post, you need AWS credentials, the region your bucket is in, and the name of your bucket. 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.

“`ruby 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.

“`erb <form action=“<%= @post.url %>” method=“post” enctype=“multipart/form-data”>

...

</form> “`

The follow attributes must be set on the form:

### Form Fields

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

“`erb <% @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`.

“`erb <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:

### 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.

“`ruby 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.

“`ruby 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}.

“`ruby 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:

“`ruby post = Aws::S3::PresignedPost.new(creds, region, bucket).

key('/fixed/key').
metadata(foo: 'bar')

post.fields #=> '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 Filesdirectory1file.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.

“`ruby post = Aws::S3::PresignedPost.new(creds, region, bucket, {

key: '/fixed/key',
metadata: {
  'original-filename': '${filename}'
}

}) “`

Attributes

url[R]

@return [String] The URL to post a file upload to. This should be

the form action.

Public Class Methods

define_field(field, *args) click to toggle source

@api private

# File lib/aws-sdk-s3/presigned_post.rb, line 282
def self.define_field(field, *args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  field_name = args.last || field.to_s

  define_method("#{field}") do |value|
    with(field_name, value)
  end

  if options[:starts_with]
    define_method("#{field}_starts_with") do |value|
      starts_with(field_name, value)
    end
  end
end
new(credentials, bucket_region, bucket_name, options = {}) click to toggle source

@param [Credentials] credentials Security credentials for signing

the post policy.

@param [String] bucket_region Region of the target bucket. @param [String] bucket_name Name of the target bucket. @option options [Time] :signature_expiration Specify when the signature on

the post will expire. Defaults to one hour from creation of the
presigned post. May not exceed one week from creation time.

@option options [String] :key See {PresignedPost#key}. @option options [String] :key_starts_with

See {PresignedPost#key_starts_with}.

@option options [String] :acl See {PresignedPost#acl}. @option options [String] :acl_starts_with

See {PresignedPost#acl_starts_with}.

@option options [String] :cache_control

See {PresignedPost#cache_control}.

@option options [String] :cache_control_starts_with

See {PresignedPost#cache_control_starts_with}.

@option options [String] :content_type See {PresignedPost#content_type}. @option options [String] :content_type_starts_with

See {PresignedPost#content_type_starts_with}.

@option options [String] :content_disposition

See {PresignedPost#content_disposition}.

@option options [String] :content_disposition_starts_with

See {PresignedPost#content_disposition_starts_with}.

@option options [String] :content_encoding

See {PresignedPost#content_encoding}.

@option options [String] :content_encoding_starts_with

See {PresignedPost#content_encoding_starts_with}.

@option options [String] :expires See {PresignedPost#expires}. @option options [String] :expires_starts_with

See {PresignedPost#expires_starts_with}.

@option options [Range<Integer>] :content_length_range

See {PresignedPost#content_length_range}.

@option options [String] :success_action_redirect

See {PresignedPost#success_action_redirect}.

@option options [String] :success_action_redirect_starts_with

See {PresignedPost#success_action_redirect_starts_with}.

@option options [String] :success_action_status

See {PresignedPost#success_action_status}.

@option options [String] :storage_class

See {PresignedPost#storage_class}.

@option options [String] :website_redirect_location

See {PresignedPost#website_redirect_location}.

@option options [Hash<String,String>] :metadata

See {PresignedPost#metadata}.

@option options [Hash<String,String>] :metadata_starts_with

See {PresignedPost#metadata_starts_with}.

@option options [String] :server_side_encryption

See {PresignedPost#server_side_encryption}.

@option options [String] :server_side_encryption_aws_kms_key_id

See {PresignedPost#server_side_encryption_aws_kms_key_id}.

@option options [String] :server_side_encryption_customer_algorithm

See {PresignedPost#server_side_encryption_customer_algorithm}.

@option options [String] :server_side_encryption_customer_key

See {PresignedPost#server_side_encryption_customer_key}.
# File lib/aws-sdk-s3/presigned_post.rb, line 235
def initialize(credentials, bucket_region, bucket_name, options = {})
  @credentials = credentials.credentials
  @bucket_region = bucket_region
  @bucket_name = bucket_name
  @accelerate = !!options.delete(:use_accelerate_endpoint)
  options.delete(:url) if @accelerate # resource methods pass url
  @url = options.delete(:url) || bucket_url
  @fields = {}
  @key_set = false
  @signature_expiration = Time.now + 3600
  @conditions = [{ 'bucket' => @bucket_name }]
  options.each do |option_name, option_value|
    case option_name
    when :allow_any then allow_any(option_value)
    when :signature_expiration then @signature_expiration = option_value
    else send("#{option_name}", option_value)
    end
  end
end

Public Instance Methods

allow_any(*field_names) click to toggle source

A list of form fields to white-list with any value. @param [Sting, Array<String>] field_names @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 273
def allow_any(*field_names)
  field_names.flatten.each do |field_name|
    @key_set = true if field_name.to_s == 'key'
    starts_with(field_name, '')
  end
  self
end
content_length_range(byte_range) click to toggle source

The minimum and maximum allowable size for the uploaded content. @param [Range<Integer>] byte_range @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 416
def content_length_range(byte_range)
  min = byte_range.begin
  max = byte_range.end
  max -= 1 if byte_range.exclude_end?
  @conditions << ['content-length-range', min, max]
  self
end
expires(time) click to toggle source

The date and time at which the object is no longer cacheable. @note This does not affect the expiration of the presigned post

signature.

@param [Time] time @see www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 402
def expires(time)
  with('Expires', time.httpdate)
end
expires_starts_with(prefix) click to toggle source

@param [String] prefix @see expires @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 409
def expires_starts_with(prefix)
  starts_with('Expires', prefix)
end
fields() click to toggle source

@return [Hash] A hash of fields to render in an HTML form

as hidden input fields.
# File lib/aws-sdk-s3/presigned_post.rb, line 261
def fields
  check_required_values!
  datetime = Time.now.utc.strftime('%Y%m%dT%H%M%SZ')
  fields = @fields.dup
  fields.update('policy' => policy(datetime))
  fields.update(signature_fields(datetime))
  fields.update('x-amz-signature' => signature(datetime, fields['policy']))
end
key(key) click to toggle source

The key to use for the uploaded object. You can use `${filename}` as a variable in the key. This will be replaced with the name of the file as provided by the user.

For example, if the key is given as `/user/betty/${filename}` and the file uploaded is named `lolcatz.jpg`, the resultant key will be `/user/betty/lolcatz.jpg`.

@param [String] key @see docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html) @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 310
def key(key)
  @key_set = true
  with('key', key)
end
key_starts_with(prefix) click to toggle source

Specify a prefix the uploaded @param [String] prefix @see key @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 319
def key_starts_with(prefix)
  @key_set = true
  starts_with('key', prefix)
end
metadata(hash) click to toggle source

Metadata hash to store with the uploaded object. Hash keys will be prefixed with “x-amz-meta-”. @param [Hash<String,String>] hash @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 495
def metadata(hash)
  hash.each do |key, value|
    with("x-amz-meta-#{key}", value)
  end
  self
end
metadata_starts_with(hash) click to toggle source

Specify allowable prefix for each key in the metadata hash. @param [Hash<String,String>] hash @see metadata @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 506
def metadata_starts_with(hash)
  hash.each do |key, value|
    starts_with("x-amz-meta-#{key}", value)
  end
  self
end
server_side_encryption_customer_key(value) click to toggle source

Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data. This value is used to store the object and then it is discarded; Amazon does not store the encryption key.

You must also call {#server_side_encryption_customer_algorithm}.

@param [String] value @see server_side_encryption_customer_algorithm @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 564
def server_side_encryption_customer_key(value)
  field_name = 'x-amz-server-side-encryption-customer-key'
  with(field_name, base64(value))
  with(field_name + '-MD5', base64(OpenSSL::Digest::MD5.digest(value)))
end
server_side_encryption_customer_key_starts_with(prefix) click to toggle source

@param [String] prefix @see server_side_encryption_customer_key @return [self]

# File lib/aws-sdk-s3/presigned_post.rb, line 573
def server_side_encryption_customer_key_starts_with(prefix)
  field_name = 'x-amz-server-side-encryption-customer-key'
  starts_with(field_name, prefix)
end

Private Instance Methods

base64(str) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 681
def base64(str)
  Base64.strict_encode64(str)
end
bucket_url() click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 612
def bucket_url
  url = Aws::Partitions::EndpointProvider.resolve(@bucket_region, 's3')
  url = URI.parse(url)
  if Plugins::BucketDns.dns_compatible?(@bucket_name, _ssl = true)
    if @accelerate
      url.host = "#{@bucket_name}.s3-accelerate.amazonaws.com"
    else
      url.host = "#{@bucket_name}.#{url.host}"
    end
  else
    url.path = "/#{@bucket_name}"
  end
  if @bucket_region == 'us-east-1'
    # keep legacy behavior by default
    url.host = Plugins::IADRegionalEndpoint.legacy_host(url.host)
  end
  url.to_s
end
check_required_values!() click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 604
def check_required_values!
  unless @key_set
    msg = 'key required; you must provide a key via :key, '\
          ":key_starts_with, or :allow_any => ['key']"
    raise msg
  end
end
credential_scope(datetime) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 671
def credential_scope(datetime)
  parts = []
  parts << @credentials.access_key_id
  parts << datetime[0,8]
  parts << @bucket_region
  parts << 's3'
  parts << 'aws4_request'
  parts.join('/')
end
hexhmac(key, value) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 667
def hexhmac(key, value)
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
end
hmac(key, value) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 663
def hmac(key, value)
  OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
end
policy(datetime) click to toggle source

@return [Hash]

# File lib/aws-sdk-s3/presigned_post.rb, line 632
def policy(datetime)
  check_required_values!
  policy = {}
  policy['expiration'] = @signature_expiration.utc.iso8601
  policy['conditions'] = @conditions.dup
  signature_fields(datetime).each do |name, value|
    policy['conditions'] << { name => value }
  end
  base64(Json.dump(policy))
end
signature(datetime, string_to_sign) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 654
def signature(datetime, string_to_sign)
  k_secret = @credentials.secret_access_key
  k_date = hmac('AWS4' + k_secret, datetime[0,8])
  k_region = hmac(k_date, @bucket_region)
  k_service = hmac(k_region, 's3')
  k_credentials = hmac(k_service, 'aws4_request')
  hexhmac(k_credentials, string_to_sign)
end
signature_fields(datetime) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 643
def signature_fields(datetime)
  fields = {}
  fields['x-amz-credential'] = credential_scope(datetime)
  fields['x-amz-algorithm'] = 'AWS4-HMAC-SHA256'
  fields['x-amz-date'] = datetime
  if session_token = @credentials.session_token
    fields['x-amz-security-token'] = session_token
  end
  fields
end
starts_with(field_name, value, &block) click to toggle source
# File lib/aws-sdk-s3/presigned_post.rb, line 599
def starts_with(field_name, value, &block)
  @conditions << ['starts-with', "$#{field_name}", value.to_s]
  self
end
with(field_name, value) click to toggle source

@!endgroup

# File lib/aws-sdk-s3/presigned_post.rb, line 582
def with(field_name, value)
  fvar = '${filename}'
  if index = value.rindex(fvar)
    if index + fvar.size == value.size
      @fields[field_name] = value
      starts_with(field_name, value[0,index])
    else
      msg = "${filename} only supported at the end of #{field_name}"
      raise ArgumentError, msg
    end
  else
    @fields[field_name] = value.to_s
    @conditions << { field_name => value.to_s }
  end
  self
end