aws-sdk 1.6.2 → 1.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -16,30 +16,228 @@ require 'uri'
16
16
  module AWS
17
17
  class S3
18
18
 
19
- # Represents an object in S3 identified by a key.
19
+ # Represents an object in S3. Objects live in a bucket and have
20
+ # unique keys.
20
21
  #
21
- # object = bucket.objects["key-to-my-object"]
22
- # object.key #=> 'key-to-my-object'
22
+ # = Getting Objects
23
+ #
24
+ # You can get an object by its key.
25
+ #
26
+ # s3 = AWS::S3.new
27
+ # obj = s3.buckets['my-bucket'].objects['key'] # no request made
28
+ #
29
+ # You can also get objects by enumerating a objects in a bucket.
30
+ #
31
+ # bucket.objects.each do |obj|
32
+ # puts obj.key
33
+ # end
23
34
  #
24
35
  # See {ObjectCollection} for more information on finding objects.
25
- #
26
- # == Writing and Reading S3Objects
36
+ #
37
+ # = Creating Objects
38
+ #
39
+ # You create an object by writing to it. The following two
40
+ # expressions are equivalent.
41
+ #
42
+ # obj = bucket.objects.create('key', 'data')
43
+ # obj = bucket.objects['key'].write('data')
44
+ #
45
+ # = Writing Objects
46
+ #
47
+ # To upload data to S3, you simply need to call {#write} on an object.
48
+ #
49
+ # obj.write('Hello World!')
50
+ # obj.read
51
+ # #=> 'Hello World!'
52
+ #
53
+ # == Uploading Files
54
+ #
55
+ # You can upload a file to S3 in a variety of ways. Given a path
56
+ # to a file (as a string) you can do any of the following:
57
+ #
58
+ # # specify the data as a path to a file
59
+ # obj.write(Pathname.new(path_to_file))
60
+ #
61
+ # # also works this way
62
+ # obj.write(:file => path_to_file)
63
+ #
64
+ # # Also accepts an open file object
65
+ # file = File.open(path_to_file, 'r')
66
+ # obj.write(file)
67
+ #
68
+ # All three examples above produce the same result. The file
69
+ # will be streamed to S3 in chunks. It will not be loaded
70
+ # entirely into memory.
71
+ #
72
+ # == Streaming Uploads
73
+ #
74
+ # When you call {#write} with any IO-like object (must respond to
75
+ # #read and #eof?), it will be streamed to S3 in chunks.
76
+ #
77
+ # While it is possible to determine the size of many IO objects, you may
78
+ # have to specify the :content_length of your IO object.
79
+ # If the exact size can not be known, you may provide an
80
+ # +:estimated_content_length+. Depending on the size (actual or
81
+ # estimated) of your data, it will be uploaded in a single request or
82
+ # in multiple requests via {#multipart_upload}.
83
+ #
84
+ # You may also stream uploads to S3 using a block:
85
+ #
86
+ # obj.write do |buffer, bytes|
87
+ # # writing fewer than the requested number of bytes to the buffer
88
+ # # will cause write to stop yielding to the block
89
+ # end
90
+ #
91
+ # = Reading Objects
92
+ #
93
+ # You can read an object directly using {#read}. Be warned, this will
94
+ # load the entire object into memory and is not recommended for large
95
+ # objects.
96
+ #
97
+ # obj.write('abc')
98
+ # puts obj.read
99
+ # #=> abc
100
+ #
101
+ # == Streaming Downloads
102
+ #
103
+ # If you want to stream an object from S3, you can pass a block
104
+ # to {#read}.
105
+ #
106
+ # File.open('output', 'w') do |file|
107
+ # large_object.read do |chunk|
108
+ # file.write(chunk)
109
+ # end
110
+ # end
111
+ #
112
+ # = Encryption
113
+ #
114
+ # Amazon S3 can encrypt objects for you service-side. You can also
115
+ # use client-side encryption.
116
+ #
117
+ # == Server Side Encryption
118
+ #
119
+ # Amazon S3 provides server side encryption for an additional cost.
120
+ # You can specify to use server side encryption when writing an object.
121
+ #
122
+ # obj.write('data', :server_size_encryption => :aes256)
123
+ #
124
+ # You can also make this the default behavior.
125
+ #
126
+ # AWS.config(:s3_server_side_encryption => :aes256)
127
+ #
128
+ # s3 = AWS::S3.new
129
+ # s3.buckets['name'].objects['key'].write('abc') # will be encrypted
130
+ #
131
+ # == Client Side Encryption
132
+ #
133
+ # Client side encryption utilizes envelope encryption, so that your keys are
134
+ # never sent to S3. You can use a symetric key or an asymmetric
135
+ # key pair.
136
+ #
137
+ # === Symmetric Key Encryption
138
+ #
139
+ # An AES key is used for symmetric encryption. The key can be 128, 192,
140
+ # and 256 bit sizes. Start by generating key or read a previously
141
+ # generated key.
142
+ #
143
+ # # generate a new random key
144
+ # my_key = OpenSSL::Cipher.new("AES-256-ECB").random_key
145
+ #
146
+ # # read an existing key from disk
147
+ # my_key = File.read("my_key.der")
148
+ #
149
+ # Now you can encrypt locally and upload the encrypted data to S3.
150
+ # To do this, you need to provide your key.
27
151
  #
28
152
  # obj = bucket.objects["my-text-object"]
29
153
  #
30
- # obj.write("MY TEXT")
154
+ # # encrypt then upload data
155
+ # obj.write("MY TEXT", :encryption_key => my_key)
156
+ #
157
+ # # try read the object without decrypting, oops
31
158
  # obj.read
159
+ # #=> '.....'
160
+ #
161
+ # Lastly, you can download and decrypt by providing the same key.
162
+ #
163
+ # obj.read(:encryption_key => my_key)
32
164
  # #=> "MY TEXT"
33
165
  #
