aws-sdk-s3 1.75.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aws-sdk-s3.rb +73 -0
  3. data/lib/aws-sdk-s3/bucket.rb +861 -0
  4. data/lib/aws-sdk-s3/bucket_acl.rb +277 -0
  5. data/lib/aws-sdk-s3/bucket_cors.rb +262 -0
  6. data/lib/aws-sdk-s3/bucket_lifecycle.rb +264 -0
  7. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +283 -0
  8. data/lib/aws-sdk-s3/bucket_logging.rb +251 -0
  9. data/lib/aws-sdk-s3/bucket_notification.rb +293 -0
  10. data/lib/aws-sdk-s3/bucket_policy.rb +242 -0
  11. data/lib/aws-sdk-s3/bucket_region_cache.rb +81 -0
  12. data/lib/aws-sdk-s3/bucket_request_payment.rb +236 -0
  13. data/lib/aws-sdk-s3/bucket_tagging.rb +251 -0
  14. data/lib/aws-sdk-s3/bucket_versioning.rb +312 -0
  15. data/lib/aws-sdk-s3/bucket_website.rb +292 -0
  16. data/lib/aws-sdk-s3/client.rb +11818 -0
  17. data/lib/aws-sdk-s3/client_api.rb +3014 -0
  18. data/lib/aws-sdk-s3/customizations.rb +34 -0
  19. data/lib/aws-sdk-s3/customizations/bucket.rb +162 -0
  20. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +44 -0
  21. data/lib/aws-sdk-s3/customizations/object.rb +389 -0
  22. data/lib/aws-sdk-s3/customizations/object_summary.rb +85 -0
  23. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +13 -0
  24. data/lib/aws-sdk-s3/encryption.rb +21 -0
  25. data/lib/aws-sdk-s3/encryption/client.rb +375 -0
  26. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +190 -0
  27. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +65 -0
  28. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +40 -0
  29. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +61 -0
  30. data/lib/aws-sdk-s3/encryption/errors.rb +15 -0
  31. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +58 -0
  32. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +36 -0
  33. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +71 -0
  34. data/lib/aws-sdk-s3/encryption/key_provider.rb +31 -0
  35. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +75 -0
  36. data/lib/aws-sdk-s3/encryption/materials.rb +60 -0
  37. data/lib/aws-sdk-s3/encryption/utils.rb +81 -0
  38. data/lib/aws-sdk-s3/encryptionV2/client.rb +388 -0
  39. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +198 -0
  40. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +103 -0
  41. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +38 -0
  42. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +66 -0
  43. data/lib/aws-sdk-s3/encryptionV2/errors.rb +13 -0
  44. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +56 -0
  45. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +35 -0
  46. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +71 -0
  47. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +29 -0
  48. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +99 -0
  49. data/lib/aws-sdk-s3/encryptionV2/materials.rb +58 -0
  50. data/lib/aws-sdk-s3/encryptionV2/utils.rb +116 -0
  51. data/lib/aws-sdk-s3/encryption_v2.rb +20 -0
  52. data/lib/aws-sdk-s3/errors.rb +115 -0
  53. data/lib/aws-sdk-s3/event_streams.rb +69 -0
  54. data/lib/aws-sdk-s3/file_downloader.rb +142 -0
  55. data/lib/aws-sdk-s3/file_part.rb +78 -0
  56. data/lib/aws-sdk-s3/file_uploader.rb +70 -0
  57. data/lib/aws-sdk-s3/legacy_signer.rb +189 -0
  58. data/lib/aws-sdk-s3/multipart_file_uploader.rb +227 -0
  59. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +173 -0
  60. data/lib/aws-sdk-s3/multipart_upload.rb +401 -0
  61. data/lib/aws-sdk-s3/multipart_upload_error.rb +18 -0
  62. data/lib/aws-sdk-s3/multipart_upload_part.rb +423 -0
  63. data/lib/aws-sdk-s3/object.rb +1422 -0
  64. data/lib/aws-sdk-s3/object_acl.rb +333 -0
  65. data/lib/aws-sdk-s3/object_copier.rb +101 -0
  66. data/lib/aws-sdk-s3/object_multipart_copier.rb +182 -0
  67. data/lib/aws-sdk-s3/object_summary.rb +1181 -0
  68. data/lib/aws-sdk-s3/object_version.rb +550 -0
  69. data/lib/aws-sdk-s3/plugins/accelerate.rb +87 -0
  70. data/lib/aws-sdk-s3/plugins/bucket_arn.rb +212 -0
  71. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +91 -0
  72. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +45 -0
  73. data/lib/aws-sdk-s3/plugins/dualstack.rb +74 -0
  74. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +28 -0
  75. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +25 -0
  76. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -0
  77. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +62 -0
  78. data/lib/aws-sdk-s3/plugins/location_constraint.rb +35 -0
  79. data/lib/aws-sdk-s3/plugins/md5s.rb +84 -0
  80. data/lib/aws-sdk-s3/plugins/redirects.rb +45 -0
  81. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +30 -0
  82. data/lib/aws-sdk-s3/plugins/s3_signer.rb +222 -0
  83. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +70 -0
  84. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +118 -0
  85. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +97 -0
  86. data/lib/aws-sdk-s3/presigned_post.rb +686 -0
  87. data/lib/aws-sdk-s3/presigner.rb +253 -0
  88. data/lib/aws-sdk-s3/resource.rb +117 -0
  89. data/lib/aws-sdk-s3/types.rb +13154 -0
  90. data/lib/aws-sdk-s3/waiters.rb +243 -0
  91. metadata +184 -0
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'openssl'
5
+
6
+ module Aws
7
+ module S3
8
+ module Plugins
9
+ class SseCpk < Seahorse::Client::Plugin
10
+
11
+ option(:require_https_for_sse_cpk,
12
+ default: true,
13
+ doc_type: 'Boolean',
14
+ docstring: <<-DOCS)
15
+ When `true`, the endpoint **must** be HTTPS for all operations
16
+ where server-side-encryption is used with customer-provided keys.
17
+ This should only be disabled for local testing.
18
+ DOCS
19
+
20
+ class Handler < Seahorse::Client::Handler
21
+
22
+ def call(context)
23
+ compute_key_md5(context) if context.params.is_a?(Hash)
24
+ @handler.call(context)
25
+ end
26
+
27
+ private
28
+
29
+ def compute_key_md5(context)
30
+ params = context.params
31
+ if key = params[:sse_customer_key]
32
+ require_https(context)
33
+ params[:sse_customer_key] = base64(key)
34
+ params[:sse_customer_key_md5] = base64(md5(key))
35
+ end
36
+ if key = params[:copy_source_sse_customer_key]
37
+ require_https(context)
38
+ params[:copy_source_sse_customer_key] = base64(key)
39
+ params[:copy_source_sse_customer_key_md5] = base64(md5(key))
40
+ end
41
+ end
42
+
43
+ def require_https(context)
44
+ unless URI::HTTPS === context.config.endpoint
45
+ msg = <<-MSG.strip.gsub("\n", ' ')
46
+ Attempting to send customer-provided-keys for S3
47
+ server-side-encryption over HTTP; Please configure a HTTPS
48
+ endpoint. If you are attempting to use a test endpoint,
49
+ you can disable this check via `:require_https_for_sse_cpk`
50
+ MSG
51
+ raise ArgumentError, msg
52
+ end
53
+ end
54
+
55
+ def md5(str)
56
+ OpenSSL::Digest::MD5.digest(str)
57
+ end
58
+
59
+ def base64(str)
60
+ Base64.encode64(str).strip
61
+ end
62
+
63
+ end
64
+
65
+ handler(Handler, step: :initialize)
66
+
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Aws
6
+ module S3
7
+ module Plugins
8
+
9
+ # A wrapper around BlockIO that adds no-ops for truncate and rewind
10
+ # @api private
11
+ class RetryableBlockIO
12
+ extend Forwardable
13
+ def_delegators :@block_io, :write, :read, :size
14
+
15
+ def initialize(block_io)
16
+ @block_io = block_io
17
+ end
18
+
19
+ def truncate(_integer); end
20
+
21
+ def rewind; end
22
+ end
23
+
24
+ # A wrapper around ManagedFile that adds no-ops for truncate and rewind
25
+ # @api private
26
+ class RetryableManagedFile
27
+ extend Forwardable
28
+ def_delegators :@file, :write, :read, :size, :open?, :close
29
+
30
+ def initialize(managed_file)
31
+ @file = managed_file
32
+ end
33
+
34
+ def truncate(_integer); end
35
+
36
+ def rewind; end
37
+ end
38
+
39
+ # This handler works with the ResponseTarget plugin to provide smart
40
+ # retries of S3 streaming operations that support the range parameter
41
+ # (currently only: get_object). When a 200 OK with a TruncatedBodyError
42
+ # is received this handler will add a range header that excludes the
43
+ # data that has already been processed (written to file or sent to
44
+ # the target Proc).
45
+ # It is important to not write data to the custom target in the case of
46
+ # a non-success response. We do not want to write an XML error
47
+ # message to someone's file or pass it to a user's Proc.
48
+ # @api private
49
+ class StreamingRetry < Seahorse::Client::Plugin
50
+
51
+ class Handler < Seahorse::Client::Handler
52
+
53
+ def call(context)
54
+ target = context.params[:response_target] || context[:response_target]
55
+
56
+ # retry is only supported when range is NOT set on the initial request
57
+ if supported_target?(target) && !context.params[:range]
58
+ add_event_listeners(context, target)
59
+ end
60
+ @handler.call(context)
61
+ end
62
+
63
+ private
64
+
65
+ def add_event_listeners(context, target)
66
+ context.http_response.on_headers(200..299) do
67
+ case context.http_response.body
68
+ when Seahorse::Client::BlockIO then
69
+ context.http_response.body = RetryableBlockIO.new(context.http_response.body)
70
+ when Seahorse::Client::ManagedFile then
71
+ context.http_response.body = RetryableManagedFile.new(context.http_response.body)
72
+ end
73
+ end
74
+
75
+ context.http_response.on_headers(400..599) do
76
+ context.http_response.body = StringIO.new # something to write the error to
77
+ end
78
+
79
+ context.http_response.on_success(200..299) do
80
+ body = context.http_response.body
81
+ if body.is_a?(RetryableManagedFile) && body.open?
82
+ body.close
83
+ end
84
+ end
85
+
86
+ context.http_response.on_error do |error|
87
+ if retryable_body?(context) && truncated_body?(error)
88
+ context.http_request.headers[:range] = "bytes=#{context.http_response.body.size}-"
89
+ end
90
+ end
91
+ end
92
+
93
+ def truncated_body?(error)
94
+ error.is_a?(Seahorse::Client::NetworkingError) &&
95
+ error.original_error.is_a?(
96
+ Seahorse::Client::NetHttp::Handler::TruncatedBodyError
97
+ )
98
+ end
99
+
100
+ def retryable_body?(context)
101
+ context.http_response.body.is_a?(RetryableBlockIO) ||
102
+ context.http_response.body.is_a?(RetryableManagedFile)
103
+ end
104
+
105
+ def supported_target?(target)
106
+ case target
107
+ when Proc, String, Pathname then true
108
+ else false
109
+ end
110
+ end
111
+ end
112
+
113
+ handler(Handler, step: :sign, operations: [:get_object], priority: 10)
114
+
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'cgi'
5
+
6
+ module Aws
7
+ module S3
8
+ module Plugins
9
+
10
+ # This plugin auto-populates the `:encoding_type` request parameter
11
+ # to all calls made to Amazon S3 that accept it.
12
+ #
13
+ # This enables Amazon S3 to return object keys that might contain
14
+ # invalid XML characters as URL encoded strings. This plugin also
15
+ # automatically decodes these keys so that the key management is
16
+ # transparent to the user.
17
+ #
18
+ # If you specify the `:encoding_type` parameter, then this plugin
19
+ # will be disabled, and you will need to decode the keys yourself.
20
+ #
21
+ # The following operations are managed:
22
+ #
23
+ # * {S3::Client#list_objects}
24
+ # * {S3::Client#list_multipart_uploads}
25
+ # * {S3::Client#list_object_versions}
26
+ #
27
+ class UrlEncodedKeys < Seahorse::Client::Plugin
28
+
29
+ class Handler < Seahorse::Client::Handler
30
+
31
+ def call(context)
32
+ if context.params.key?(:encoding_type)
33
+ @handler.call(context) # user managed
34
+ else
35
+ manage_keys(context)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def manage_keys(context)
42
+ context.params[:encoding_type] = 'url'
43
+ @handler.call(context).on_success do |resp|
44
+ send("decode_#{resp.context.operation_name}_keys", resp.data)
45
+ end
46
+ end
47
+
48
+ def decode_list_objects_keys(data)
49
+ decode(:marker, data)
50
+ decode(:next_marker, data)
51
+ decode(:prefix, data)
52
+ decode(:delimiter, data)
53
+ data.contents.each { |o| decode(:key, o) } if data.contents
54
+ data.common_prefixes.each { |o| decode(:prefix, o) } if data.common_prefixes
55
+ end
56
+
57
+ def decode_list_object_versions_keys(data)
58
+ decode(:key_marker, data)
59
+ decode(:next_key_marker, data)
60
+ decode(:prefix, data)
61
+ decode(:delimiter, data)
62
+ data.versions.each { |o| decode(:key, o) } if data.versions
63
+ data.delete_markers.each { |o| decode(:key, o) } if data.delete_markers
64
+ data.common_prefixes.each { |o| decode(:prefix, o) } if data.common_prefixes
65
+ end
66
+
67
+ def decode_list_multipart_uploads_keys(data)
68
+ decode(:key_marker, data)
69
+ decode(:next_key_marker, data)
70
+ decode(:prefix, data)
71
+ decode(:delimiter, data)
72
+ data.uploads.each { |o| decode(:key, o) } if data.uploads
73
+ data.common_prefixes.each { |o| decode(:prefix, o) } if data.common_prefixes
74
+ end
75
+
76
+ def decode(member, struct)
77
+ if struct[member]
78
+ struct[member] = CGI.unescape(struct[member])
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ handler(Handler,
85
+ step: :validate,
86
+ priority: 0,
87
+ operations: [
88
+ :list_objects,
89
+ :list_object_versions,
90
+ :list_multipart_uploads,
91
+ ]
92
+ )
93
+
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,686 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module Aws
7
+ module S3
8
+
9
+ # @note Normally you do not need to construct a {PresignedPost} yourself.
10
+ # See {Bucket#presigned_post} and {Object#presigned_post}.
11
+ #
12
+ # ## Basic Usage
13
+ #
14
+ # To generate a presigned post, you need AWS credentials, the region
15
+ # your bucket is in, and the name of your bucket. You can apply constraints
16
+ # to the post object as options to {#initialize} or by calling
17
+ # methods such as {#key} and {#content_length_range}.
18
+ #
19
+ # The following two examples are equivalent.
20
+ #
21
+ # ```ruby
22
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
23
+ # key: '/uploaded/object/key',
24
+ # content_length_range: 0..1024,
25
+ # acl: 'public-read',
26
+ # metadata: {
27
+ # 'original-filename' => '${filename}'
28
+ # }
29
+ # })
30
+ # post.fields
31
+ # #=> { ... }
32
+ #
33
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket).
34
+ # key('/uploaded/object/key').
35
+ # content_length_range(0..1024).
36
+ # acl('public-read').
37
+ # metadata('original-filename' => '${filename}').
38
+ # fields
39
+ # #=> { ... }
40
+ # ```
41
+ #
42
+ # ## HTML Forms
43
+ #
44
+ # You can use a {PresignedPost} object to build an HTML form. It is
45
+ # recommended to use some helper to build the form tag and input
46
+ # tags that properly escapes values.
47
+ #
48
+ # ### Form Tag
49
+ #
50
+ # To upload a file to Amazon S3 using a browser, you need to create
51
+ # a post form. The {#url} method returns the value you should use
52
+ # as the form action.
53
+ #
54
+ # ```erb
55
+ # <form action="<%= @post.url %>" method="post" enctype="multipart/form-data">
56
+ # ...
57
+ # </form>
58
+ # ```
59
+ #
60
+ # The follow attributes must be set on the form:
61
+ #
62
+ # * `action` - This must be the {#url}.
63
+ # * `method` - This must be `post`.
64
+ # * `enctype` - This must be `multipart/form-data`.
65
+ #
66
+ # ### Form Fields
67
+ #
68
+ # The {#fields} method returns a hash of form fields to render inside
69
+ # the form. Typically these are rendered as hidden input fields.
70
+ #
71
+ # ```erb
72
+ # <% @post.fields.each do |name, value| %>
73
+ # <input type="hidden" name="<%= name %>" value="<%= value %>"/>
74
+ # <% end %>
75
+ # ```
76
+ #
77
+ # Lastly, the form must have a file field with the name `file`.
78
+ #
79
+ # ```erb
80
+ # <input type="file" name="file"/>
81
+ # ```
82
+ #
83
+ # ## Post Policy
84
+ #
85
+ # When you construct a {PresignedPost}, you must specify every form
86
+ # field name that will be posted by the browser. If you omit a form
87
+ # field sent by the browser, Amazon S3 will reject the request.
88
+ # You can specify accepted form field values three ways:
89
+ #
90
+ # * Specify exactly what the value must be.
91
+ # * Specify what value the field starts with.
92
+ # * Specify the field may have any value.
93
+ #
94
+ # ### Field Equals
95
+ #
96
+ # You can specify that a form field must be a certain value.
97
+ # Simply pass an option like `:content_type` to the constructor,
98
+ # or call the associated method.
99
+ #
100
+ # ```ruby
101
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket).
102
+ # post.content_type('text/plain')
103
+ # ```
104
+ #
105
+ # If any of the given values are changed by the user in the form, then
106
+ # Amazon S3 will reject the POST request.
107
+ #
108
+ # ### Field Starts With
109
+ #
110
+ # You can specify prefix values for many of the POST form fields.
111
+ # To specify a required prefix, use the `:<fieldname>_starts_with`
112
+ # option or call the associated `#<field_name>_starts_with` method.
113
+ #
114
+ # ```ruby
115
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
116
+ # key_starts_with: '/images/',
117
+ # content_type_starts_with: 'image/',
118
+ # # ...
119
+ # })
120
+ # ```
121
+ #
122
+ # When using starts with, the form must contain a field where the
123
+ # user can specify the value. The {PresignedPost} will not add
124
+ # a value for these fields.
125
+ #
126
+ # ### Any Field Value
127
+ #
128
+ # To white-list a form field to send any value, you can name that
129
+ # field with `:allow_any` or {#allow_any}.
130
+ #
131
+ # ```ruby
132
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
133
+ # key: 'object-key',
134
+ # allow_any: ['Filename'],
135
+ # # ...
136
+ # })
137
+ # ```
138
+ #
139
+ # ### Metadata
140
+ #
141
+ # You can add rules for metadata fields using `:metadata`, {#metadata},
142
+ # `:metadata_starts_with` and {#metadata_starts_with}. Unlike other
143
+ # form fields, you pass a hash value to these options/methods:
144
+ #
145
+ # ```ruby
146
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket).
147
+ # key('/fixed/key').
148
+ # metadata(foo: 'bar')
149
+ #
150
+ # post.fields['x-amz-meta-foo']
151
+ # #=> 'bar'
152
+ # ```
153
+ #
154
+ # ### The `${filename}` Variable
155
+ #
156
+ # The string `${filename}` is automatically replaced with the name of the
157
+ # file provided by the user and is recognized by all form fields. It is
158
+ # not supported with `starts_with` conditions.
159
+ #
160
+ # If the browser or client provides a full or partial path to the file,
161
+ # only the text following the last slash (/) or backslash (\) will be used
162
+ # (e.g., "C:\Program Files\directory1\file.txt" will be interpreted
163
+ # as "file.txt"). If no file or file name is provided, the variable is
164
+ # replaced with an empty string.
165
+ #
166
+ # In the following example, we use `${filename}` to store the original
167
+ # filename in the `x-amz-meta-` hash with the uploaded object.
168
+ #
169
+ # ```ruby
170
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
171
+ # key: '/fixed/key',
172
+ # metadata: {
173
+ # 'original-filename': '${filename}'
174
+ # }
175
+ # })
176
+ # ```
177
+ #
178
+ class PresignedPost
179
+
180
+ # @param [Credentials] credentials Security credentials for signing
181
+ # the post policy.
182
+ # @param [String] bucket_region Region of the target bucket.
183
+ # @param [String] bucket_name Name of the target bucket.
184
+ # @option options [Time] :signature_expiration Specify when the signature on
185
+ # the post will expire. Defaults to one hour from creation of the
186
+ # presigned post. May not exceed one week from creation time.
187
+ # @option options [String] :key See {PresignedPost#key}.
188
+ # @option options [String] :key_starts_with
189
+ # See {PresignedPost#key_starts_with}.
190
+ # @option options [String] :acl See {PresignedPost#acl}.
191
+ # @option options [String] :acl_starts_with
192
+ # See {PresignedPost#acl_starts_with}.
193
+ # @option options [String] :cache_control
194
+ # See {PresignedPost#cache_control}.
195
+ # @option options [String] :cache_control_starts_with
196
+ # See {PresignedPost#cache_control_starts_with}.
197
+ # @option options [String] :content_type See {PresignedPost#content_type}.
198
+ # @option options [String] :content_type_starts_with
199
+ # See {PresignedPost#content_type_starts_with}.
200
+ # @option options [String] :content_disposition
201
+ # See {PresignedPost#content_disposition}.
202
+ # @option options [String] :content_disposition_starts_with
203
+ # See {PresignedPost#content_disposition_starts_with}.
204
+ # @option options [String] :content_encoding
205
+ # See {PresignedPost#content_encoding}.
206
+ # @option options [String] :content_encoding_starts_with
207
+ # See {PresignedPost#content_encoding_starts_with}.
208
+ # @option options [String] :expires See {PresignedPost#expires}.
209
+ # @option options [String] :expires_starts_with
210
+ # See {PresignedPost#expires_starts_with}.
211
+ # @option options [Range<Integer>] :content_length_range
212
+ # See {PresignedPost#content_length_range}.
213
+ # @option options [String] :success_action_redirect
214
+ # See {PresignedPost#success_action_redirect}.
215
+ # @option options [String] :success_action_redirect_starts_with
216
+ # See {PresignedPost#success_action_redirect_starts_with}.
217
+ # @option options [String] :success_action_status
218
+ # See {PresignedPost#success_action_status}.
219
+ # @option options [String] :storage_class
220
+ # See {PresignedPost#storage_class}.
221
+ # @option options [String] :website_redirect_location
222
+ # See {PresignedPost#website_redirect_location}.
223
+ # @option options [Hash<String,String>] :metadata
224
+ # See {PresignedPost#metadata}.
225
+ # @option options [Hash<String,String>] :metadata_starts_with
226
+ # See {PresignedPost#metadata_starts_with}.
227
+ # @option options [String] :server_side_encryption
228
+ # See {PresignedPost#server_side_encryption}.
229
+ # @option options [String] :server_side_encryption_aws_kms_key_id
230
+ # See {PresignedPost#server_side_encryption_aws_kms_key_id}.
231
+ # @option options [String] :server_side_encryption_customer_algorithm
232
+ # See {PresignedPost#server_side_encryption_customer_algorithm}.
233
+ # @option options [String] :server_side_encryption_customer_key
234
+ # See {PresignedPost#server_side_encryption_customer_key}.
235
+ def initialize(credentials, bucket_region, bucket_name, options = {})
236
+ @credentials = credentials.credentials
237
+ @bucket_region = bucket_region
238
+ @bucket_name = bucket_name
239
+ @accelerate = !!options.delete(:use_accelerate_endpoint)
240
+ @url = options.delete(:url) || bucket_url
241
+ @fields = {}
242
+ @key_set = false
243
+ @signature_expiration = Time.now + 3600
244
+ @conditions = [{ 'bucket' => @bucket_name }]
245
+ options.each do |option_name, option_value|
246
+ case option_name
247
+ when :allow_any then allow_any(option_value)
248
+ when :signature_expiration then @signature_expiration = option_value
249
+ else send("#{option_name}", option_value)
250
+ end
251
+ end
252
+ end
253
+
254
+ # @return [String] The URL to post a file upload to. This should be
255
+ # the form action.
256
+ attr_reader :url
257
+
258
+ # @return [Hash] A hash of fields to render in an HTML form
259
+ # as hidden input fields.
260
+ def fields
261
+ check_required_values!
262
+ datetime = Time.now.utc.strftime('%Y%m%dT%H%M%SZ')
263
+ fields = @fields.dup
264
+ fields.update('policy' => policy(datetime))
265
+ fields.update(signature_fields(datetime))
266
+ fields.update('x-amz-signature' => signature(datetime, fields['policy']))
267
+ end
268
+
269
+ # A list of form fields to white-list with any value.
270
+ # @param [Sting, Array<String>] field_names
271
+ # @return [self]
272
+ def allow_any(*field_names)
273
+ field_names.flatten.each do |field_name|
274
+ @key_set = true if field_name.to_s == 'key'
275
+ starts_with(field_name, '')
276
+ end
277
+ self
278
+ end
279
+
280
+ # @api private
281
+ def self.define_field(field, *args)
282
+ options = args.last.is_a?(Hash) ? args.pop : {}
283
+ field_name = args.last || field.to_s
284
+
285
+ define_method("#{field}") do |value|
286
+ with(field_name, value)
287
+ end
288
+
289
+ if options[:starts_with]
290
+ define_method("#{field}_starts_with") do |value|
291
+ starts_with(field_name, value)
292
+ end
293
+ end
294
+ end
295
+
296
+ # @!group Fields
297
+
298
+ # The key to use for the uploaded object. You can use `${filename}`
299
+ # as a variable in the key. This will be replaced with the name
300
+ # of the file as provided by the user.
301
+ #
302
+ # For example, if the key is given as `/user/betty/${filename}` and
303
+ # the file uploaded is named `lolcatz.jpg`, the resultant key will
304
+ # be `/user/betty/lolcatz.jpg`.
305
+ #
306
+ # @param [String] key
307
+ # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html)
308
+ # @return [self]
309
+ def key(key)
310
+ @key_set = true
311
+ with('key', key)
312
+ end
313
+
314
+ # Specify a prefix the uploaded
315
+ # @param [String] prefix
316
+ # @see #key
317
+ # @return [self]
318
+ def key_starts_with(prefix)
319
+ @key_set = true
320
+ starts_with('key', prefix)
321
+ end
322
+
323
+ # @!method acl(canned_acl)
324
+ # Specify the cannedl ACL (access control list) for the object.
325
+ # May be one of the following values:
326
+ #
327
+ # * `private`
328
+ # * `public-read`
329
+ # * `public-read-write`
330
+ # * `authenticated-read`
331
+ # * `bucket-owner-read`
332
+ # * `bucket-owner-full-control`
333
+ #
334
+ # @param [String] canned_acl
335
+ # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
336
+ # @return [self]
337
+ #
338
+ # @!method acl_starts_with(prefix)
339
+ # @param [String] prefix
340
+ # @see #acl
341
+ # @return [self]
342
+ define_field(:acl, starts_with: true)
343
+
344
+ # @!method cache_control(value)
345
+ # Specify caching behavior along the request/reply chain.
346
+ # @param [String] value
347
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.
348
+ # @return [self]
349
+ #
350
+ # @!method cache_control_starts_with(prefix)
351
+ # @param [String] prefix
352
+ # @see #cache_control
353
+ # @return [self]
354
+ define_field(:cache_control, 'Cache-Control', starts_with: true)
355
+
356
+ # @return [String]
357
+ # @!method content_type(value)
358
+ # A standard MIME type describing the format of the contents.
359
+ # @param [String] value
360
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
361
+ # @return [self]
362
+ #
363
+ # @!method content_type_starts_with(prefix)
364
+ # @param [String] prefix
365
+ # @see #content_type
366
+ # @return [self]
367
+ define_field(:content_type, 'Content-Type', starts_with: true)
368
+
369
+ # @!method content_disposition(value)
370
+ # Specifies presentational information for the object.
371
+ # @param [String] value
372
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1
373
+ # @return [self]
374
+ #
375
+ # @!method content_disposition_starts_with(prefix)
376
+ # @param [String] prefix
377
+ # @see #content_disposition
378
+ # @return [self]
379
+ define_field(:content_disposition, 'Content-Disposition', starts_with: true)
380
+
381
+ # @!method content_encoding(value)
382
+ # Specifies what content encodings have been applied to the object
383
+ # and thus what decoding mechanisms must be applied to obtain the
384
+ # media-type referenced by the Content-Type header field.
385
+ # @param [String] value
386
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
387
+ # @return [self]
388
+ #
389
+ # @!method content_encoding_starts_with(prefix)
390
+ # @param [String] prefix
391
+ # @see #content_encoding
392
+ # @return [self]
393
+ define_field(:content_encoding, 'Content-Encoding', starts_with: true)
394
+
395
+ # The date and time at which the object is no longer cacheable.
396
+ # @note This does not affect the expiration of the presigned post
397
+ # signature.
398
+ # @param [Time] time
399
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
400
+ # @return [self]
401
+ def expires(time)
402
+ with('Expires', time.httpdate)
403
+ end
404
+
405
+ # @param [String] prefix
406
+ # @see #expires
407
+ # @return [self]
408
+ def expires_starts_with(prefix)
409
+ starts_with('Expires', prefix)
410
+ end
411
+
412
+ # The minimum and maximum allowable size for the uploaded content.
413
+ # @param [Range<Integer>] byte_range
414
+ # @return [self]
415
+ def content_length_range(byte_range)
416
+ min = byte_range.begin
417
+ max = byte_range.end
418
+ max -= 1 if byte_range.exclude_end?
419
+ @conditions << ['content-length-range', min, max]
420
+ self
421
+ end
422
+
423
+ # @!method success_action_redirect(value)
424
+ # The URL to which the client is redirected
425
+ # upon successful upload. If {#success_action_redirect} is not
426
+ # specified, Amazon S3 returns the empty document type specified
427
+ # by {#success_action_status}.
428
+ #
429
+ # If Amazon S3 cannot interpret the URL, it acts as if the field
430
+ # is not present. If the upload fails, Amazon S3 displays an error
431
+ # and does not redirect the user to a URL.
432
+ #
433
+ # @param [String] value
434
+ # @return [self]
435
+ #
436
+ # @!method success_action_redirect_starts_with(prefix)
437
+ # @param [String] prefix
438
+ # @see #success_action_redirect
439
+ # @return [self]
440
+ define_field(:success_action_redirect, starts_with: true)
441
+
442
+ # @!method success_action_status(value)
443
+ # The status code returned to the client upon
444
+ # successful upload if {#success_action_redirect} is not
445
+ # specified.
446
+ #
447
+ # Accepts the values `200`, `201`, or `204` (default).
448
+ #
449
+ # If the value is set to 200 or 204, Amazon S3 returns an empty
450
+ # document with a 200 or 204 status code. If the value is set to 201,
451
+ # Amazon S3 returns an XML document with a 201 status code.
452
+ #
453
+ # If the value is not set or if it is set to an invalid value, Amazon
454
+ # S3 returns an empty document with a 204 status code.
455
+ #
456
+ # @param [String] value The status code returned to the client upon
457
+ # @return [self]
458
+ define_field(:success_action_status)
459
+
460
+ # @!method storage_class(value)
461
+ # Storage class to use for storing the object. Defaults to
462
+ # `STANDARD`. Must be one of:
463
+ #
464
+ # * `STANDARD`
465
+ # * `REDUCED_REDUNDANCY`
466
+ #
467
+ # You cannot specify `GLACIER` as the storage class. To transition
468
+ # objects to the GLACIER storage class you can use lifecycle
469
+ # configuration.
470
+ # @param [String] value Storage class to use for storing the
471
+ # @return [self]
472
+ define_field(:storage_class, 'x-amz-storage-class')
473
+
474
+ # @!method website_redirect_location(value)
475
+ # If the bucket is configured as a website,
476
+ # redirects requests for this object to another object in the
477
+ # same bucket or to an external URL. Amazon S3 stores this value
478
+ # in the object metadata.
479
+ #
480
+ # The value must be prefixed by, "/", "http://" or "https://".
481
+ # The length of the value is limited to 2K.
482
+ #
483
+ # @param [String] value
484
+ # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
485
+ # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html
486
+ # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html
487
+ # @return [self]
488
+ define_field(:website_redirect_location, 'x-amz-website-redirect-location')
489
+
490
+ # Metadata hash to store with the uploaded object. Hash keys will be
491
+ # prefixed with "x-amz-meta-".
492
+ # @param [Hash<String,String>] hash
493
+ # @return [self]
494
+ def metadata(hash)
495
+ hash.each do |key, value|
496
+ with("x-amz-meta-#{key}", value)
497
+ end
498
+ self
499
+ end
500
+
501
+ # Specify allowable prefix for each key in the metadata hash.
502
+ # @param [Hash<String,String>] hash
503
+ # @see #metadata
504
+ # @return [self]
505
+ def metadata_starts_with(hash)
506
+ hash.each do |key, value|
507
+ starts_with("x-amz-meta-#{key}", value)
508
+ end
509
+ self
510
+ end
511
+
512
+ # @!endgroup
513
+
514
+ # @!group Server-Side Encryption Fields
515
+
516
+ # @!method server_side_encryption(value)
517
+ # Specifies a server-side encryption algorithm to use when Amazon
518
+ # S3 creates an object. Valid values include:
519
+ #
520
+ # * `aws:kms`
521
+ # * `AES256`
522
+ #
523
+ # @param [String] value
524
+ # @return [self]
525
+ define_field(:server_side_encryption, 'x-amz-server-side-encryption')
526
+
527
+ # @!method server_side_encryption_aws_kms_key_id(value)
528
+ # If {#server_side_encryption} is called with the value of `aws:kms`,
529
+ # this method specifies the ID of the AWS Key Management Service
530
+ # (KMS) master encryption key to use for the object.
531
+ # @param [String] value
532
+ # @return [self]
533
+ define_field(
534
+ :server_side_encryption_aws_kms_key_id,
535
+ 'x-amz-server-side-encryption-aws-kms-key-id'
536
+ )
537
+
538
+ # @!endgroup
539
+
540
+ # @!group Server-Side Encryption with Customer-Provided Key Fields
541
+
542
+ # @!method server_side_encryption_customer_algorithm(value)
543
+ # Specifies the algorithm to use to when encrypting the object.
544
+ # Must be set to `AES256` when using customer-provided encryption
545
+ # keys. Must also call {#server_side_encryption_customer_key}.
546
+ # @param [String] value
547
+ # @see #server_side_encryption_customer_key
548
+ # @return [self]
549
+ define_field(
550
+ :server_side_encryption_customer_algorithm,
551
+ 'x-amz-server-side-encryption-customer-algorithm'
552
+ )
553
+
554
+ # Specifies the customer-provided encryption key for Amazon S3 to use
555
+ # in encrypting data. This value is used to store the object and then
556
+ # it is discarded; Amazon does not store the encryption key.
557
+ #
558
+ # You must also call {#server_side_encryption_customer_algorithm}.
559
+ #
560
+ # @param [String] value
561
+ # @see #server_side_encryption_customer_algorithm
562
+ # @return [self]
563
+ def server_side_encryption_customer_key(value)
564
+ field_name = 'x-amz-server-side-encryption-customer-key'
565
+ with(field_name, base64(value))
566
+ with(field_name + '-MD5', base64(OpenSSL::Digest::MD5.digest(value)))
567
+ end
568
+
569
+ # @param [String] prefix
570
+ # @see #server_side_encryption_customer_key
571
+ # @return [self]
572
+ def server_side_encryption_customer_key_starts_with(prefix)
573
+ field_name = 'x-amz-server-side-encryption-customer-key'
574
+ starts_with(field_name, prefix)
575
+ end
576
+
577
+ # @!endgroup
578
+
579
+ private
580
+
581
+ def with(field_name, value)
582
+ fvar = '${filename}'
583
+ if index = value.rindex(fvar)
584
+ if index + fvar.size == value.size
585
+ @fields[field_name] = value
586
+ starts_with(field_name, value[0,index])
587
+ else
588
+ msg = "${filename} only supported at the end of #{field_name}"
589
+ raise ArgumentError, msg
590
+ end
591
+ else
592
+ @fields[field_name] = value.to_s
593
+ @conditions << { field_name => value.to_s }
594
+ end
595
+ self
596
+ end
597
+
598
+ def starts_with(field_name, value, &block)
599
+ @conditions << ['starts-with', "$#{field_name}", value.to_s]
600
+ self
601
+ end
602
+
603
+ def check_required_values!
604
+ unless @key_set
605
+ msg = 'key required; you must provide a key via :key, '\
606
+ ":key_starts_with, or :allow_any => ['key']"
607
+ raise msg
608
+ end
609
+ end
610
+
611
+ def bucket_url
612
+ url = Aws::Partitions::EndpointProvider.resolve(@bucket_region, 's3')
613
+ url = URI.parse(url)
614
+ if Plugins::BucketDns.dns_compatible?(@bucket_name, _ssl = true)
615
+ if @accelerate
616
+ url.host = "#{@bucket_name}.s3-accelerate.amazonaws.com"
617
+ else
618
+ url.host = "#{@bucket_name}.#{url.host}"
619
+ end
620
+ else
621
+ url.path = "/#{@bucket_name}"
622
+ end
623
+ if @bucket_region == 'us-east-1'
624
+ # keep legacy behavior by default
625
+ url.host = Plugins::IADRegionalEndpoint.legacy_host(url.host)
626
+ end
627
+ url.to_s
628
+ end
629
+
630
+ # @return [Hash]
631
+ def policy(datetime)
632
+ check_required_values!
633
+ policy = {}
634
+ policy['expiration'] = @signature_expiration.utc.iso8601
635
+ policy['conditions'] = @conditions.dup
636
+ signature_fields(datetime).each do |name, value|
637
+ policy['conditions'] << { name => value }
638
+ end
639
+ base64(Json.dump(policy))
640
+ end
641
+
642
+ def signature_fields(datetime)
643
+ fields = {}
644
+ fields['x-amz-credential'] = credential_scope(datetime)
645
+ fields['x-amz-algorithm'] = 'AWS4-HMAC-SHA256'
646
+ fields['x-amz-date'] = datetime
647
+ if session_token = @credentials.session_token
648
+ fields['x-amz-security-token'] = session_token
649
+ end
650
+ fields
651
+ end
652
+
653
+ def signature(datetime, string_to_sign)
654
+ k_secret = @credentials.secret_access_key
655
+ k_date = hmac('AWS4' + k_secret, datetime[0,8])
656
+ k_region = hmac(k_date, @bucket_region)
657
+ k_service = hmac(k_region, 's3')
658
+ k_credentials = hmac(k_service, 'aws4_request')
659
+ hexhmac(k_credentials, string_to_sign)
660
+ end
661
+
662
+ def hmac(key, value)
663
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
664
+ end
665
+
666
+ def hexhmac(key, value)
667
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
668
+ end
669
+
670
+ def credential_scope(datetime)
671
+ parts = []
672
+ parts << @credentials.access_key_id
673
+ parts << datetime[0,8]
674
+ parts << @bucket_region
675
+ parts << 's3'
676
+ parts << 'aws4_request'
677
+ parts.join('/')
678
+ end
679
+
680
+ def base64(str)
681
+ Base64.strict_encode64(str)
682
+ end
683
+
684
+ end
685
+ end
686
+ end