mss-sdk 1.0.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 (131) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +192 -0
  5. data/bin/mss-rb +178 -0
  6. data/ca-bundle.crt +3554 -0
  7. data/lib/mss/core/async_handle.rb +89 -0
  8. data/lib/mss/core/cacheable.rb +76 -0
  9. data/lib/mss/core/client.rb +786 -0
  10. data/lib/mss/core/collection/simple.rb +81 -0
  11. data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
  12. data/lib/mss/core/collection/with_next_token.rb +96 -0
  13. data/lib/mss/core/collection.rb +262 -0
  14. data/lib/mss/core/configuration.rb +527 -0
  15. data/lib/mss/core/credential_providers.rb +653 -0
  16. data/lib/mss/core/data.rb +251 -0
  17. data/lib/mss/core/deprecations.rb +83 -0
  18. data/lib/mss/core/endpoints.rb +36 -0
  19. data/lib/mss/core/http/connection_pool.rb +374 -0
  20. data/lib/mss/core/http/curb_handler.rb +150 -0
  21. data/lib/mss/core/http/handler.rb +88 -0
  22. data/lib/mss/core/http/net_http_handler.rb +144 -0
  23. data/lib/mss/core/http/patch.rb +98 -0
  24. data/lib/mss/core/http/request.rb +258 -0
  25. data/lib/mss/core/http/response.rb +80 -0
  26. data/lib/mss/core/indifferent_hash.rb +87 -0
  27. data/lib/mss/core/inflection.rb +55 -0
  28. data/lib/mss/core/ini_parser.rb +41 -0
  29. data/lib/mss/core/json_client.rb +46 -0
  30. data/lib/mss/core/json_parser.rb +75 -0
  31. data/lib/mss/core/json_request_builder.rb +34 -0
  32. data/lib/mss/core/json_response_parser.rb +78 -0
  33. data/lib/mss/core/lazy_error_classes.rb +107 -0
  34. data/lib/mss/core/log_formatter.rb +426 -0
  35. data/lib/mss/core/managed_file.rb +31 -0
  36. data/lib/mss/core/meta_utils.rb +44 -0
  37. data/lib/mss/core/model.rb +61 -0
  38. data/lib/mss/core/naming.rb +29 -0
  39. data/lib/mss/core/option_grammar.rb +737 -0
  40. data/lib/mss/core/options/json_serializer.rb +81 -0
  41. data/lib/mss/core/options/validator.rb +154 -0
  42. data/lib/mss/core/options/xml_serializer.rb +117 -0
  43. data/lib/mss/core/page_result.rb +74 -0
  44. data/lib/mss/core/policy.rb +938 -0
  45. data/lib/mss/core/query_client.rb +40 -0
  46. data/lib/mss/core/query_error_parser.rb +23 -0
  47. data/lib/mss/core/query_request_builder.rb +46 -0
  48. data/lib/mss/core/query_response_parser.rb +34 -0
  49. data/lib/mss/core/region.rb +84 -0
  50. data/lib/mss/core/region_collection.rb +79 -0
  51. data/lib/mss/core/resource.rb +412 -0
  52. data/lib/mss/core/resource_cache.rb +39 -0
  53. data/lib/mss/core/response.rb +214 -0
  54. data/lib/mss/core/response_cache.rb +49 -0
  55. data/lib/mss/core/rest_error_parser.rb +23 -0
  56. data/lib/mss/core/rest_json_client.rb +39 -0
  57. data/lib/mss/core/rest_request_builder.rb +153 -0
  58. data/lib/mss/core/rest_response_parser.rb +65 -0
  59. data/lib/mss/core/rest_xml_client.rb +46 -0
  60. data/lib/mss/core/service_interface.rb +82 -0
  61. data/lib/mss/core/signers/base.rb +45 -0
  62. data/lib/mss/core/signers/cloud_front.rb +55 -0
  63. data/lib/mss/core/signers/s3.rb +158 -0
  64. data/lib/mss/core/signers/version_2.rb +71 -0
  65. data/lib/mss/core/signers/version_3.rb +85 -0
  66. data/lib/mss/core/signers/version_3_https.rb +60 -0
  67. data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
  68. data/lib/mss/core/signers/version_4.rb +227 -0
  69. data/lib/mss/core/uri_escape.rb +43 -0
  70. data/lib/mss/core/xml/frame.rb +245 -0
  71. data/lib/mss/core/xml/frame_stack.rb +84 -0
  72. data/lib/mss/core/xml/grammar.rb +306 -0
  73. data/lib/mss/core/xml/parser.rb +69 -0
  74. data/lib/mss/core/xml/root_frame.rb +64 -0
  75. data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
  76. data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
  77. data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
  78. data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
  79. data/lib/mss/core/xml/stub.rb +122 -0
  80. data/lib/mss/core.rb +602 -0
  81. data/lib/mss/errors.rb +161 -0
  82. data/lib/mss/rails.rb +194 -0
  83. data/lib/mss/s3/access_control_list.rb +262 -0
  84. data/lib/mss/s3/acl_object.rb +263 -0
  85. data/lib/mss/s3/acl_options.rb +200 -0
  86. data/lib/mss/s3/bucket.rb +757 -0
  87. data/lib/mss/s3/bucket_collection.rb +161 -0
  88. data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
  89. data/lib/mss/s3/bucket_region_cache.rb +51 -0
  90. data/lib/mss/s3/bucket_tag_collection.rb +110 -0
  91. data/lib/mss/s3/bucket_version_collection.rb +78 -0
  92. data/lib/mss/s3/cipher_io.rb +119 -0
  93. data/lib/mss/s3/client/xml.rb +265 -0
  94. data/lib/mss/s3/client.rb +2076 -0
  95. data/lib/mss/s3/config.rb +60 -0
  96. data/lib/mss/s3/cors_rule.rb +107 -0
  97. data/lib/mss/s3/cors_rule_collection.rb +193 -0
  98. data/lib/mss/s3/data_options.rb +190 -0
  99. data/lib/mss/s3/encryption_utils.rb +145 -0
  100. data/lib/mss/s3/errors.rb +93 -0
  101. data/lib/mss/s3/multipart_upload.rb +353 -0
  102. data/lib/mss/s3/multipart_upload_collection.rb +75 -0
  103. data/lib/mss/s3/object_collection.rb +355 -0
  104. data/lib/mss/s3/object_metadata.rb +102 -0
  105. data/lib/mss/s3/object_upload_collection.rb +76 -0
  106. data/lib/mss/s3/object_version.rb +153 -0
  107. data/lib/mss/s3/object_version_collection.rb +88 -0
  108. data/lib/mss/s3/paginated_collection.rb +74 -0
  109. data/lib/mss/s3/policy.rb +73 -0
  110. data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
  111. data/lib/mss/s3/prefixed_collection.rb +84 -0
  112. data/lib/mss/s3/presign_v4.rb +135 -0
  113. data/lib/mss/s3/presigned_post.rb +574 -0
  114. data/lib/mss/s3/region_detection.rb +75 -0
  115. data/lib/mss/s3/request.rb +61 -0
  116. data/lib/mss/s3/s3_object.rb +1795 -0
  117. data/lib/mss/s3/tree/branch_node.rb +67 -0
  118. data/lib/mss/s3/tree/child_collection.rb +103 -0
  119. data/lib/mss/s3/tree/leaf_node.rb +93 -0
  120. data/lib/mss/s3/tree/node.rb +21 -0
  121. data/lib/mss/s3/tree/parent.rb +86 -0
  122. data/lib/mss/s3/tree.rb +115 -0
  123. data/lib/mss/s3/uploaded_part.rb +81 -0
  124. data/lib/mss/s3/uploaded_part_collection.rb +83 -0
  125. data/lib/mss/s3/website_configuration.rb +101 -0
  126. data/lib/mss/s3.rb +161 -0
  127. data/lib/mss/version.rb +16 -0
  128. data/lib/mss-sdk.rb +2 -0
  129. data/lib/mss.rb +14 -0
  130. data/rails/init.rb +14 -0
  131. metadata +201 -0