34
- # obj.write(File.new("README.txt"))
35
- # obj.read
36
- # # should equal File.read("README.txt")
166
+ # === Asymmetric Key Pair
167
+ #
168
+ # A RSA key pair is used for asymmetric encryption. The public key is used
169
+ # for encryption and the private key is used for decryption. Start
170
+ # by generating a key.
171
+ #
172
+ # my_key = OpenSSL::PKey::RSA.new(1024)
173
+ #
174
+ # Provide your key to #write and the data will be encrypted before it
175
+ # is uploaded. Pass the same key to #read to decrypt the data
176
+ # when you download it.
177
+ #
178
+ # obj = bucket.objects["my-text-object"]
179
+ #
180
+ # # encrypt and upload the data
181
+ # obj.write("MY TEXT", :encryption_key => my_key)
182
+ #
183
+ # # download and decrypt the data
184
+ # obj.read(:encryption_key => my_key)
185
+ # #=> "MY TEXT"
186
+ #
187
+ # === Configuring storage locations
188
+ #
189
+ # By default, encryption materials are stored in the object metadata.
190
+ # If you prefer, you can store the encryption materials in a separate
191
+ # object in S3. This object will have the same key + '.instruction'.
192
+ #
193
+ # # new object, does not exist yet
194
+ # obj = bucket.objects["my-text-object"]
195
+ #
196
+ # # no instruction file present
197
+ # bucket.objects['my-text-object.instruction'].exists?
198
+ # #=> false
199
+ #
200
+ # # store the encryption materials in the instruction file
201
+ # # instead of obj#metadata
202
+ # obj.write("MY TEXT",
203
+ # :encryption_key => MY_KEY,
204
+ # :encryption_materials_location => :instruction_file)
205
+ #
206
+ # bucket.objects['my-text-object.instruction'].exists?
207
+ # #=> true
208
+ #
209
+ # If you store the encryption materials in an instruction file, you
210
+ # must tell #read this or it will fail to find your encryption materials.
211
+ #
212
+ # # reading an encrypted file whos materials are stored in an
213
+ # # instruction file, and not metadata
214
+ # obj.read(:encryption_key => MY_KEY,
215
+ # :encryption_materials_location => :instruction_file)
216
+ #
217
+ # === Configuring default behaviors
218
+ #
219
+ # You can configure the default key such that it will automatically
220
+ # encrypt and decrypt for you. You can do this globally or for a
221
+ # single S3 interface
222
+ #
223
+ # # all objects uploaded/downloaded with this s3 object will be
224
+ # # encrypted/decrypted
225
+ # s3 = AWS::S3.new(:s3_encryption_key => "MY_KEY")
226
+ #
227
+ # # set the key to always encrypt/decrypt
228
+ # AWS.config(:s3_encryption_key => "MY_KEY")
229
+ #
230
+ # You can also configure the default storage location for the encryption
231
+ # materials.
232
+ #
233
+ # AWS.config(:s3_encryption_materials_location => :instruction_file)
37
234
  #
38
235
  class S3Object
39
236
 
40
237
  include Core::Model
41
238
  include DataOptions
42
239
  include ACLOptions
240
+ include AWS::S3::EncryptionUtils
43
241
 
44
242
  # @param [Bucket] bucket The bucket this object belongs to.
45
243
  # @param [String] key The object's key.
@@ -62,12 +260,12 @@ module AWS
62
260
 
63
261
  # @return [Boolean] Returns true if the other object belongs to the
64
262
  # same bucket and has the same key.
65
- def ==(other)
263
+ def == other
66
264
  other.kind_of?(S3Object) and other.bucket == bucket and other.key == key
67
265
  end
68
-
69
266
  alias_method :eql?, :==
70
267
 
268
+ # @return [Boolean] Returns +true+ if the object exists in S3.
71
269
  def exists?
72
270
  head
73
271
  rescue Errors::NoSuchKey => e
@@ -85,20 +283,20 @@ module AWS
85
283
  # * etag (typically the object's MD5)
86
284
  # * server_side_encryption (the algorithm used to encrypt the
87
285
  # object on the server side, e.g. +:aes256+)
88
- #
286
+ #
89
287
  # @param [Hash] options
90
288
  # @option options [String] :version_id Which version of this object
91
289
  # to make a HEAD request against.
92
- # @return A head object response with metatadata,
290
+ # @return A head object response with metadata,
93
291
  # content_length, content_type, etag and server_side_encryption.
94
292
  def head options = {}
95
293
  client.head_object(options.merge(
96
294
  :bucket_name => bucket.name, :key => key))
97
295
  end
98
296
 
99
- # Returns the object's ETag.
297
+ # Returns the object's ETag.
100
298
  #
101
- # Generally the ETAG is the MD5 of the object. If the object was
299
+ # Generally the ETAG is the MD5 of the object. If the object was
102
300
  # uploaded using multipart upload then this is the MD5 all of the
103
301
  # upload-part-md5s.
104
302
  #
@@ -121,7 +319,7 @@ module AWS
121
319
 
122
320
  # @note S3 does not compute content-type. It reports the content-type
123
321
  # as was reported during the file upload.
124
- # @return [String] Returns the content type as reported by S3,
322
+ # @return [String] Returns the content type as reported by S3,
125
323
  # defaults to an empty string when not provided during upload.
126
324
  def content_type
127
325
  head.content_type
@@ -153,181 +351,205 @@ module AWS
153
351
  # Deletes the object from its S3 bucket.
154
352
  #
155
353
  # @param [Hash] options
354
+ #
156
355
  # @option [String] :version_id (nil) If present the specified version
157
356
  # of this object will be deleted. Only works for buckets that have
158
357
  # had versioning enabled.
358
+ #
359
+ # @option [Boolean] :delete_instruction_file (false) Set this to +true+
360
+ # if you use client-side encryption and the encryption materials
361
+ # were stored in a separate object in S3 (key.instruction).
362
+ #
159
363
  # @return [nil]
160
364
  def delete options = {}
161
- options[:bucket_name] = bucket.name
162
- options[:key] = key
163
- client.delete_object(options)
365
+
366
+ client.delete_object(options.merge(
367
+ :bucket_name => bucket.name,
368
+ :key => key))
369
+
370
+ if options[:delete_instruction_file]
371
+ client.delete_object(
372
+ :bucket_name => bucket.name,
373
+ :key => key + '.instruction')
374
+ end
375
+
164
376
  nil
377
+
165
378
  end
166
379
 
167
380
  # @option [String] :version_id (nil) If present the metadata object
168
381
  # will be for the specified version.
169
- # @return [ObjectMetadata] Returns an instance of ObjectMetadata
382
+ # @return [ObjectMetadata] Returns an instance of ObjectMetadata
170
383
  # representing the metadata for this object.
171
384
  def metadata options = {}
172
385
  options[:config] = config
173
386
  ObjectMetadata.new(self, options)
174
387
  end
175
388
 
176
- # Returns a colletion representing all the object versions
389
+ # Returns a collection representing all the object versions
177
390
  # for this object.
178
391
  #
179
392
  # bucket.versioning_enabled? # => true
180
393
  # version = bucket.objects["mykey"].versions.latest
181
394
  #
182
- # @return [ObjectVersionCollection]
395
+ # @return [ObjectVersionCollection]
183
396
  def versions
184
397
  ObjectVersionCollection.new(self)
185
398
  end
186
399
 
187
- # Writes data to the object in S3. This method will attempt
188
- # to intelligently choose between uploading in one request and
189
- # using {#multipart_upload}.
190
- #
191
- # Unless versioning is enabled, any data currently in S3 at {#key}
192
- # will be replaced.
400
+ # Uploads data to the object in S3.
193
401
  #
