aws-sdk-resources 2.11.443 → 3.69.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/aws-v3.rb +201 -0
  3. data/lib/aws-sdk-resources.rb +223 -87
  4. metadata +3070 -70
  5. data/lib/aws-sdk-resources/batch.rb +0 -143
  6. data/lib/aws-sdk-resources/builder.rb +0 -85
  7. data/lib/aws-sdk-resources/builder_sources.rb +0 -105
  8. data/lib/aws-sdk-resources/collection.rb +0 -107
  9. data/lib/aws-sdk-resources/definition.rb +0 -331
  10. data/lib/aws-sdk-resources/documenter.rb +0 -70
  11. data/lib/aws-sdk-resources/documenter/base_operation_documenter.rb +0 -279
  12. data/lib/aws-sdk-resources/documenter/data_operation_documenter.rb +0 -25
  13. data/lib/aws-sdk-resources/documenter/has_many_operation_documenter.rb +0 -69
  14. data/lib/aws-sdk-resources/documenter/has_operation_documenter.rb +0 -66
  15. data/lib/aws-sdk-resources/documenter/operation_documenter.rb +0 -20
  16. data/lib/aws-sdk-resources/documenter/resource_operation_documenter.rb +0 -53
  17. data/lib/aws-sdk-resources/documenter/waiter_operation_documenter.rb +0 -77
  18. data/lib/aws-sdk-resources/errors.rb +0 -15
  19. data/lib/aws-sdk-resources/operation_methods.rb +0 -83
  20. data/lib/aws-sdk-resources/operations.rb +0 -280
  21. data/lib/aws-sdk-resources/options.rb +0 -17
  22. data/lib/aws-sdk-resources/request.rb +0 -39
  23. data/lib/aws-sdk-resources/request_params.rb +0 -140
  24. data/lib/aws-sdk-resources/resource.rb +0 -243
  25. data/lib/aws-sdk-resources/services/ec2.rb +0 -21
  26. data/lib/aws-sdk-resources/services/ec2/instance.rb +0 -29
  27. data/lib/aws-sdk-resources/services/iam.rb +0 -19
  28. data/lib/aws-sdk-resources/services/s3.rb +0 -20
  29. data/lib/aws-sdk-resources/services/s3/bucket.rb +0 -131
  30. data/lib/aws-sdk-resources/services/s3/encryption.rb +0 -21
  31. data/lib/aws-sdk-resources/services/s3/encryption/client.rb +0 -369
  32. data/lib/aws-sdk-resources/services/s3/encryption/decrypt_handler.rb +0 -174
  33. data/lib/aws-sdk-resources/services/s3/encryption/default_cipher_provider.rb +0 -63
  34. data/lib/aws-sdk-resources/services/s3/encryption/default_key_provider.rb +0 -38
  35. data/lib/aws-sdk-resources/services/s3/encryption/encrypt_handler.rb +0 -50
  36. data/lib/aws-sdk-resources/services/s3/encryption/errors.rb +0 -13
  37. data/lib/aws-sdk-resources/services/s3/encryption/io_auth_decrypter.rb +0 -56
  38. data/lib/aws-sdk-resources/services/s3/encryption/io_decrypter.rb +0 -29
  39. data/lib/aws-sdk-resources/services/s3/encryption/io_encrypter.rb +0 -69
  40. data/lib/aws-sdk-resources/services/s3/encryption/key_provider.rb +0 -29
  41. data/lib/aws-sdk-resources/services/s3/encryption/kms_cipher_provider.rb +0 -71
  42. data/lib/aws-sdk-resources/services/s3/encryption/materials.rb +0 -58
  43. data/lib/aws-sdk-resources/services/s3/encryption/utils.rb +0 -79
  44. data/lib/aws-sdk-resources/services/s3/file_downloader.rb +0 -169
  45. data/lib/aws-sdk-resources/services/s3/file_part.rb +0 -75
  46. data/lib/aws-sdk-resources/services/s3/file_uploader.rb +0 -58
  47. data/lib/aws-sdk-resources/services/s3/multipart_file_uploader.rb +0 -187
  48. data/lib/aws-sdk-resources/services/s3/multipart_upload.rb +0 -42
  49. data/lib/aws-sdk-resources/services/s3/multipart_upload_error.rb +0 -16
  50. data/lib/aws-sdk-resources/services/s3/object.rb +0 -290
  51. data/lib/aws-sdk-resources/services/s3/object_copier.rb +0 -99
  52. data/lib/aws-sdk-resources/services/s3/object_multipart_copier.rb +0 -180
  53. data/lib/aws-sdk-resources/services/s3/object_summary.rb +0 -73
  54. data/lib/aws-sdk-resources/services/s3/presigned_post.rb +0 -651
  55. data/lib/aws-sdk-resources/services/sns.rb +0 -7
  56. data/lib/aws-sdk-resources/services/sns/message_verifier.rb +0 -171
  57. data/lib/aws-sdk-resources/services/sqs.rb +0 -7
  58. data/lib/aws-sdk-resources/services/sqs/queue_poller.rb +0 -521
  59. data/lib/aws-sdk-resources/source.rb +0 -39
