aws-sdk-s3 1.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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