194
- # You can pass +:data+ or +:file+ as the first argument or as
195
- # options. Example usage:
402
+ # obj = s3.buckets['bucket-name'].objects['key']
196
403
  #
197
- # obj = s3.buckets.mybucket.objects.mykey
404
+ # # strings
198
405
  # obj.write("HELLO")
199
- # obj.write(:data => "HELLO")
200
- # obj.write(Pathname.new("myfile"))
201
- # obj.write(:file => "myfile")
202
- #
203
- # # writes zero-length data
204
- # obj.write(:metadata => { "avg-rating" => "5 stars" })
205
- #
206
- # @overload write(options = {})
207
- # @overload write(data, options = {})
208
- # @param data The data to upload (see the +:data+ option).
209
- #
210
- # @param options [Hash] Additional upload options.
211
- #
212
- # @option options :data The data to upload. Valid values include:
213
- #
214
- # * A string
215
- # * A Pathname object
216
- # * Any object responding to +read+ and +eof?+; the object
217
- # must support the following access methods:
218
- # read # all at once
219
- # read(length) until eof? # in chunks
220
- #
221
- # If you specify data this way, you must also include the
222
- # +:content_length+ option.
223
- #
224
- # @option options [String] :file Can be specified instead of +:data+;
225
- # its value specifies the path of a file to upload.
226
- #
227
- # @option options [Boolean] :single_request If this option is
228
- # true, the method will always generate exactly one request
229
- # to S3 regardless of how much data is being uploaded.
230
406
  #
231
- # @option options [Integer] :content_length If provided, this
232
- # option must match the total number of bytes written to S3
233
- # during the operation. This option is required if +:data+
234
- # is an IO-like object without a +size+ method.
407
+ # # files (by path)
408
+ # obj.write(Pathname.new('path/to/file.txt'))
235
409
  #
236
- # @option options [Integer] :multipart_threshold Specifies the
237
- # maximum size in bytes of a single-request upload. If the
238
- # data being uploaded is larger than this threshold, it will
239
- # be uploaded using {#multipart_upload}.
410
+ # # file objects
411
+ # obj.write(File.open('path/to/file.txt', 'r'))
240
412
  #
241
- # @option options [Integer] :multipart_min_part_size The
242
- # minimum size of a part if a multi-part upload is used. S3
243
- # will reject non-final parts smaller than 5MB, and the
244
- # default for this option is 5MB.
413
+ # # IO objects (must respond to #read and #eof?)
414
+ # obj.write(io)
245
415
  #
246
- # @option options [Hash] :metadata A hash of metadata to be
247
- # included with the object. These will be sent to S3 as
248
- # headers prefixed with +x-amz-meta+. Each name, value pair
249
- # must conform to US-ASCII.
250
- #
251
- # @option options [Symbol,String] :acl (:private) A canned access
252
- # control policy. Valid values are:
253
- #
254
- # * +:private+
255
- # * +:public_read+
256
- # * +:public_read_write+
257
- # * +:authenticated_read+
258
- # * +:bucket_owner_read+
259
- # * +:bucket_owner_full_control+
260
- #
261
- # @option options [String] :grant_read
262
- # @option options [String] :grant_write
263
- # @option options [String] :grant_read_acp
264
- # @option options [String] :grant_write_acp
265
- # @option options [String] :grant_full_control
416
+ # === Multipart Uploads vs Single Uploads
266
417
  #
267
- # @option options [Symbol] :storage_class Controls whether
268
- # Reduced Redundancy Storage is enabled for the object.
269
- # Valid values are +:standard+ (the default) or
270
- # +:reduced_redundancy+.
271
- #
272
- # @option options :cache_control [String] Can be used to specify
273
- # caching behavior. See
274
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
418
+ # This method will intelligently choose between uploading the
419
+ # file in a signal request and using {#multipart_upload}.
420
+ # You can control this behavior by configuring the thresholds
421
+ # and you can disable the multipart feature as well.
275
422
  #
276
- # @option options :content_disposition [String] Specifies
277
- # presentational information for the object. See
278
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec19.5.1
423
+ # # always send the file in a single request
424
+ # obj.write(file, :single_request => true)
279
425
  #
280
- # @option options :content_encoding [String] Specifies what
281
- # content encodings have been applied to the object and thus
282
- # what decoding mechanisms must be applied to obtain the
283
- # media-type referenced by the +Content-Type+ header field.
284
- # See
285
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
426
+ # # upload the file in parts if the total file size exceeds 100MB
427
+ # obj.write(file, :multipart_threshold => 100 * 1024 * 1024)
286
428
  #
287
- # @option options :content_type A standard MIME type
288
- # describing the format of the object data.
429
+ # @overload write(data, options = {})
289
430
  #