@@ -1,99 +0,0 @@
1
- require 'thread'
2
-
3
- module Aws
4
- module S3
5
- # @api private
6
- class ObjectCopier
7
-
8
- # @param [S3::Object] object
9
- def initialize(object, options = {})
10
- @object = object
11
- @options = options.merge(client: @object.client)
12
- end
13
-
14
- def copy_from(source, options = {})
15
- copy_object(source, @object, merge_options(source, options))
16
- end
17
-
18
- def copy_to(target, options = {})
19
- copy_object(@object, target, merge_options(target, options))
20
- end
21
-
22
- private
23
-
24
- def copy_object(source, target, options)
25
- target_bucket, target_key = copy_target(target)
26
- options[:bucket] = target_bucket
27
- options[:key] = target_key
28
- options[:copy_source] = copy_source(source)
29
- if options.delete(:multipart_copy)
30
- apply_source_client(source, options)
31
- ObjectMultipartCopier.new(@options).copy(options)
32
- else
33
- @object.client.copy_object(options)
34
- end
35
- end
36
-
37
- def copy_source(source)
38
- case source
39
- when String then source
40
- when Hash
41
- src = "#{source[:bucket]}/#{escape(source[:key])}"
42
- src += "?versionId=#{source[:version_id]}" if source.key?(:version_id)
43
- src
44
- when S3::Object, S3::ObjectSummary
45
- "#{source.bucket_name}/#{escape(source.key)}"
46
- when S3::ObjectVersion
47
- "#{source.bucket_name}/#{escape(source.object_key)}?versionId=#{source.id}"
48
- else
49
- msg = "expected source to be an Aws::S3::Object, Hash, or String"
50
- raise ArgumentError, msg
51
- end
52
- end
53
-
54
- def copy_target(target)
55
- case target
56
- when String then target.match(/([^\/]+?)\/(.+)/)[1,2]
57
- when Hash then target.values_at(:bucket, :key)
58
- when S3::Object then [target.bucket_name, target.key]
59
- else
60
- msg = "expected target to be an Aws::S3::Object, Hash, or String"
61
- raise ArgumentError, msg
62
- end
63
- end
64
-
65
- def merge_options(source_or_target, options)
66
- if Hash === source_or_target
67
- source_or_target.inject(options.dup) do |opts, (key, value)|
68
- opts[key] = value unless [:bucket, :key, :version_id].include?(key)
69
- opts
70
- end
71
- else
72
- options.dup
73
- end
74
- end
75
-
76
- def apply_source_client(source, options)
77
-
78
- if source.respond_to?(:client)
79
- options[:copy_source_client] ||= source.client
80
- end
81
-
82
- if options[:copy_source_region]
83
- config = @object.client.config
84
- config = config.each_pair.inject({}) { |h, (k,v)| h[k] = v; h }
85
- config[:region] = options.delete(:copy_source_region)
86
- options[:copy_source_client] ||= S3::Client.new(config)
87
- end
88
-
89
- options[:copy_source_client] ||= @object.client
90
-
91
- end
92
-
93
- def escape(str)
94
- Seahorse::Util.uri_path_escape(str)
95
- end
96
-
97
- end
98
- end
99
- end
@@ -1,180 +0,0 @@
1
- require 'thread'
2
- require 'cgi'
3
-
4
- module Aws
5
- module S3
6
- # @api private
7
- class ObjectMultipartCopier
8
-
9
- FIVE_MB = 5 * 1024 * 1024 # 5MB
10
-
11
- FILE_TOO_SMALL = "unable to multipart copy files smaller than 5MB"
12
-
13
- MAX_PARTS = 10_000
14
-
15
- # @option options [Client] :client
16
- # @option [Integer] :min_part_size (52428800) Size of copied parts.
17
- # Defaults to 50MB.
18
- # will be constructed from the given `options' hash.
19
- # @option [Integer] :thread_count (10) Number of concurrent threads to
20
- # use for copying parts.
21
- def initialize(options = {})
22
- @thread_count = options.delete(:thread_count) || 10
23
- @min_part_size = options.delete(:min_part_size) || (FIVE_MB * 10)
24
- @client = options[:client] || Client.new
25
- end
26
-
27
- # @return [Client]
28
- attr_reader :client
29
-
30
- # @option (see S3::Client#copy_object)
31
- def copy(options = {})
32
- size = source_size(options)
33
- options[:upload_id] = initiate_upload(options)
34
- begin
35
- parts = copy_parts(size, default_part_size(size), options)
36
- complete_upload(parts, options)
37
- rescue => error
38
- abort_upload(options)
39
- raise error
40
- end
41
- end
42
-
43
- private
44
-
45
- def initiate_upload(options)
46
- options = options_for(:create_multipart_upload, options)
47
- @client.create_multipart_upload(options).upload_id
48
- end
49
-
50
- def copy_parts(size, default_part_size, options)
51
- queue = PartQueue.new(compute_parts(size, default_part_size, options))
52
- threads = []
53
- @thread_count.times do
54
- threads << copy_part_thread(queue)
55
- end
56
- threads.map(&:value).flatten.sort_by{ |part| part[:part_number] }
57
- end
58
-
59
- def copy_part_thread(queue)
60
- Thread.new do
61
- begin
62
- completed = []
63
- while part = queue.shift
64
- completed << copy_part(part)
65
- end
66
- completed
67
- rescue => error
68
- queue.clear!
69
- raise error
70
- end
71
- end
72
- end
73
-
74
- def copy_part(part)
75
- {
76
- etag: @client.upload_part_copy(part).copy_part_result.etag,
77
- part_number: part[:part_number],
78
- }
79
- end
80
-
81
- def complete_upload(parts, options)
82
- options = options_for(:complete_multipart_upload, options)
83
- options[:multipart_upload] = { parts: parts }
84
- @client.complete_multipart_upload(options)
85
- end
86
-
87
- def abort_upload(options)
88
- @client.abort_multipart_upload({
89
- bucket: options[:bucket],
90
- key: options[:key],
91
- upload_id: options[:upload_id],
92
- })
93
- end
94
-
95
- def compute_parts(size, default_part_size, options)
96
- part_number = 1
97
- offset = 0
98
- parts = []
99
- options = options_for(:upload_part_copy, options)
100
- while offset < size
101
- parts << options.merge({
102
- part_number: part_number,
103
- copy_source_range: byte_range(offset, default_part_size, size),
104
- })
105
- part_number += 1
106
- offset += default_part_size
107
- end
108
- parts
109
- end
110
-
111
- def byte_range(offset, default_part_size, size)
112
- if offset + default_part_size < size
113
- "bytes=#{offset}-#{offset + default_part_size - 1}"
114
- else
115
- "bytes=#{offset}-#{size - 1}"
116
- end
117
- end
118
-
119
- def source_size(options)
120
- return options.delete(:content_length) if options[:content_length]
121
-
122
- client = options[:copy_source_client] || @client
123
-
124
- if vid_match = options[:copy_source].match(/([^\/]+?)\/(.+)\?versionId=(.+)/)
125
- bucket, key, version_id = vid_match[1,3]
126
- else
127
- bucket, key = options[:copy_source].match(/([^\/]+?)\/(.+)/)[1,2]
128
- end
129
-
130
- key = CGI.unescape(key)
131
- opts = { bucket: bucket, key: key }
132
- opts[:version_id] = version_id if version_id
133
- client.head_object(opts).content_length
134
- end
135
-
136
- def default_part_size(source_size)
137
- if source_size < FIVE_MB
138
- raise ArgumentError, FILE_TOO_SMALL
139
- else
140
- [(source_size.to_f / MAX_PARTS).ceil, @min_part_size].max.to_i
141
- end
142
- end
143
-
144
- def options_for(operation_name, options)
145
- API_OPTIONS[operation_name].inject({}) do |hash, opt_name|
146
- hash[opt_name] = options[opt_name] if options.key?(opt_name)
147
- hash
148
- end
149
- end
150
-
151
- # @api private
152
- def self.options_for(shape_name)
153
- Client.api.metadata['shapes'][shape_name].member_names
154
- end
155
-
156
- API_OPTIONS = {
157
- create_multipart_upload: options_for('CreateMultipartUploadRequest'),
158
- upload_part_copy: options_for('UploadPartCopyRequest'),
159
- complete_multipart_upload: options_for('CompleteMultipartUploadRequest'),
160
- }
161
-
162
- class PartQueue
163
-
164
- def initialize(parts = [])
165
- @parts = parts
166
- @mutex = Mutex.new
167
- end
168
-
169
- def shift
170
- @mutex.synchronize { @parts.shift }
171
- end
172
-
173
- def clear!
174
- @mutex.synchronize { @parts.clear }
175
- end
176
-
177
- end
178
- end
179
- end
180
- end
@@ -1,73 +0,0 @@
1
- module Aws
2
- module S3
3
- class ObjectSummary
4
-
5
- alias content_length size
6
-
7
- # @param (see Object#copy_from)
8
- # @options (see Object#copy_from)
9
- # @return (see Object#copy_from)
10
- # @see Object#copy_from
11
- def copy_from(source, options = {})
12
- object.copy_from(source, options)
13
- end
14
-
15
- # @param (see Object#copy_to)
16
- # @options (see Object#copy_to)
17
- # @return (see Object#copy_to)
18
- # @see Object#copy_to
19
- def copy_to(target, options = {})
20
- object.copy_to(target, options)
21
- end
22
-
23
- # @param (see Object#move_to)
24
- # @options (see Object#move_to)
25
- # @return (see Object#move_to)
26
- # @see Object#move_to
27
- def move_to(target, options = {})
28
- object.move_to(target, options)
29
- end
30
-
31
- # @param (see Object#presigned_post)
32
- # @options (see Object#presigned_post)
33
- # @return (see Object#presigned_post)
34
- # @see Object#presigned_post
35
- def presigned_post(options = {})
36
- object.presigned_post(options)
37
- end
38
-
39
- # @param (see Object#presigned_url)
40
- # @options (see Object#presigned_url)
41
- # @return (see Object#presigned_url)
42
- # @see Object#presigned_url
43
- def presigned_url(http_method, params = {})
44
- object.presigned_url(http_method, params)
45
- end
46
-
47
- # @param (see Object#public_url)
48
- # @options (see Object#public_url)
49
- # @return (see Object#public_url)
50
- # @see Object#public_url
51
- def public_url(options = {})
52
- object.public_url(options)
53
- end
54
-
55
- # @param (see Object#upload_file)
56
- # @options (see Object#upload_file)
57
- # @return (see Object#upload_file)
58
- # @see Object#upload_file
59
- def upload_file(source, options = {})
60
- object.upload_file(source, options)
61
- end
62
-
63
- # @param (see Object#download_file)
64
- # @options (see Object#download_file)
65
- # @return (see Object#download_file)
66
- # @see Object#download_file
67
- def download_file(destination, options = {})
68
- object.download_file(destination, options)
69
- end
70
-
71
- end
72
- end
73
- end
@@ -1,651 +0,0 @@
1
- require 'openssl'
2
- require 'base64'
3
-
4
- module Aws
5
- module S3
6
-
7
- # @note Normally you do not need to construct a {PresignedPost} yourself.
8
- # See {Bucket#presigned_post} and {Object#presigned_post}.
9
- #
10
- # ## Basic Usage
11
- #
12
- # To generate a presigned post, you need AWS credentials, the region
13
- # your bucket is in, and the name of your bucket. You can apply constraints
14
- # to the post object as options to {#initialize} or by calling
15
- # methods such as {#key} and {#content_length_range}.
16
- #
17
- # The following two examples are equivalent.
18
- #
19
- # ```ruby
20
- # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
21
- # key: '/uploaded/object/key',
22
- # content_length_range: 0..1024,
23
- # acl: 'public-read',
24
- # metadata: {
25
- # 'original-filename' => '${filename}'
26
- # }
27
- # })
28
- # post.fields
29
- # #=> { ... }
30
- #
31
- # post = Aws::S3::PresignedPost.new(creds, region, bucket).
32
- # key('/uploaded/object/key').
33
- # content_length_range(0..1024).
34
- # acl('public-read').
35
- # metadata('original-filename' => '${filename}').
36
- # fields
37
- # #=> { ... }
38
- # ```
39
- #
40
- # ## HTML Forms
41
- #
42
- # You can use a {PresignedPost} object to build an HTML form. It is
43
- # recommended to use some helper to build the form tag and input
44
- # tags that properly escapes values.
45
- #
46
- # ### Form Tag
47
- #
48
- # To upload a file to Amazon S3 using a browser, you need to create
49
- # a post form. The {#url} method returns the value you should use
50
- # as the form action.
51
- #
52
- # ```erb
53
- # <form action="<%= @post.url %>" method="post" enctype="multipart/form-data">
54
- # ...
55
- # </form>
56
- # ```
57
- #
58
- # The follow attributes must be set on the form:
59
- #
60
- # * `action` - This must be the {#url}.
61
- # * `method` - This must be `post`.
62
- # * `enctype` - This must be `multipart/form-data`.
63
- #
64
- # ### Form Fields
65
- #
66
- # The {#fields} method returns a hash of form fields to render inside
67
- # the form. Typically these are rendered as hidden input fields.
68
- #
69
- # ```erb
70
- # <% @post.fields.each do |name, value| %>
71
- # <input type="hidden" name="<%= name %>" value="<%= value %>"/>
72
- # <% end %>
73
- # ```
74
- #
75
- # Lastly, the form must have a file field with the name `file`.
76
- #
77
- # ```erb
78
- # <input type="file" name="file"/>
79
- # ```
80
- #
81
- # ## Post Policy
82
- #
83
- # When you construct a {PresignedPost}, you must specify every form
84
- # field name that will be posted by the browser. If you omit a form
85
- # field sent by the browser, Amazon S3 will reject the request.
86
- # You can specify accepted form field values three ways:
87
- #
88
- # * Specify exactly what the value must be.
89
- # * Specify what value the field starts with.
90
- # * Specify the field may have any value.
91
- #
92
- # ### Field Equals
93
- #
94
- # You can specify that a form field must be a certain value.
95
- # Simply pass an option like `:content_type` to the constructor,
96
- # or call the associated method.
97
- #
98
- # ```ruby
99
- # post = Aws::S3::PresignedPost.new(creds, region, bucket).
100
- # post.content_type('text/plain')
101
- # ```
102
- #
103
- # If any of the given values are changed by the user in the form, then
104
- # Amazon S3 will reject the POST request.
105
- #
106
- # ### Field Starts With
107
- #
108
- # You can specify prefix values for many of the POST form fields.
109
- # To specify a required prefix, use the `:<fieldname>_starts_with`
110
- # option or call the associated `#<field_name>_starts_with` method.
111
- #
112
- # ```ruby
113
- # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
114
- # key_starts_with: '/images/',
115
- # content_type_starts_with: 'image/',
116
- # # ...
117
- # })
118
- # ```
119
- #
120
- # When using starts with, the form must contain a field where the
121
- # user can specify the value. The {PresignedPost} will not add
122
- # a value for these fields.
123
- #
124
- # ### Any Field Value
125
- #
126
- # To white-list a form field to send any value, you can name that
127
- # field with `:allow_any` or {#allow_any}.
128
- #
129
- # ```ruby
130
- # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
131
- # key: 'object-key',
132
- # allow_any: ['Filename'],
133
- # # ...
134
- # })
135
- # ```
136
- #
137
- # ### Metadata
138
- #
139
- # You can add rules for metadata fields using `:metadata`, {#metadata},
140
- # `:metadata_starts_with` and {#metadata_starts_with}. Unlike other
141
- # form fields, you pass a hash value to these options/methods:
142
- #
143
- # ```ruby
144
- # post = Aws::S3::PresignedPost.new(creds, region, bucket).
145
- # key('/fixed/key').
146
- # metadata(foo: 'bar')
147
- #
148
- # post.fields['x-amz-meta-foo']
149
- # #=> 'bar'
150
- # ```
151
- #
152
- # ### The `${filename}` Variable
153
- #
154
- # The string `${filename}` is automatically replaced with the name of the
155
- # file provided by the user and is recognized by all form fields. It is
156
- # not supported with `starts_with` conditions.
157
- #
158
- # If the browser or client provides a full or partial path to the file,
159
- # only the text following the last slash (/) or backslash (\) will be used
160
- # (e.g., "C:\Program Files\directory1\file.txt" will be interpreted
161
- # as "file.txt"). If no file or file name is provided, the variable is
162
- # replaced with an empty string.
163
- #
164
- # In the following example, we use `${filename}` to store the original
165
- # filename in the `x-amz-meta-` hash with the uploaded object.
166
- #
167
- # ```ruby
168
- # post = Aws::S3::PresignedPost.new(creds, region, bucket, {
169
- # key: '/fixed/key',
170
- # metadata: {
171
- # 'original-filename': '${filename}'
172
- # }
173
- # })
174
- # ```
175
- #
176
- class PresignedPost
177
-
178
- # @param [Credentials] credentials Security credentials for signing
179
- # the post policy.
180
- # @param [String] bucket_region Region of the target bucket.
181
- # @param [String] bucket_name Name of the target bucket.
182
- # @option options [Time] :signature_expiration Specify when the signature on
183
- # the post will expire. Defaults to one hour from creation of the
184
- # presigned post. May not exceed one week from creation time.
185
- # @option options [String] :key See {PresignedPost#key}.
186
- # @option options [String] :key_starts_with See {PresignedPost#key_starts_with}.
187
- # @option options [String] :acl See {PresignedPost#acl}.
188
- # @option options [String] :acl_starts_with See {PresignedPost#acl_starts_with}.
189
- # @option options [String] :cache_control See {PresignedPost#cache_control}.
190
- # @option options [String] :cache_control_starts_with See {PresignedPost#cache_control_starts_with}.
191
- # @option options [String] :content_type See {PresignedPost#content_type}.
192
- # @option options [String] :content_type_starts_with See {PresignedPost#content_type_starts_with}.
193
- # @option options [String] :content_disposition See {PresignedPost#content_disposition}.
194
- # @option options [String] :content_disposition_starts_with See {PresignedPost#content_disposition_starts_with}.
195
- # @option options [String] :content_encoding See {PresignedPost#content_encoding}.
196
- # @option options [String] :content_encoding_starts_with See {PresignedPost#content_encoding_starts_with}.
197
- # @option options [String] :expires See {PresignedPost#expires}.
198
- # @option options [String] :expires_starts_with See {PresignedPost#expires_starts_with}.
199
- # @option options [Range<Integer>] :content_length_range See {PresignedPost#content_length_range}.
200
- # @option options [String] :success_action_redirect See {PresignedPost#success_action_redirect}.
201
- # @option options [String] :success_action_redirect_starts_with See {PresignedPost#success_action_redirect_starts_with}.
202
- # @option options [String] :success_action_status See {PresignedPost#success_action_status}.
203
- # @option options [String] :storage_class See {PresignedPost#storage_class}.
204
- # @option options [String] :website_redirect_location See {PresignedPost#website_redirect_location}.
205
- # @option options [Hash<String,String>] :metadata See {PresignedPost#metadata}.
206
- # @option options [Hash<String,String>] :metadata_starts_with See {PresignedPost#metadata_starts_with}.
207
- # @option options [String] :server_side_encryption See {PresignedPost#server_side_encryption}.
208
- # @option options [String] :server_side_encryption_aws_kms_key_id See {PresignedPost#server_side_encryption_aws_kms_key_id}.
209
- # @option options [String] :server_side_encryption_customer_algorithm See {PresignedPost#server_side_encryption_customer_algorithm}.
210
- # @option options [String] :server_side_encryption_customer_key See {PresignedPost#server_side_encryption_customer_key}.
211
- def initialize(credentials, bucket_region, bucket_name, options = {})
212
- @credentials = credentials.credentials
213
- @bucket_region = bucket_region
214
- @bucket_name = bucket_name
215
- @url = options.delete(:url) || bucket_url
216
- @fields = {}
217
- @key_set = false
218
- @signature_expiration = Time.now + 3600
219
- @conditions = [{ 'bucket' => @bucket_name }]
220
- options.each do |option_name, option_value|
221
- case option_name
222
- when :allow_any then allow_any(option_value)
223
- when :signature_expiration then @signature_expiration = option_value
224
- else send("#{option_name}", option_value)
225
- end
226
- end
227
- end
228
-
229
- # @return [String] The URL to post a file upload to. This should be
230
- # the form action.
231
- attr_reader :url
232
-
233
- # @return [Hash] A hash of fields to render in an HTML form
234
- # as hidden input fields.
235
- def fields
236
- check_required_values!
237
- datetime = Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
238
- fields = @fields.dup
239
- fields.update('policy' => policy(datetime))
240
- fields.update(signature_fields(datetime))
241
- fields.update('x-amz-signature' => signature(datetime, fields['policy']))
242
- end
243
-
244
- # A list of form fields to white-list with any value.
245
- # @param [Sting, Array<String>] field_names
246
- # @return [self]
247
- def allow_any(*field_names)
248
- field_names.flatten.each do |field_name|
249
- @key_set = true if field_name.to_s == 'key'
250
- starts_with(field_name, '')
251
- end
252
- self
253
- end
254
-
255
- # @api private
256
- def self.define_field(field, *args)
257
- options = args.last.is_a?(Hash) ? args.pop : {}
258
- field_name = args.last || field.to_s
259
-
260
- define_method("#{field}") do |value|
261
- with(field_name, value)
262
- end
263
-
264
- if options[:starts_with]
265
- define_method("#{field}_starts_with") do |value|
266
- starts_with(field_name, value)
267
- end
268
- end
269
- end
270
-
271
- # @!group Fields
272
-
273
- # The key to use for the uploaded object. Use can use `${filename}`
274
- # as a variable in the key. This will be replaced with the name
275
- # of the file as provided by the user.
276
- #
277
- # For example, if the key is given as `/user/betty/${filename}` and
278
- # the file uploaded is named `lolcatz.jpg`, the resultant key will
279
- # be `/user/betty/lolcatz.jpg`.
280
- #
281
- # @param [String] key
282
- # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html)
283
- # @return [self]
284
- def key(key)
285
- @key_set = true
286
- with('key', key)
287
- end
288
-
289
- # Specify a prefix the uploaded
290
- # @param [String] prefix
291
- # @see #key
292
- # @return [self]
293
- def key_starts_with(prefix)
294
- @key_set = true
295
- starts_with('key', prefix)
296
- end
297
-
298
- # @!method acl(canned_acl)
299
- # Specify the cannedl ACL (access control list) for the object.
300
- # May be one of the following values:
301
- #
302
- # * `private`
303
- # * `public-read`
304
- # * `public-read-write`
305
- # * `authenticated-read`
306
- # * `bucket-owner-read`
307
- # * `bucket-owner-full-control`
308
- #
309
- # @param [String] canned_acl
310
- # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
311
- # @return [self]
312
- #
313
- # @!method acl_starts_with(prefix)
314
- # @param [String] prefix
315
- # @see #acl
316
- # @return [self]
317
- define_field(:acl, starts_with: true)
318
-
319
- # @!method cache_control(value)
320
- # Specify caching behavior along the request/reply chain.
321
- # @param [String] value
322
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.
323
- # @return [self]
324
- #
325
- # @!method cache_control_starts_with(prefix)
326
- # @param [String] prefix
327
- # @see #cache_control
328
- # @return [self]
329
- define_field(:cache_control, 'Cache-Control', starts_with: true)
330
-
331
- # @return [String]
332
- # @!method content_type(value)
333
- # A standard MIME type describing the format of the contents.
334
- # @param [String] value
335
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
336
- # @return [self]
337
- #
338
- # @!method content_type_starts_with(prefix)
339
- # @param [String] prefix
340
- # @see #content_type
341
- # @return [self]
342
- define_field(:content_type, 'Content-Type', starts_with: true)
343
-
344
- # @!method content_disposition(value)
345
- # Specifies presentational information for the object.
346
- # @param [String] value
347
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1
348
- # @return [self]
349
- #
350
- # @!method content_disposition_starts_with(prefix)
351
- # @param [String] prefix
352
- # @see #content_disposition
353
- # @return [self]
354
- define_field(:content_disposition, 'Content-Disposition', starts_with: true)
355
-
356
- # @!method content_encoding(value)
357
- # Specifies what content encodings have been applied to the object
358
- # and thus what decoding mechanisms must be applied to obtain the
359
- # media-type referenced by the Content-Type header field.
360
- # @param [String] value
361
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
362
- # @return [self]
363
- #
364
- # @!method content_encoding_starts_with(prefix)
365
- # @param [String] prefix
366
- # @see #content_encoding
367
- # @return [self]
368
- define_field(:content_encoding, 'Content-Encoding', starts_with: true)
369
-
370
- # The date and time at which the object is no longer cacheable.
371
- # @note This does not affect the expiration of the presigned post
372
- # signature.
373
- # @param [Time] time
374
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
375
- # @return [self]
376
- def expires(time)
377
- with('Expires', time.httpdate)
378
- end
379
-
380
- # @param [String] prefix
381
- # @see #expires
382
- # @return [self]
383
- def expires_starts_with(prefix)
384
- starts_with('Expires', prefix)
385
- end
386
-
387
- # The minimum and maximum allowable size for the uploaded content.
388
- # @param [Range<Integer>] byte_range
389
- # @return [self]
390
- def content_length_range(byte_range)
391
- min = byte_range.begin
392
- max = byte_range.end
393
- max -= 1 if byte_range.exclude_end?
394
- @conditions << ['content-length-range', min, max]
395
- self
396
- end
397
-
398
- # @!method success_action_redirect(value)
399
- # The URL to which the client is redirected
400
- # upon successful upload. If {#success_action_redirect} is not
401
- # specified, Amazon S3 returns the empty document type specified
402
- # by {#success_action_status}.
403
- #
404
- # If Amazon S3 cannot interpret the URL, it acts as if the field
405
- # is not present. If the upload fails, Amazon S3 displays an error
406
- # and does not redirect the user to a URL.
407
- #
408
- # @param [String] value
409
- # @return [self]
410
- #
411
- # @!method success_action_redirect_starts_with(prefix)
412
- # @param [String] prefix
413
- # @see #success_action_redirect
414
- # @return [self]
415
- define_field(:success_action_redirect, starts_with: true)
416
-
417
- # @!method success_action_status(value)
418
- # The status code returned to the client upon
419
- # successful upload if {#success_action_redirect} is not
420
- # specified.
421
- #
422
- # Accepts the values `200`, `201`, or `204` (default).
423
- #
424
- # If the value is set to 200 or 204, Amazon S3 returns an empty
425
- # document with a 200 or 204 status code. If the value is set to 201,
426
- # Amazon S3 returns an XML document with a 201 status code.
427
- #
428
- # If the value is not set or if it is set to an invalid value, Amazon
429
- # S3 returns an empty document with a 204 status code.
430
- #
431
- # @param [String] The status code returned to the client upon
432
- # @return [self]
433
- define_field(:success_action_status)
434
-
435
- # @!method storage_class(value)
436
- # Storage class to use for storing the object. Defaults to
437
- # `STANDARD`. Must be one of:
438
- #
439
- # * `STANDARD`
440
- # * `REDUCED_REDUNDANCY`
441
- #
442
- # You cannot specify `GLACIER` as the storage class. To transition
443
- # objects to the GLACIER storage class you can use lifecycle
444
- # configuration.
445
- # @param [String] value Storage class to use for storing the
446
- # @return [self]
447
- define_field(:storage_class, 'x-amz-storage-class')
448
-
449
- # @!method website_redirect_location(value)
450
- # If the bucket is configured as a website,
451
- # redirects requests for this object to another object in the
452
- # same bucket or to an external URL. Amazon S3 stores this value
453
- # in the object metadata.
454
- #
455
- # The value must be prefixed by, "/", "http://" or "https://".
456
- # The length of the value is limited to 2K.
457
- #
458
- # @param [String] value
459
- # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
460
- # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html
461
- # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html
462
- # @return [self]
463
- define_field(:website_redirect_location, 'x-amz-website-redirect-location')
464
-
465
- # Metadata hash to store with the uploaded object. Hash keys will be
466
- # prefixed with "x-amz-meta-".
467
- # @param [Hash<String,String>] hash
468
- # @return [self]
469
- def metadata(hash)
470
- hash.each do |key, value|
471
- with("x-amz-meta-#{key}", value)
472
- end
473
- self
474
- end
475
-
476
- # Specify allowable prefix for each key in the metadata hash.
477
- # @param [Hash<String,String>] hash
478
- # @see #metadata
479
- # @return [self]
480
- def metadata_starts_with(hash)
481
- hash.each do |key, value|
482
- starts_with("x-amz-meta-#{key}", value)
483
- end
484
- self
485
- end
486
-
487
- # @!endgroup
488
-
489
- # @!group Server-Side Encryption Fields
490
-
491
- # @!method server_side_encryption(value)
492
- # Specifies a server-side encryption algorithm to use when Amazon
493
- # S3 creates an object. Valid values include:
494
- #
495
- # * `aws:kms`
496
- # * `AES256`
497
- #
498
- # @param [String] value
499
- # @return [self]
500
- define_field(:server_side_encryption, 'x-amz-server-side-encryption')
501
-
502
- # @!method server_side_encryption_aws_kms_key_id(value)
503
- # If {#server_side_encryption} is called with the value of `aws:kms`,
504
- # this method specifies the ID of the AWS Key Management Service
505
- # (KMS) master encryption key to use for the object.
506
- # @param [String] value
507
- # @return [self]
508
- define_field(:server_side_encryption_aws_kms_key_id, 'x-amz-server-side-encryption-aws-kms-key-id')
509
-
510
- # @!endgroup
511
-
512
- # @!group Server-Side Encryption with Customer-Provided Key Fields
513
-
514
- # @!method server_side_encryption_customer_algorithm(value)
515
- # Specifies the algorithm to use to when encrypting the object.
516
- # Must be set to `AES256` when using customer-provided encryption
517
- # keys. Must also call {#server_side_encryption_customer_key}.
518
- # @param [String] value
519
- # @see #server_side_encryption_customer_key
520
- # @return [self]
521
- define_field(:server_side_encryption_customer_algorithm, 'x-amz-server-side-encryption-customer-algorithm')
522
-
523
- # Specifies the customer-provided encryption key for Amazon S3 to use
524
- # in encrypting data. This value is used to store the object and then
525
- # it is discarded; Amazon does not store the encryption key.
526
- #
527
- # You must also call {#server_side_encryption_customer_algorithm}.
528
- #
529
- # @param [String] value
530
- # @see #server_side_encryption_customer_algorithm
531
- # @return [self]
532
- def server_side_encryption_customer_key(value)
533
- field_name = 'x-amz-server-side-encryption-customer-key'
534
- with(field_name, base64(value))
535
- with(field_name + '-MD5', base64(OpenSSL::Digest::MD5.digest(value)))
536
- end
537
-
538
- # @param [String] prefix
539
- # @see #server_side_encryption_customer_key
540
- # @return [self]
541
- def server_side_encryption_customer_key_starts_with(prefix)
542
- field_name = 'x-amz-server-side-encryption-customer-key'
543
- starts_with(field_name, prefix)
544
- end
545
-
546
- # @!endgroup
547
-
548
- private
549
-
550
- def with(field_name, value)
551
- fvar = '${filename}'
552
- if index = value.rindex(fvar)
553
- if index + fvar.size == value.size
554
- @fields[field_name] = value
555
- starts_with(field_name, value[0,index])
556
- else
557
- msg = "${filename} only supported at the end of #{field_name}"
558
- raise ArgumentError, msg
559
- end
560
- else
561
- @fields[field_name] = value.to_s
562
- @conditions << { field_name => value.to_s }
563
- end
564
- self
565
- end
566
-
567
- def starts_with(field_name, value, &block)
568
- @conditions << ['starts-with', "$#{field_name}", value.to_s]
569
- self
570
- end
571
-
572
- def check_required_values!
573
- unless @key_set
574
- msg = "key required; you must provide a key via :key, "
575
- msg << ":key_starts_with, or :allow_any => ['key']"
576
- raise msg
577
- end
578
- end
579
-
580
- def bucket_url
581
- url = EndpointProvider.resolve(@bucket_region, 's3')
582
- url = URI.parse(url)
583
- if Plugins::S3BucketDns.dns_compatible?(@bucket_name, true)
584
- url.host = @bucket_name + '.' + url.host
585
- else
586
- url.path = '/' + @bucket_name
587
- end
588
- if @bucket_region == 'us-east-1'
589
- # keep legacy behavior by default
590
- url.host = Plugins::S3IADRegionalEndpoint.legacy_host(url.host)
591
- end
592
- url.to_s
593
- end
594
-
595
- # @return [Hash]
596
- def policy(datetime)
597
- check_required_values!
598
- policy = {}
599
- policy['expiration'] = @signature_expiration.utc.iso8601
600
- policy['conditions'] = @conditions.dup
601
- signature_fields(datetime).each do |name, value|
602
- policy['conditions'] << { name => value }
603
- end
604
- base64(Json.dump(policy))
605
- end
606
-
607
- def signature_fields(datetime)
608
- fields = {}
609
- fields['x-amz-credential'] = credential_scope(datetime)
610
- fields['x-amz-algorithm'] = 'AWS4-HMAC-SHA256'
611
- fields['x-amz-date'] = datetime
612
- if session_token = @credentials.session_token
613
- fields['x-amz-security-token'] = session_token
614
- end
615
- fields
616
- end
617
-
618
- def signature(datetime, string_to_sign)
619
- k_secret = @credentials.secret_access_key
620
- k_date = hmac("AWS4" + k_secret, datetime[0,8])
621
- k_region = hmac(k_date, @bucket_region)
622
- k_service = hmac(k_region, 's3')
623
- k_credentials = hmac(k_service, 'aws4_request')
624
- hexhmac(k_credentials, string_to_sign)
625
- end
626
-
627
- def hmac(key, value)
628
- OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
629
- end
630
-
631
- def hexhmac(key, value)
632
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
633
- end
634
-
635
- def credential_scope(datetime)
636
- parts = []
637
- parts << @credentials.access_key_id
638
- parts << datetime[0,8]
639
- parts << @bucket_region
640
- parts << 's3'
641
- parts << 'aws4_request'
642
- parts.join('/')
643
- end
644
-
645
- def base64(str)
646
- Base64.strict_encode64(str)
647
- end
648
-
649
- end
650
- end
651
- end