@@ -0,0 +1,574 @@
1
+ # Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ require 'uri'
14
+ require 'time'
15
+
16
+ module MSS
17
+ class S3
18
+
19
+ # Helper to generate form fields for presigned POST requests to
20
+ # a bucket. You can use this to create a form that can be used
21
+ # from a web browser to upload objects to S3 while specifying
22
+ # conditions on what can be uploaded and how it is processed and
23
+ # stored.
24
+ #
25
+ # @example Form fields for uploading by file name
26
+ #
27
+ # form = bucket.presigned_post(:key => "photos/${filename}")
28
+ # form.url.to_s # => "https://mybucket.s3.amazonmss.com/"
29
+ # form.fields # => { "AWSAccessKeyId" => "...", ... }
30
+ #
31
+ # @example Generating a minimal HTML form
32
+ #
33
+ # form = bucket.objects.myobj.presigned_post
34
+ # hidden_inputs = form.fields.map do |(name, value)|
35
+ # %(<input type="hidden" name="#{name}" value="#{value}" />)
36
+ # end
37
+ # <<-END
38
+ # <form action="#{form.url}" method="post" enctype="multipart/form-data">
39
+ # #{hidden_inputs}
40
+ # <input type="file" name="file" />
41
+ # </form>
42
+ # END
43
+ #
44
+ # @example Restricting the size of the uploaded object
45
+ #
46
+ # bucket.presigned_post(:content_length => 1..(10*1024))
47
+ #
48
+ # @example Restricting the key prefix
49
+ #
50
+ # bucket.presigned_post.where(:key).starts_with("photos/")
51
+ #
52
+ class PresignedPost
53
+
54
+ include Core::Model
55
+
56
+ # @return [Bucket] The bucket to which data can be uploaded
57
+ # using the form fields
58
+ attr_reader :bucket
59
+
60
+ # @return [String] The key of the object that will be
61
+ # uploaded. If this is nil, then the object can be uploaded
62
+ # with any key that satisfies the conditions specified for
63
+ # the upload (see {#where}).
64
+ attr_reader :key
65
+
66
+ # @return [Hash] A hash of the metadata fields included in the
67
+ # signed fields. Additional metadata fields may be provided
68
+ # with the upload as long as they satisfy the conditions
69
+ # specified for the upload (see {#where}).
70
+ attr_reader :metadata
71
+
72
+ # @return [Range] The range of acceptable object sizes for the
73
+ # upload. By default any size object may be uploaded.
74
+ attr_reader :content_length
75
+
76
+ # @api private
77
+ attr_reader :conditions
78
+
79
+ # @return [Array<String>] Additional fields which may be sent
80
+ # with the upload. These will be included in the policy so
81
+ # that they can be sent with any value. S3 will ignore
82
+ # them.
83
+ attr_reader :ignored_fields
84
+
85
+ # @return The expiration time for the signature. By default
86
+ # the signature will expire an hour after it is generated.
87
+ attr_reader :expires
88
+
89
+ attr_reader :callback_url
90
+ attr_reader :callback_body
91
+ attr_reader :callback_body_type
92
+ attr_reader :callback_host
93
+
94
+ # @api private
95
+ SPECIAL_FIELDS = [:key,
96
+ :policy,
97
+ :signature,
98
+ :expires,
99
+ :metadata,
100
+ :content_length,
101
+ :conditions,
102
+ :ignore,
103
+ :secure,
104
+ :callback_url,
105
+ :callback_body,
106
+ :callback_body_type,
107
+ :callback_host]
108
+
109
+ # Creates a new presigned post object.
110
+ #
111
+ # @param [Bucket] bucket The bucket to which data can be uploaded
112
+ # using the form fields.
113
+ #
114
+ # @param [Hash] opts Additional options for the upload. Aside
115
+ # from `:secure`, `:expires`, `:content_length` and `:ignore`
116
+ # the values provided here will be stored in the hash returned
117
+ # from the {#fields} method, and the policy in that hash will
118
+ # restrict their values to the values provided. If you
119
+ # instead want to only restrict the values and not provide
120
+ # them -- for example, if your application generates separate
121
+ # form fields for those values -- you should use the {#where}
122
+ # method on the returned object instead of providing the
123
+ # values here.
124
+ #
125
+ # @option opts [String] :key The key of the object that will
126
+ # be uploaded. If this is nil, then the object can be
127
+ # uploaded with any key that satisfies the conditions
128
+ # specified for the upload (see {#where}).
129
+ #
130
+ # @option opts [Boolean] :secure By setting this to false, you
131
+ # can cause {#url} to return an HTTP URL. By default it
132
+ # returns an HTTPS URL.
133
+ #
134
+ # @option opts [Time, DateTime, Integer, String] :expires The
135
+ # time at which the signature will expire. By default the
136
+ # signature will expire one hour after it is generated
137
+ # (e.g. when {#fields} is called).
138
+ #
139
+ # When the value is a Time or DateTime, the signature
140
+ # expires at the specified time. When it is an integer, the
141
+ # signature expires the specified number of seconds after it
142
+ # is generated. When it is a string, the string is parsed
143
+ # as a time (using Time.parse) and the signature expires at
144
+ # that time.
145
+ #
146
+ # @option opts [String] :cache_control Sets the Cache-Control
147
+ # header stored with the object.
148
+ #
149
+ # @option opts [String] :content_type Sets the Content-Type
150
+ # header stored with the object.
151
+ #
152
+ # @option opts [String] :content_disposition Sets the
153
+ # Content-Disposition header stored with the object.
154
+ #
155
+ # @option opts [String] :expires_header Sets the Expires
156
+ # header stored with the object.
157
+ #
158
+ # @option options [Symbol] :acl A canned access control
159
+ # policy. Valid values are:
160
+ # * `:private`
161
+ # * `:public_read`
162
+ # * `:public_read_write`
163
+ # * `:authenticated_read`
164
+ # * `:bucket_owner_read`
165
+ # * `:bucket_owner_full_control`
166
+ #
167
+ # @option options [Symbol] :server_side_encryption (nil) If this
168
+ # option is set, the object will be stored using server side
169
+ # encryption. The only valid value is `:aes256`, which
170
+ # specifies that the object should be stored using the AES
171
+ # encryption algorithm with 256 bit keys. By default, this
172
+ # option uses the value of the `:s3_server_side_encryption`
173
+ # option in the current configuration; for more information,
174
+ # see {MSS.config}.
175
+ #
176
+ # @option opts [String] :success_action_redirect The URL to
177
+ # which the client is redirected upon successful upload.
178
+ #
179
+ # @option opts [Integer] :success_action_status The status
180
+ # code returned to the client upon successful upload if
181
+ # `:success_action_redirect` is not specified. Accepts the
182
+ # values 200, 201, or 204 (default).
183
+ #
184
+ # If the value is set to 200 or 204, Amazon S3 returns an
185
+ # empty document with a 200 or 204 status code.
186
+ #
187
+ # If the value is set to 201, Amazon S3 returns an XML
188
+ # document with a 201 status code. For information on the
189
+ # content of the XML document, see
190
+ #
191
+ # @option opts [Hash] :metadata A hash of the metadata fields
192
+ # included in the signed fields. Additional metadata fields
193
+ # may be provided with the upload as long as they satisfy
194
+ # the conditions specified for the upload (see {#where}).
195
+ #
196
+ # @option opts [Integer, Range] :content_length The range of
197
+ # acceptable object sizes for the upload. By default any
198
+ # size object may be uploaded.
199
+ #
200
+ # @option opts [Array<String>] :ignore Additional fields which
201
+ # may be sent with the upload. These will be included in
202
+ # the policy so that they can be sent with any value. S3
203
+ # will ignore them.
204
+ def initialize(bucket, opts = {})
205
+ @bucket = bucket
206
+ @key = opts[:key]
207
+ @secure = (opts[:secure] != false)
208
+ @fields = {}
209
+ # TODO normalize all values to @fields
210
+ opts.each do |opt_key, opt_val|
211
+ @fields[opt_key] = opt_val unless SPECIAL_FIELDS.include? opt_key
212
+ end
213
+ @metadata = opts[:metadata] || {}
214
+ @content_length = range_value(opts[:content_length])
215
+ @conditions = opts[:conditions] || {}
216
+ @ignored_fields = [opts[:ignore]].flatten.compact
217
+ @expires = opts[:expires]
218
+ @callback_url = opts[:callback_url]
219
+ @callback_body = opts[:callback_body]
220
+ @callback_body_type = opts[:callback_body_type]
221
+ @callback_host = opts[:callback_host]
222
+
223
+ super
224
+
225
+ @fields[:server_side_encryption] =
226
+ config.s3_server_side_encryption unless
227
+ @fields.key?(:server_side_encryption)
228
+ @fields.delete(:server_side_encryption) if
229
+ @fields[:server_side_encryption].nil?
230
+ end
231
+
232
+ # @return [Boolean] True if {#url} generates an HTTPS url.
233
+ def secure?
234
+ @secure
235
+ end
236
+
237
+ # @return [URI::HTTP, URI::HTTPS] The URL to which the form
238
+ # fields should be POSTed. If you are using the fields in
239
+ # an HTML form, this is the URL to put in the `action`
240
+ # attribute of the form tag.
241
+ def url
242
+ req = Request.new
243
+ req.bucket = bucket.name
244
+ req.host = config.s3_endpoint
245
+ req.force_path_style = config.s3_force_path_style
246
+ build_uri(req)
247
+ end
248
+
249
+ # Lets you specify conditions on a field. See
250
+ # {PresignedPost#where} for usage examples.
251
+ class ConditionBuilder
252
+
253
+ # @api private
254
+ def initialize(post, field)
255
+ @post = post
256
+ @field = field
257
+ end
258
+
259
+ # Specifies that the value of the field must equal the
260
+ # provided value.
261
+ def is(value)
262
+ if @field == :content_length
263
+ self.in(value)
264
+ else
265
+ @post.with_equality_condition(@field, value)
266
+ end
267
+ end
268
+
269
+ # Specifies that the value of the field must begin with the
270
+ # provided value. If you are specifying a condition on the
271
+ # "key" field, note that this check takes place after the
272
+ # `${filename}` variable is expanded. This is only valid
273
+ # for the following fields:
274
+ #
275
+ # * `:key`
276
+ # * `:cache_control`
277
+ # * `:content_type`
278
+ # * `:content_disposition`
279
+ # * `:content_encoding`
280
+ # * `:expires_header`
281
+ # * `:acl`
282
+ # * `:success_action_redirect`
283
+ # * metadata fields (see {#where_metadata})
284
+ def starts_with(prefix)
285
+ @post.with_prefix_condition(@field, prefix)
286
+ end
287
+
288
+ # Specifies that the value of the field must be in the given
289
+ # range. This may only be used to constrain the
290
+ # `:content_length` field,
291
+ # e.g. `presigned_post.with(:conent_length).in(1..4)`.
292
+ def in(range)
293
+ @post.refine(:content_length => range)
294
+ end
295
+
296
+ end
297
+
298
+ # Adds a condition to the policy for the POST. Use
299
+ # {#where_metadata} to add metadata conditions.
300
+ #
301
+ # @example Restricting the ACL to "bucket-owner" ACLs
302
+ # presigned_post.where(:acl).starts_with("bucket-owner")
303
+ #
304
+ # @param [Symbol] field The field for which a condition should
305
+ # be added. In addition to any arbitrary values you have set,
306
+ # the following values are also permitted:
307
+ #
308
+ # * `:key`
309
+ # * `:content_length`
310
+ # * `:cache_control`
311
+ # * `:content_type`
312
+ # * `:content_disposition`
313
+ # * `:content_encoding`
314
+ # * `:expires_header`
315
+ # * `:acl`
316
+ # * `:success_action_redirect`
317
+ # * `:success_action_status`
318
+ #
319
+ # @return [ConditionBuilder] An object that allows you to
320
+ # specify a condition on the field.
321
+ def where(field)
322
+ ConditionBuilder.new(self, field)
323
+ end
324
+
325
+ # Adds a condition to the policy for the POST to constrain the
326
+ # values of metadata fields uploaded with the object. If a
327
+ # metadata field does not have a condition associated with it
328
+ # and is not specified in the constructor (see {#metadata})
329
+ # then S3 will reject it.
330
+ #
331
+ # @param [Symbol, String] field The name of the metadata
332
+ # attribute. For example, `:color` corresponds to the
333
+ # "x-amz-meta-color" field in the POST body.
334
+ #
335
+ # @return [ConditionBuilder] An object that allows you to
336
+ # specify a condition on the metadata attribute.
337
+ # def where_metadata(field)
338
+ # where("x-amz-meta-#{field}")
339
+ # end
340
+
341
+ # @return [String] The Base64-encoded JSON policy document.
342
+ def policy
343
+ json = {
344
+ "expiration" => format_expiration,
345
+ "conditions" => generate_conditions,
346
+ "callbackUrl" => callback_url,
347
+ "callbackBody" => callback_body,
348
+ "callbackBodyType" => callback_body_type,
349
+ "callbackHost" => callback_host
350
+ }.to_json
351
+ Base64.encode64(json).tr("\n","")
352
+ end
353
+
354
+ # @return [Hash] A collection of form fields (including a
355
+ # signature and a policy) that can be used to POST data to
356
+ # S3. Additional form fields may be added after the fact as
357
+ # long as they are described by a policy condition (see
358
+ # {#where}).
359
+ def fields
360
+
361
+ secret = config.credential_provider.secret_access_key
362
+ signature = Core::Signers::Base.sign(secret, policy, 'sha1')
363
+
364
+ fields = {
365
+ "AWSAccessKeyId" => config.credential_provider.access_key_id,
366
+ "key" => key,
367
+ "bucket" => bucket.name,
368
+ "policy" => policy,
369
+ "signature" => signature
370
+ }.merge(optional_fields)
371
+
372
+ if token = config.credential_provider.session_token
373
+ fields["x-amz-security-token"] = token
374
+ end
375
+
376
+ fields.merge(optional_fields)
377
+
378
+ end
379
+
380
+ # @api private
381
+ def with_equality_condition(option_name, value)
382
+ field_name = field_name(option_name)
383
+ with_condition(option_name, Hash[[[field_name, value]]])
384
+ end
385
+
386
+ # @api private
387
+ def with_prefix_condition(option_name, prefix)
388
+ field_name = field_name(option_name)
389
+ with_condition(option_name,
390
+ ["starts-with", "$#{field_name}", prefix])
391
+ end
392
+
393
+ # @api private
394
+ def refine(opts)
395
+ self.class.new(bucket, {
396
+ :conditions => conditions,
397
+ :key => key,
398
+ :metadata => metadata,
399
+ :secure => secure?,
400
+ :content_length => content_length,
401
+ :expires => expires,
402
+ :ignore => ignored_fields
403
+ }.merge(@fields).
404
+ merge(opts))
405
+ end
406
+
407
+ # @api private
408
+ private
409
+ def with_condition(field, condition)
410
+ conditions = self.conditions.dup
411
+ (conditions[field] ||= []) << condition
412
+ refine(:conditions => conditions)
413
+ end
414
+
415
+ # @api private
416
+ private
417
+ def format_expiration
418
+ time = expires || Time.now.utc + 60*60
419
+ time =
420
+ case time
421
+ when Time
422
+ time
423
+ when DateTime
424
+ Time.parse(time.to_s)
425
+ when Integer
426
+ (Time.now + time)
427
+ when String
428
+ Time.parse(time)
429
+ end
430
+ time.utc.iso8601
431
+ end
432
+
433
+ # @api private
434
+ private
435
+ def range_value(range)
436
+ case range
437
+ when Integer
438
+ range..range
439
+ when Range
440
+ range
441
+ end
442
+ end
443
+
444
+ # @api private
445
+ private
446
+ def split_range(range)
447
+ range = range_value(range)
448
+ [range.begin,
449
+ (range.exclude_end? ?
450
+ range.end-1 :
451
+ range.end)]
452
+ end
453
+
454
+ # @api private
455
+ private
456
+ def optional_fields
457
+ fields = @fields.keys.inject({}) do |fields, option_name|
458
+ fields[field_name(option_name)] =
459
+ field_value(option_name)
460
+ fields
461
+ end
462
+
463
+ @metadata.each do |key, value|
464
+ # fields["x-amz-meta-#{key}"] = value.to_s
465
+ fields["#{key}"] = value.to_s
466
+ end
467
+
468
+ fields
469
+ end
470
+
471
+ # @api private
472
+ private
473
+ def field_name(option_name)
474
+ case option_name
475
+ when :expires_header
476
+ "Expires"
477
+ when :server_side_encryption
478
+ "x-amz-server-side-encryption"
479
+ when :key, "Key", :policy, "Policy", :bucket, "Bucket"
480
+ option_name.to_s.downcase
481
+ when :acl, :success_action_redirect, :success_action_status
482
+ option_name.to_s
483
+ else
484
+ # e.g. Cache-Control from cache_control
485
+ field_name = option_name.to_s.tr("_", "-").
486
+ gsub(/-(.)/) { |m| m.upcase }
487
+ field_name[0,1] = field_name[0,1].upcase
488
+ field_name
489
+ end
490
+ end
491
+
492
+ # @api private
493
+ private
494
+ def field_value(option_name)
495
+ case option_name
496
+ when :acl
497
+ @fields[:acl].to_s.tr("_", "-")
498
+ when :server_side_encryption
499
+ value = @fields[:server_side_encryption]
500
+ if value.kind_of?(Symbol)
501
+ value.to_s.upcase
502
+ else
503
+ value.to_s
504
+ end
505
+ else
506
+ @fields[option_name].to_s
507
+ end
508
+ end
509
+
510
+ # @api private
511
+ private
512
+ def generate_conditions
513
+
514
+ conditions = self.conditions.inject([]) do |list, (field, field_conds)|
515
+ list + field_conds
516
+ end
517
+
518
+ conditions << { "bucket" => bucket.name }
519
+ conditions += key_conditions
520
+ conditions += optional_fields.map { |(n, v)| Hash[[[n, v]]] }
521
+ conditions += range_conditions
522
+ conditions += ignored_conditions
523
+
524
+ if token = config.credential_provider.session_token
525
+ conditions << { "x-amz-security-token" => token }
526
+ end
527
+
528
+ conditions
529
+
530
+ end
531
+
532
+ # @api private
533
+ private
534
+ def ignored_conditions
535
+ ignored_fields.map do |field|
536
+ ["starts-with", "$#{field}", ""]
537
+ end
538
+ end
539
+
540
+ # @api private
541
+ private
542
+ def range_conditions
543
+ if content_length
544
+ [["content-length-range", *split_range(content_length)]]
545
+ else
546
+ []
547
+ end
548
+ end
549
+
550
+ # @api private
551
+ private
552
+ def key_conditions
553
+ [if key && key.include?("${filename}")
554
+ ["starts-with", "$key", key[/^(.*)\$\{filename\}/, 1]]
555
+ elsif key
556
+ { "key" => key }
557
+ else
558
+ ["starts-with", "$key", ""]
559
+ end]
560
+ end
561
+
562
+ # @api private
563
+ private
564
+ def build_uri(request)
565
+ uri_class = secure? ? URI::HTTPS : URI::HTTP
566
+ uri_class.build(:host => request.host,
567
+ :path => request.path,
568
+ :query => request.querystring)
569
+ end
570
+
571
+ end
572
+
573
+ end
574
+ end
@@ -0,0 +1,75 @@
1
+ require 'cgi'
2
+
3
+ module MSS
4
+ class S3
5
+ # @api private
6
+ module RegionDetection
7
+
8
+ private
9
+
10
+ def retry_server_errors(&block)
11
+ response = super
12
+ if requires_sigv4?(response)
13
+ detect_region_and_retry(response, &block)
14
+ else
15
+ response
16
+ end
17
+ end
18
+
19
+ def requires_sigv4?(resp)
20
+ resp.http_response.status == 400 &&
21
+ resp.http_response.body &&
22
+ resp.http_response.body.include?('Please use MSS4-HMAC-SHA256')
23
+ end
24
+
25
+ def detect_region_and_retry(response, &retry_block)
26
+ updgrade_to_v4(response, 'us-east-1')
27
+ yield
28
+ return if response.http_response.status == 200
29
+ actual_region = region_from_location_header(response)
30
+ updgrade_to_v4(response, actual_region)
31
+ log_region_warning(response, actual_region)
32
+ yield
33
+ end
34
+
35
+ def updgrade_to_v4(response, region)
36
+ bucket = response.request_options[:bucket_name]
37
+ if response.http_request.body_stream.respond_to?(:rewind)
38
+ response.http_request.body_stream.rewind
39
+ end
40
+ response.http_request.headers.delete('authorization')
41
+ response.http_request.headers.delete('x-amz-security-token')
42
+ response.http_request.host = new_hostname(response, region)
43
+ new_v4_signer(region).sign_request(response.http_request)
44
+ end
45
+
46
+ def region_from_location_header(response)
47
+ location = response.http_response.headers['location'].first
48
+ location.match(/s3\.(.+?)\.amazonmss\.com/)[1]
49
+ end
50
+
51
+ def new_v4_signer(region)
52
+ Core::Signers::Version4.new(credential_provider, 's3', region)
53
+ end
54
+
55
+ def new_hostname(response, region)
56
+ bucket = response.request_options[:bucket_name]
57
+ if region == 'us-east-1'
58
+ 's3-external-1.amazonmss.com'
59
+ else
60
+ "s3.#{region}.amazonmss.com"
61
+ end
62
+ end
63
+
64
+ def log_region_warning(response, actual_region)
65
+ bucket_name = response.request_options[:bucket_name]
66
+ S3::BUCKET_REGIONS[bucket_name] = actual_region
67
+ log_warning("S3 client configured for #{@region.inspect} " +
68
+ "but the bucket #{bucket_name.inspect} is in" +
69
+ "#{actual_region.inspect}; Please configure the proper region " +
70
+ "to avoid multiple unecessary redirects and signing attempts\n")
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ require 'uri'
14
+ require 'time'
15
+
16
+ module MSS
17
+ class S3
18
+
19
+ # @api private
20
+ class Request < Core::Http::Request
21
+
22
+ include Core::UriEscape
23
+
24
+ # @return [bucket] S3 bucket name
25
+ attr_accessor :bucket
26
+
27
+ # @return [String] S3 object key
28
+ attr_accessor :key
29
+
30
+ # @api private
31
+ attr_accessor :force_path_style
32
+
33
+ def host
34
+ path_style? ? @host : "#{bucket}.#{@host}"
35
+ end
36
+
37
+ def path_style?
38
+ if force_path_style
39
+ true
40
+ else
41
+ Client.path_style_bucket_name?(bucket)
42
+ end
43
+ end
44
+
45
+ def uri
46
+ parts = []
47
+ parts << bucket if bucket and path_style?
48
+ parts << escape_path(key) if key
49
+
50
+ path = '/' + parts.join('/')
51
+ querystring = url_encoded_params
52
+
53
+ uri = ''
54
+ uri << path
55
+ uri << "?#{querystring}" if querystring
56
+ uri
57
+ end
58
+
59
+ end
60
+ end
61
+ end