290
- # @option options [Symbol] :server_side_encryption (nil) If this
291
- # option is set, the object will be stored using server side
292
- # encryption. The only valid value is +:aes256+, which
293
- # specifies that the object should be stored using the AES
294
- # encryption algorithm with 256 bit keys. By default, this
295
- # option uses the value of the +:s3_server_side_encryption+
296
- # option in the current configuration; for more information,
297
- # see {AWS.config}.
431
+ # @param [String,Pathname,File,IO] data The data to upload.
432
+ # This may be a:
433
+ # * String
434
+ # * Pathname
435
+ # * File
436
+ # * IO
437
+ # * Any object that responds to +#read+ and +#eof?+.
438
+ #
439
+ # @param options [Hash] Additional upload options.
440
+ #
441
+ # @option options [Integer] :content_length If provided, this
442
+ # option must match the total number of bytes written to S3.
443
+ # This options is *required* when it is not possible to
444
+ # automatically determine the size of +data+.
445
+ #
446
+ # @option options [Integer] :estimated_content_length When uploading
447
+ # data of unknown content length, you may specify this option to
448
+ # hint what mode of upload should take place. When
449
+ # +:estimated_content_length+ exceeds the +:multipart_threshold+,
450
+ # then the data will be uploaded in parts, otherwise it will
451
+ # be read into memory and uploaded via {Client#put_object}.
452
+ #
453
+ # @option options [Boolean] :single_request (false) When +true+,
454
+ # this method will always upload the data in a single request
455
+ # (via {Client#put_object}). When +false+, this method will
456
+ # choose between {Client#put_object} and {#multipart_upload}.
457
+ #
458
+ # @option options [Integer] :multipart_threshold (16777216) Specifies
459
+ # the maximum size (in bytes) of a single-request upload. If the
460
+ # data exceeds this threshold, it will be uploaded via
461
+ # {#multipart_upload}. The default threshold is 16MB and can
462
+ # be configured via AWS.config(:s3_multipart_threshold => ...).
463
+ #
464
+ # @option options [Integer] :multipart_min_part_size (5242880) The
465
+ # minimum size of a part to upload to S3 when using
466
+ # {#multipart_upload}. S3 will reject parts smaller than 5MB
467
+ # (except the final part). The default is 5MB and can be
468
+ # configured via AWS.config(:s3_multipart_min_part_size => ...).
469
+ #
470
+ # @option options [Hash] :metadata A hash of metadata to be
471
+ # included with the object. These will be sent to S3 as
472
+ # headers prefixed with +x-amz-meta+. Each name, value pair
473
+ # must conform to US-ASCII.
474
+ #
475
+ # @option options [Symbol,String] :acl (:private) A canned access
476
+ # control policy. Valid values are:
477
+ #
478
+ # * +:private+
479
+ # * +:public_read+
480
+ # * +:public_read_write+
481
+ # * +:authenticated_read+
482
+ # * +:bucket_owner_read+
483
+ # * +:bucket_owner_full_control+
484
+ #
485
+ # @option options [String] :grant_read
486
+ #
487
+ # @option options [String] :grant_write
488
+ #
489
+ # @option options [String] :grant_read_acp
490
+ #
491
+ # @option options [String] :grant_write_acp
492
+ #
493
+ # @option options [String] :grant_full_control
494
+ #
495
+ # @option options [Boolean] :reduced_redundancy (false) When +true+,
496
+ # this object will be stored with Reduced Redundancy Storage.
497
+ #
498
+ # @option options :cache_control [String] Can be used to specify
499
+ # caching behavior. See
500
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
501
+ #
502
+ # @option options :content_disposition [String] Specifies
503
+ # presentational information for the object. See
504
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec19.5.1
505
+ #
506
+ # @option options :content_encoding [String] Specifies what
507
+ # content encodings have been applied to the object and thus
508
+ # what decoding mechanisms must be applied to obtain the
509
+ # media-type referenced by the +Content-Type+ header field.
510
+ # See
511
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
512
+ #
513
+ # @option options :content_type A standard MIME type
514
+ # describing the format of the object data.
515
+ #
516
+ # @option options [Symbol] :server_side_encryption (nil) If this
517
+ # option is set, the object will be stored using server side
518
+ # encryption. The only valid value is +:aes256+, which
519
+ # specifies that the object should be stored using the AES
520
+ # encryption algorithm with 256 bit keys. By default, this
521
+ # option uses the value of the +:s3_server_side_encryption+
522
+ # option in the current configuration; for more information,
523
+ # see {AWS.config}.
524
+ #
525
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key
526
+ # Set this to encrypt the data client-side using envelope
527
+ # encryption. The key must be an OpenSSL asymmetric key
528
+ # or a symmetric key string (16, 24 or 32 bytes in length).
529
+ #
530
+ # @option options [Symbol] :encryption_materials_location (:metadata)
531
+ # Set this to +:instruction_file+ if you prefer to store the
532
+ # client-side encryption materials in a separate object in S3
533
+ # instead of in the object metadata.
298
534
  #
299
535
  # @return [S3Object, ObjectVersion] If the bucket has versioning
300
- # enabled, returns the {ObjectVersion} representing the
301
- # version that was uploaded. If versioning is disabled,
302
- # returns self.
536
+ # enabled, this methods returns an {ObjectVersion}, otherwise
537
+ # this method returns +self+.
303
538
  #
304
- def write(options_or_data = nil, options = nil)
539
+ def write *args, &block
305
540
 
306
- (data_options, put_options) =
307
- compute_put_options(options_or_data, options)
541
+ options = compute_write_options(*args, &block)
308
542
 
309
- if acl = put_options[:acl]
310
- put_options[:acl] = acl.to_s.tr('_', '-')
311
- end
312
-
313
- add_configured_write_options(put_options)
543
+ add_storage_class_option(options)
544
+ add_sse_options(options)
545
+ add_cse_options(options)
314
546
 
315
- if use_multipart?(data_options, put_options)
316
- put_options.delete(:multipart_threshold)
317
- multipart_upload(put_options) do |upload|
318
- each_part(data_options, put_options) do |part|
319
- upload.add_part(part)
320
- end
321
- end
547
+ if use_multipart?(options)
548
+ write_with_multipart(options)
322
549
  else
323
- opts = { :bucket_name => bucket.name, :key => key }
324
- resp = client.put_object(opts.merge(put_options).merge(data_options))
325
- if resp.data[:version_id]
326
- ObjectVersion.new(self, resp.data[:version_id])
327
- else
328
- self
329
- end
550
+ write_with_put_object(options)
330
551
  end
552
+
331
553
  end
332
554
 
333
555
  # Performs a multipart upload. Use this if you have specific
@@ -421,10 +643,11 @@ module AWS
421
643
  # enabled, returns the {ObjectVersion} representing the
422
644
  # version that was uploaded. If versioning is disabled,
423
645
  # returns self.
646
+ #
424
647
  def multipart_upload(options = {})
425
648
 
426
649
  options = options.dup
427
- add_configured_write_options(options)
650
+ add_sse_options(options)
428
651
 
429
652
  upload = multipart_uploads.create(options)
430
653
 
@@ -432,8 +655,9 @@ module AWS
432
655
  begin
433
656
  yield(upload)
434
657
  upload.close
435
- rescue
658
+ rescue => e
436
659
  upload.abort
660
+ raise e
437
661
  end
438
662
  else
439
663
  upload
@@ -450,10 +674,10 @@ module AWS
450
674
  ObjectUploadCollection.new(self)
451
675
  end
452
676
 
453
- # Moves an object to a new key.
677
+ # Moves an object to a new key.
454
678
  #
455
679
  # This works by copying the object to a new key and then
456
- # deleting the old object. This function returns the
680
+ # deleting the old object. This function returns the
457
681
  # new object once this is done.
458
682
  #
459
683
  # bucket = s3.buckets['old-bucket']
@@ -464,7 +688,7 @@ module AWS
464
688
  #
465
689
  # old_obj.key #=> 'old-key'
466
690
  # old_obj.exists? #=> false
467
- #
691
+ #
468
692
  # new_obj.key #=> 'new-key'
469
693
  # new_obj.exists? #=> true
470
694
  #
@@ -473,9 +697,9 @@ module AWS
473
697
  #
474
698
  # obj = s3.buckets['old-bucket'].objects['old-key']
475
699
  # obj.move_to('new-key', :bucket_name => 'new_bucket')
476
- #
700
+ #
477
701
  # If the copy succeeds, but the then the delete fails, an error
478
- # will be raised.
702
+ # will be raised.
479
703
  #
480
704
  # @param [String] target The key to move this object to.
481
705
  #
@@ -492,10 +716,10 @@ module AWS
492
716
  end
493
717
  alias_method :rename_to, :move_to
494
718
 
495
- # Copies data from one S3 object to another.
719
+ # Copies data from one S3 object to another.
496
720
  #
497
721
  # S3 handles the copy so the clients does not need to fetch the data
498
- # and upload it again. You can also change the storage class and
722
+ # and upload it again. You can also change the storage class and
499
723
  # metadata of the object when copying.
500
724
  #
501
725
  # @note This operation does not copy the ACL, storage class
@@ -548,6 +772,10 @@ module AWS
548
772
  # option in the current configuration; for more information,
549
773
  # see {AWS.config}.
550
774
  #
775
+ # @option options [Boolean] :client_side_encrypted (false) Set to true
776
+ # when the object being copied was client-side encrypted. This
777
+ # is important so the encryption metadata will be copied.
778
+ #
551
779
  # @option options :cache_control [String] Can be used to specify
552
780
  # caching behavior. See
553
781
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
@@ -558,13 +786,13 @@ module AWS
558
786
  copy_opts = { :bucket_name => bucket.name, :key => key }
559
787
 
560
788
  copy_opts[:copy_source] = case source
561
- when S3Object
789
+ when S3Object
562
790
  "#{source.bucket.name}/#{source.key}"
563
- when ObjectVersion
791
+ when ObjectVersion
564
792
  copy_opts[:version_id] = source.version_id
565
793
  "#{source.object.bucket.name}/#{source.object.key}"
566
794
  else
567
- case
795
+ case
568
796
  when options[:bucket] then "#{options[:bucket].name}/#{source}"
569
797
  when options[:bucket_name] then "#{options[:bucket_name]}/#{source}"
570
798
  else "#{self.bucket.name}/#{source}"
@@ -573,9 +801,12 @@ module AWS
573
801
 
574
802
  copy_opts[:metadata_directive] = 'COPY'
575
803
 
576
- if options[:metadata]
577
- copy_opts[:metadata] = options[:metadata]
578
- copy_opts[:metadata_directive] = 'REPLACE'
804
+ # Saves client-side encryption headers and copies the instruction file
805
+ copy_cse_materials(source, options) do |cse_materials|
806
+ if options[:metadata]
807
+ copy_opts[:metadata] = options[:metadata].merge(cse_materials)
808
+ copy_opts[:metadata_directive] = 'REPLACE'
809
+ end
579
810
  end
580
811
 
581
812
  if options[:content_type]
@@ -588,9 +819,9 @@ module AWS
588
819
  copy_opts[:server_side_encryption] =
589
820
  options[:server_side_encryption] if
590
821
  options.key?(:server_side_encryption)
591
- copy_opts[:cache_control] = options[:cache_control] if
822
+ copy_opts[:cache_control] = options[:cache_control] if
592
823
  options[:cache_control]
593
- add_configured_write_options(copy_opts)
824
+ add_sse_options(copy_opts)
594
825
 
595
826
  if options[:reduced_redundancy]
596
827
  copy_opts[:storage_class] = 'REDUCED_REDUNDANCY'
@@ -607,7 +838,7 @@ module AWS
607
838
  # Copies data from the current object to another object in S3.
608
839
  #
609
840
  # S3 handles the copy so the client does not need to fetch the data
610
- # and upload it again. You can also change the storage class and
841
+ # and upload it again. You can also change the storage class and
611
842
  # metadata of the object when copying.
612
843
  #
613
844
  # @note This operation does not copy the ACL, storage class
@@ -655,6 +886,11 @@ module AWS
655
886
  # option in the current configuration; for more information,
656
887
  # see {AWS.config}.
657
888
  #
889
+ # @option options [Boolean] :client_side_encrypted (false) When +true+,
890
+ # the client-side encryption materials will be copied. Without this
891
+ # option, the key and iv are not guaranteed to be transferred to
892
+ # the new object.
893
+ #
658
894
  # @return [S3Object] Returns the copy (target) object.
659
895
  #
660
896
  def copy_to target, options = {}
@@ -663,7 +899,7 @@ module AWS
663
899
 
664
900
  bucket = case
665
901
  when options[:bucket] then options[:bucket]
666
- when options[:bucket_name]
902
+ when options[:bucket_name]
667
903
  Bucket.new(options[:bucket_name], :config => config)
668
904
  else self.bucket
669
905
  end
@@ -677,17 +913,37 @@ module AWS
677
913
 
678
914
  target.copy_from(self, copy_opts)
679
915
  target
680
-
916
+
681
917
  end
682
918
 
683
- # Fetches the object data from S3.
919
+ # Fetches the object data from S3. If you pass a block to this
920
+ # method, the data will be yielded to the block in chunks as it
921
+ # is read off the HTTP response.
922
+ #
923
+ # === Read an object from S3 in chunks
924
+ #
925
+ # When downloading large objects it is recommended to pass a block
926
+ # to #read. Data will be yielded to the block as it is read off
927
+ # the HTTP response.
928
+ #
929
+ # # read an object from S3 to a file
930
+ # File.open('output.txt', 'w') do |file|
931
+ # bucket.objects['key'].read do |chunk|
932
+ # file.write(chunk)
933
+ # end
934
+ # end
684
935
  #
685
- # @example Reading data as a string
686
- # object.write('some data')
687
- # object.read
688
- # #=> 'some data'
936
+ # === Reading an object without a block
937
+ #
938
+ # When you omit the block argument to #read, then the entire
939
+ # HTTP response and read and the object data is loaded into
940
+ # memory.
941
+ #
942
+ # bucket.objects['key'].read
943
+ # #=> 'object-contents-here'
689
944
  #
690
945
  # @param [Hash] options
946
+ #
691
947
  # @option options [String] :version_id Reads data from a
692
948
  # specific version of this object.
693
949
  #
@@ -710,10 +966,41 @@ module AWS
710
966
  #
711
967
  # @option options [Range] :range A byte range to read data from
712
968
  #
713
- def read(options = {}, &blk)
969
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key
970
+ # (nil) If this option is set, the object will be decrypted using
971
+ # envelope encryption. The valid values are OpenSSL asymmetric keys
972
+ # +OpenSSL::Pkey::RSA+ or strings representing symmetric keys
973
+ # of an AES-128/192/256-ECB cipher as a +String+.
974
+ # This value defaults to the value in +s3_encryption_key+;
975
+ # for more information, see {AWS.config}.
976
+ #
977
+ # Symmetric Keys:
978
+ #
979
+ # cipher = OpenSSL::Cipher.new('AES-256-ECB')
980
+ # key = cipher.random_key
981
+ #
982
+ # Asymmetric keys can also be generated as so:
983
+ # key = OpenSSL::PKey::RSA.new(KEY_SIZE)
984
+ #
985
+ # @option options [Symbol] :encryption_materials_location (:metadata)
986
+ # Set this to +:instruction_file+ if the encryption materials
987
+ # are not stored in the object metadata
988
+ #
989
+ # @note +:range+ option cannot be used with client-side encryption
990
+ #
991
+ # @note All decryption reads incur at least an extra HEAD operation.
992
+ #
993
+ def read options = {}, &read_block
994
+
714
995
  options[:bucket_name] = bucket.name
715
996
  options[:key] = key
716
- client.get_object(options).data[:data]
997
+
998
+ if should_decrypt?(options)
999
+ get_encrypted_object(options, &read_block)
1000
+ else
1001
+ get_object(options, &read_block)
1002
+ end
1003
+
717
1004
  end
718
1005
 
719
1006
  # @private
@@ -792,7 +1079,8 @@ module AWS
792
1079
  #
793
1080
  # @example Override response headers for reading an object
794
1081
  # object = bucket.objects.myobject
795
- # url = object.url_for(:read, :response_content_type => "application/json")
1082
+ # url = object.url_for(:read,
1083
+ # :response_content_type => "application/json")
796
1084
  #
797
1085
  # @example Generate a url that expires in 10 minutes
798
1086
  # bucket.objects.myobject.url_for(:read, :expires => 10*60)
@@ -846,11 +1134,13 @@ module AWS
846
1134
 
847
1135
  method = http_method(method)
848
1136
  expires = expiration_timestamp(options[:expires])
849
- req.add_param("AWSAccessKeyId", config.credential_provider.access_key_id)
1137
+ req.add_param("AWSAccessKeyId",
1138
+ config.credential_provider.access_key_id)
850
1139
  req.add_param("versionId", options[:version_id]) if options[:version_id]
851
1140
  req.add_param("Signature", signature(method, expires, req))
852
1141
  req.add_param("Expires", expires)
853
- req.add_param("x-amz-security-token", config.credential_provider.session_token) if
1142
+ req.add_param("x-amz-security-token",
1143
+ config.credential_provider.session_token) if
854
1144
  config.credential_provider.session_token
855
1145
 
856
1146
  build_uri(options[:secure] != false, req)
@@ -898,8 +1188,74 @@ module AWS
898
1188
  value
899
1189
  end
900
1190
 
901
- # @private
902
- private
1191
+ protected
1192
+
1193
+ # @return [Boolean]
1194
+ def should_decrypt? options
1195
+ options[:encryption_key]
1196
+ end
1197
+
1198
+ # A small wrapper around client#get_object
1199
+ def get_object options, &read_block
1200
+ client.get_object(options, &read_block).data[:data]
1201
+ end
1202
+
1203
+ # A wrapper around get_object that decrypts
1204
+ def get_encrypted_object options, &read_block
1205
+ decryption_cipher(options) do |cipher|
1206
+ if block_given?
1207
+ get_object(options) do |chunk|
1208
+ yield(cipher.update(chunk))
1209
+ end
1210
+ yield(cipher.final)
1211
+ else
1212
+ cipher.update(get_object(options)) + cipher.final
1213
+ end
1214
+ end
1215
+ end
1216
+
1217
+ # @return [Boolean] Returns +true+ if the :data option is large or
1218
+ # guessed to be larger than a configured threshold.
1219
+ def use_multipart? options
1220
+ estimated_content_length(options) > multipart_threshold(options) and
1221
+ !options[:single_request]
1222
+ end
1223
+
1224
+ # @return [Integer] Returns the number of bytes where a multipart
1225
+ # upload is used instead of #put_object.
1226
+ def multipart_threshold options
1227
+ threshold = options[:multipart_threshold] ||
1228
+ config.s3_multipart_threshold
1229
+ end
1230
+
1231
+ # @return [Integer] Returns the size of each multipart chunk.
1232
+ def compute_part_size options
1233
+
1234
+ max_parts = options[:multipart_max_parts] ||
1235
+ config.s3_multipart_max_parts
1236
+
1237
+ min_size = options[:multipart_min_part_size] ||
1238
+ config.s3_multipart_min_part_size
1239
+
1240
+ estimated_size = estimated_content_length(options)
1241
+
1242
+ [(estimated_size.to_f / max_parts).ceil, min_size].max.to_i
1243
+
1244
+ end
1245
+
1246
+ # @return [Integer] Returns the size of the data or an estimated
1247
+ # size as provided by the user (useful for IO streams).
1248
+ def estimated_content_length options
1249
+ estimate = options[:content_length] ||
1250
+ options[:estimated_content_length]
1251
+ unless estimate
1252
+ msg = "unknown content length, must set :content_length or " +
1253
+ ":estimated_content_length"
1254
+ raise ArgumentError, msg
1255
+ end
1256
+ estimate
1257
+ end
1258
+
903
1259
  def build_uri(secure, request)
904
1260
  uri_class = secure ? URI::HTTPS : URI::HTTP
905
1261
  uri_class.build(:host => request.host,
@@ -907,8 +1263,6 @@ module AWS
907
1263
  :query => request.querystring)
908
1264
  end
909
1265
 
910
- # @private
911
- private
912
1266
  def signature(method, expires, request)
913
1267
 
914
1268
  parts = []
@@ -916,8 +1270,10 @@ module AWS
916
1270
  parts << ""
917
1271
  parts << ""
918
1272
  parts << expires
919
- parts << "x-amz-security-token:#{config.credential_provider.session_token}" if
920
- config.credential_provider.session_token
1273
+ if config.credential_provider.session_token
1274
+ parts << "x-amz-security-token:"
1275
+ parts << "#{config.credential_provider.session_token}"
1276
+ end
921
1277
  parts << request.canonicalized_resource
922
1278
 
923
1279
  string_to_sign = parts.join("\n")
@@ -927,8 +1283,6 @@ module AWS
927
1283
 
928
1284
  end
929
1285
 
930
- # @private
931
- private
932
1286
  def expiration_timestamp(input)
933
1287
  case input
934
1288
  when Time
@@ -944,8 +1298,6 @@ module AWS
944
1298
  end
945
1299
  end
946
1300
 
947
- # @private
948
- private
949
1301
  def http_method(input)
950
1302
  symbol = case input
951
1303
  when :read then :get
@@ -956,8 +1308,6 @@ module AWS
956
1308
  symbol.to_s.upcase
957
1309
  end
958
1310
 
959
- # @private
960
- private
961
1311
  def request_for_signing(options)
962
1312
  req = Request.new
963
1313
 
@@ -973,76 +1323,286 @@ module AWS
973
1323
  req
974
1324
  end
975
1325
 
976
- # @private
977
- private
978
- def compute_put_options(options_or_data, options)
979
- put_opts = {}
1326
+ def add_sse_options(options)
1327
+ options[:server_side_encryption] =
1328
+ config.s3_server_side_encryption unless
1329
+ options.key?(:server_side_encryption)
1330
+ options.delete(:server_side_encryption) if
1331
+ options[:server_side_encryption] == nil
1332
+ end
980
1333
 
981
- if options
1334
+ # Adds client-side encryption metadata headers and encrypts key
1335
+ def add_cse_options(options)
1336
+ encryption_key_for(options) do |encryption_key|
982
1337
 
983
- raise ArgumentError.new("Object data passed twice (argument and file path)") if
984
- options[:file]
1338
+ check_encryption_materials(:encrypt, encryption_key)
1339
+ cipher = get_aes_cipher(:encrypt, :CBC)
985
1340
 
986
- raise ArgumentError.new("Object data passed twice (argument and option)") if
987
- options[:data]
1341
+ generate_aes_key(cipher) do |envelope_key, envelope_iv|
1342
+ envelope_key, envelope_iv =
1343
+ encode_envelope_key(encryption_key, envelope_key, envelope_iv)
988
1344
 
989
- [{ :data => options_or_data }, options]
990
- elsif options_or_data.kind_of?(Hash)
991
- if file = options_or_data[:file]
992
- data_options = { :file => file }
993
- else
994
- data_options = { :data => options_or_data[:data] || "" }
1345
+ build_cse_metadata(options,
1346
+ envelope_key,
1347
+ envelope_iv) do |headers, encryption_materials|
1348
+ store_encryption_materials(options, headers, encryption_materials)
1349
+ end
995
1350
  end
996
1351
 
997
- [data_options, options_or_data]
1352
+ # Wrap current stream in encryption
1353
+ options[:data] = CipherIO.new(cipher,
1354
+ options[:data],
1355
+ options[:content_length])
1356
+
1357
+ # Update content_length
1358
+ options[:content_length] =
1359
+ get_encrypted_size(options[:content_length]) if
1360
+ options[:content_length]
1361
+
1362
+ end
1363
+ remove_cse_options(options)
1364
+ end
1365
+
1366
+ # @yield [String, String] Yields an encrypted encoded key and iv pair
1367
+ def encode_envelope_key encryption_key, envelope_key, envelope_iv, &block
1368
+ envelope_key = encrypt(envelope_key, encryption_key)
1369
+ [encode64(envelope_key), encode64(envelope_iv)]
1370
+ end
1371
+
1372
+ # @yield [Hash, Hash] Yields headers and encryption materials that are
1373
+ # to be stored in the metadata and/or instruction file
1374
+ def build_cse_metadata options, enc_envelope_key, enc_envelope_iv, &block
1375
+ # Ensure metadata exists
1376
+ options[:metadata] = {} unless options[:metadata]
1377
+
1378
+ encryption_materials = {'x-amz-key' => enc_envelope_key,
1379
+ 'x-amz-iv' => enc_envelope_iv,
1380
+ 'x-amz-matdesc' => "{}"}
1381
+ orig_headers = {}
1382
+
1383
+ # Save the unencrypted content length
1384
+ if options[:content_length]
1385
+ orig_headers['x-amz-unencrypted-content-length'] =
1386
+ options[:content_length]
1387
+ end
1388
+
1389
+ # Save the unencrypted content MD5
1390
+ if options[:content_md5]
1391
+ orig_headers['x-amz-unencrypted-content-md5'] =
1392
+ options[:content_md5]
1393
+ options.delete(:content_md5)
1394
+ end
1395
+
1396
+ options[:metadata].merge!(orig_headers)
1397
+
1398
+ yield([orig_headers, encryption_materials])
1399
+ end
1400
+
1401
+ # Stores the headers and encryption materials needed to decrypt the data
1402
+ # and to know unencrypted information about the object
1403
+ def store_encryption_materials options, orig_headers, encryption_materials
1404
+ # Get the storage location
1405
+ cse_location = options[:encryption_materials_location] ||
1406
+ config.s3_encryption_materials_location
1407
+
1408
+ # Encryption type specific metadata
1409
+ case cse_location
1410
+ when :metadata
1411
+ options[:metadata].merge!(encryption_materials)
1412
+ when :instruction_file
1413
+ json_string = JSON.generate(encryption_materials)
1414
+ inst_headers = {'x-amz-crypto-instr-file' => ""}.merge(orig_headers)
1415
+ bucket.objects["#{key}.instruction"].write(json_string,
1416
+ :metadata => inst_headers)
998
1417
  else
999
- [{ :data => options_or_data || "" }, {}]
1418
+ msg = "invalid :encryption_materials_location, expected "
1419
+ msg << ":metadata or :instruction_file, got: #{cse_location.inspect}"
1420
+ raise ArgumentError, msg
1000
1421
  end
1422
+ nil
1001
1423
  end
1002
1424
 
1003
- private
1004
- def add_configured_write_options(options)
1005
- options[:server_side_encryption] =
1006
- config.s3_server_side_encryption unless
1007
- options.key?(:server_side_encryption)
1008
- options.delete(:server_side_encryption) if
1009
- options[:server_side_encryption] == nil
1425
+ # Removes any extra headers client-side encryption uses.
1426
+ def remove_cse_options options
1427
+ options.delete(:encryption_key)
1428
+ options.delete(:encryption_materials_location)
1429
+ options.delete(:encryption_matdesc)
1010
1430
  end
1011
1431
 
1012
- # @private
1013
- private
1014
- def use_multipart?(data_options, options)
1015
- threshold = options[:multipart_threshold] ||
1016
- config.s3_multipart_threshold
1432
+ # Yields a decryption cipher for the given client-side encryption key
1433
+ # or raises an error.
1434
+ def decryption_cipher options, &block
1435
+ encryption_key_for(options) do |encryption_key|
1436
+
1437
+ check_encryption_materials(:decrypt, encryption_key)
1438
+
1439
+ location = options[:encryption_materials_location] ||
1440
+ config.s3_encryption_materials_location
1017
1441
 
1018
- !options[:single_request] and
1019
- size = content_length_from(data_options.merge(options)) and
1020
- size > threshold
1442
+ cipher =
1443
+ decryption_materials(location) do |envelope_key, envelope_iv|
1444
+ envelope_key, envelope_iv =
1445
+ decode_envelope_key(envelope_key, envelope_iv, options)
1446
+ get_aes_cipher(:decrypt, :CBC, envelope_key, envelope_iv)
1447
+ end
1448
+
1449
+ remove_cse_options(options)
1450
+
1451
+ yield(cipher)
1452
+
1453
+ end
1021
1454
  end
1022
1455
 
1023
- # @private
1024
- private
1025
- def each_part(data_options, options)
1026
- total_size = content_length_from(data_options.merge(options))
1027
- part_size = optimal_part_size(total_size, options)
1028
- stream = data_stream_from(data_options.merge(options))
1029
- while !stream.eof?
1030
- yield(stream.read(part_size))
1456
+ # Decodes the envelope key for decryption
1457
+ def decode_envelope_key envelope_key, envelope_iv, options
1458
+ decrypted_key =
1459
+ begin
1460
+ decrypt(decode64(envelope_key), options[:encryption_key])
1461
+ rescue RuntimeError
1462
+ msg = "Master key used to decrypt data key is not correct."
1463
+ raise AWS::S3::Errors::IncorrectClientSideEncryptionKey, msg
1031
1464
  end
1465
+
1466
+ [decrypted_key, decode64(envelope_iv)]
1032
1467
  end
1033
1468
 
1034
- # @private
1035
- private
1036
- def optimal_part_size(total_size, options)
1037
- maximum_parts = config.s3_multipart_max_parts
1038
- min_size = options[:multipart_min_part_size] ||
1039
- config.s3_multipart_min_part_size
1040
1469
 
1041
- [(total_size.to_f / maximum_parts.to_f).ceil,
1042
- min_size].max.to_i
1470
+ # @yield [String, String, String] Yields encryption materials for
1471
+ # decryption
1472
+ def decryption_materials location, &block
1473
+
1474
+ materials = case location
1475
+ when :metadata then get_metadata_materials
1476
+ when :instruction_file then get_inst_file_materials
1477
+ else
1478
+ msg = "invalid :encryption_materials_location option, expected "
1479
+ msg << ":metadata or :instruction_file, got: #{location.inspect}"
1480
+ raise ArgumentError, msg
1481
+ end
1482
+
1483
+ envelope_key, envelope_iv = materials
1484
+
1485
+ unless envelope_key and envelope_iv
1486
+ raise 'no encryption materials found, unable to decrypt'
1487
+ end
1488
+
1489
+ yield(envelope_key, envelope_iv)
1490
+
1043
1491
  end
1044
1492
 
1045
- end
1493
+ # @return [String, String, String] Returns the data key, envelope_iv, and the
1494
+ # material description for decryption from the metadata.
1495
+ def get_metadata_materials
1496
+ metadata.to_h.values_at(*%w(x-amz-key x-amz-iv))
1497
+ end
1498
+
1499
+ # @return [String, String, String] Returns the data key, envelope_iv, and the
1500
+ # material description for decryption from the instruction file.
1501
+ def get_inst_file_materials
1502
+ obj = bucket.objects["#{key}.instruction"]
1503
+ JSON.parse(obj.read).values_at(*%w(x-amz-key x-amz-iv))
1504
+ end
1505
+
1506
+ # @yield [Hash] Yields the metadata to be saved for client-side encryption
1507
+ def copy_cse_materials source, options
1508
+ cse_materials = {}
1509
+ if options[:client_side_encrypted]
1510
+ meta = source.metadata.to_h
1511
+ cse_materials['x-amz-key'] = meta['x-amz-key'] if meta['x-amz-key']
1512
+ cse_materials['x-amz-iv'] = meta['x-amz-iv'] if meta['x-amz-iv']
1513
+ cse_materials['x-amz-matdesc'] = meta['x-amz-matdesc'] if
1514
+ meta['x-amz-matdesc']
1515
+ cse_materials['x-amz-unencrypted-content-length'] =
1516
+ meta['x-amz-unencrypted-content-length'] if
1517
+ meta['x-amz-unencrypted-content-length']
1518
+ cse_materials['x-amz-unencrypted-content-md5'] =
1519
+ meta['x-amz-unencrypted-content-md5'] if
1520
+ meta['x-amz-unencrypted-content-md5']
1521
+
1522
+ # Handling instruction file
1523
+ unless cse_materials['x-amz-key'] and
1524
+ cse_materials['x-amz-iv'] and
1525
+ cse_materials['x-amz-matdesc']
1526
+ source_inst = "#{source.key}.instruction"
1527
+ dest_inst = "#{key}.instruction"
1528
+ self.bucket.objects[dest_inst].copy_from(
1529
+ source.bucket.objects[source_inst])
1530
+ end
1531
+ end
1532
+ yield(cse_materials)
1533
+ end
1534
+
1535
+ # Removes unwanted options that should not be passed to the client.
1536
+ def clean_up_options(options)
1537
+ options.delete(:estimated_content_length)
1538
+ options.delete(:single_request)
1539
+ options.delete(:multipart_threshold)
1540
+ end
1541
+
1542
+ # Performs a write using a multipart upload
1543
+ def write_with_multipart options
1544
+ part_size = compute_part_size(options)
1545
+ clean_up_options(options)
1546
+ options.delete(:content_length)
1547
+
1548
+ multipart_upload(options) do |upload|
1549
+ upload.add_part(options[:data].read(part_size)) until
1550
+ options[:data].eof?
1551
+ end
1552
+ end
1553
+
1554
+ # Performs a write using a single request
1555
+ def write_with_put_object options
1556
+
1557
+ # its possible we don't know the content length of the data
1558
+ # option, but the :estimated_content_length was sufficiently
1559
+ # small that we will read the entire stream into memory
1560
+ # so we can tell s3 the content length (this is required).
1561
+ unless options[:content_length]
1562
+ data = StringIO.new
1563
+
1564
+ while (chunk = options[:data].read(4 * 1024))
1565
+ data << chunk
1566
+ end
1046
1567
 
1568
+ options[:content_length] = data.size
1569
+ data.rewind
1570
+ options[:data] = data
1571
+ end
1572
+
1573
+ clean_up_options(options)
1574
+
1575
+ options[:bucket_name] = bucket.name
1576
+ options[:key] = key
1577
+
1578
+ resp = client.put_object(options)
1579
+
1580
+ resp.data[:version_id] ?
1581
+ ObjectVersion.new(self, resp.data[:version_id]) : self
1582
+ end
1583
+
1584
+ def encryption_key_for options, &block
1585
+ if key = options[:encryption_key] || config.s3_encryption_key
1586
+ yield(key)
1587
+ end
1588
+ end
1589
+
1590
+ def add_storage_class_option options
1591
+ if options[:reduced_redundancy] == true
1592
+ options[:storage_class] = 'REDUCED_REDUNDANCY'
1593
+ end
1594
+ end
1595
+
1596
+ # @return [String] Encodes a +String+ in base 64 regardless of version of
1597
+ # Ruby for http headers (removes newlines).
1598
+ def encode64 input
1599
+ Base64.encode64(input).split("\n") * ""
1600
+ end
1601
+
1602
+ # @return [String] Decodes a +String+ in base 64.
1603
+ def decode64 input
1604
+ Base64.decode64(input)
1605
+ end
1606
+ end
1047
1607
  end
1048
1608
  end