aws-sdk 1.6.2 → 1.6.3

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.
@@ -25,4 +25,10 @@ AWS::Core::Configuration.module_eval do
25
25
 
26
26
  add_option :s3_server_side_encryption, nil
27
27
 
28
+ add_option :s3_encryption_key, nil
29
+
30
+ add_option :s3_encryption_materials_location, :metadata
31
+
32
+ add_option :s3_storage_class, 'STANDARD'
33
+
28
34
  end
@@ -16,82 +16,169 @@ require 'pathname'
16
16
  module AWS
17
17
  class S3
18
18
 
19
+ # Used by S3#S3Object and S3::Client to accept options with
20
+ # data that should be uploaded (streamed).
19
21
  # @private
20
22
  module DataOptions
21
23
 
22
24
  protected
23
25
 
24
- def data_stream_from options, &block
26
+ # @return [Hash] Returns a hash of options with a :data option that
27
+ # responds to #read and #eof?.
28
+ def compute_write_options *args, &block
25
29
 
26
- validate_data!(options, block)
30
+ options = convert_args_to_options_hash(*args)
27
31
 
28
- # block format
29
- if block_given?
30
- buffer = StringIO.new
31
- yield(buffer)
32
- buffer.rewind
33
- return buffer
32
+ validate_data!(options, &block)
33
+
34
+ rename_file_to_data(options)
35
+
36
+ convert_data_to_io_obj(options, &block)
37
+
38
+ try_to_determine_content_length(options)
39
+
40
+ options
41
+
42
+ end
43
+
44
+ # Converts an argument list into a single hash of options. Treats
45
+ # non-hash arguments in the first position as a data option.
46
+ def convert_args_to_options_hash *args
47
+ case args.count
48
+ when 0 then {}
49
+ when 1 then args[0].is_a?(Hash) ? args[0] : { :data => args[0] }
50
+ when 2 then args[1].merge(:data => args[0])
51
+ else
52
+ msg = "expected 0, 1 or 2 arguments, got #{args.count}"
53
+ raise ArgumentError, msg
34
54
  end
55
+ end
35
56
 
36
- # string, pathname, file, io-like object, etc
37
- data = options[:data]
38
- file_opts = ["rb"]
39
- file_opts << { :encoding => "BINARY" } if Object.const_defined?(:Encoding)
40
- case
41
- when data.is_a?(String)
57
+ # Moves options[:file] to options[:data]. If this option is a string
58
+ # then it is treated as a file path and is converted to an open file.
59
+ def rename_file_to_data options
60
+ if file = options.delete(:file)
61
+ options[:data] = file.is_a?(String) ? open_file(file) : file
62
+ end
63
+ end
64
+
65
+ # Converts the :data option to an IO-like object. This allows us
66
+ # to always perform streaming uploads.
67
+ def convert_data_to_io_obj options, &block
68
+
69
+ data = options.delete(:data)
70
+
71
+ if block_given?
72
+ options[:data] = IOProxy.new(block)
73
+ elsif data.is_a?(String)
42
74
  data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
43
- StringIO.new(data)
44
- when data.is_a?(Pathname) then File.open(data.to_s, *file_opts)
45
- when options[:file] then File.open(options[:file], *file_opts)
46
- else data
75
+ options[:data] = StringIO.new(data)
76
+ elsif data.is_a?(Pathname)
77
+ options[:data] = open_file(data.to_s)
78
+ elsif io_like?(data)
79
+ options[:data] = data
80
+ else
81
+ msg = "invalid :data option, expected a String, Pathname or "
82
+ msg << "an object that responds to #read and #eof?"
83
+ raise ArgumentError, msg
47
84
  end
48
85
 
49
86
  end
50
87
 
51
- def content_length_from options
52
- data = options[:data]
53
- case
54
- when options[:content_length] then options[:content_length]
55
- when options[:file] then File.size(options[:file])
56
- when data.is_a?(Pathname) then File.size(data.to_s)
57
- when data.is_a?(File) then File.size(data.path)
58
- when data.respond_to?(:bytesize) then data.bytesize
59
- when data.respond_to?(:size) then data.size
60
- when data.respond_to?(:length) then data.length
61
- else raise ArgumentError, 'content_length was not provided ' +
62
- 'and could not be determined'
88
+ # Attempts to determine the content length of the :data option.
89
+ # This is only done when a content length is not already provided.
90
+ def try_to_determine_content_length options
91
+ unless options[:content_length]
92
+
93
+ data = options[:data]
94
+
95
+ length = case
96
+ when data.respond_to?(:bytesize) then data.bytesize
97
+ when data.respond_to?(:size) then data.size
98
+ when data.respond_to?(:length) then data.length
99
+ when data.respond_to?(:path) then File.size(data.path)
100
+ else nil
101
+ end
102
+
103
+ options[:content_length] = length if length
104
+
63
105
  end
64
106
  end
65
107
 
66
- def validate_data! options, block
108
+ def validate_data! options, &block
67
109
 
68
110
  data = options[:data]
69
- filename = options[:file]
111
+ file = options[:file]
70
112
 
71
- raise ArgumentError, 'data passed multiple ways' if
72
- [data, filename, block].compact.size > 1
113
+ raise ArgumentError, 'Object data passed multiple ways.' if
114
+ [data, file, block].compact.count > 1
73
115
 
74
- # accepting block format
75
- return if block and block.arity == 1
116
+ data = file if file
76
117
 
77
- # accepting file path
78
- return if filename.kind_of?(String)
79
-
80
- # accepting strings
118
+ return if block_given?
81
119
  return if data.kind_of?(String)
82
-
83
- # accepting pathname
84
120
  return if data.kind_of?(Pathname)
121
+ return if io_like?(data)
122
+
123
+ msg = ":data must be provided as a String, Pathname, File, or "
124
+ msg << "an object that responds to #read and #eof?"
125
+ raise ArgumentError, msg
85
126
 
86
- # accepts io-like objects (responds to read and eof?)
87
- if data.respond_to?(:read) and
88
- data.method(:read).arity != 0 and
89
- data.respond_to?(:eof?) then
90
- return true
127
+ end
128
+
129
+ # @return [Boolean] Returns +true+ if the object responds to
130
+ # +#read+ and +#eof?+.
131
+ def io_like? io
132
+ io.respond_to?(:read) and io.respond_to?(:eof?)
133
+ end
134
+
135
+ # @param [String] path Path to a file on disk.
136
+ # @return [File] Given a path string, returns an open File.
137
+ def open_file path
138
+ file_opts = ['rb']
139
+ file_opts << { :encoding => "BINARY" } if Object.const_defined?(:Encoding)
140
+ File.open(path, *file_opts)
141
+ end
142
+
143
+ # A utility class that turns a block (with 2 args) into an
144
+ # IO object that responds to #read and #eof.
145
+ # @private
146
+ class IOProxy
147
+
148
+ def initialize write_block
149
+ unless write_block.arity == 2
150
+ msg = "a write block must accept 2 yield params: a buffer and "
151
+ msg << "a number of bytes to write"
152
+ raise ArgumentError, msg
153
+ end
154
+ @write_block = write_block
155
+ @eof = false
156
+ end
157
+
158
+ def read bytes = nil
159
+ if bytes
160
+ buffer = StringIO.new
161
+ @write_block.call(buffer, bytes)
162
+ buffer.rewind
163
+ @eof = true if buffer.size < bytes
164
+ buffer.size == 0 ? nil : buffer.read
165
+ else
166
+ read_all
167
+ end
168
+ end
169
+
170
+ def eof?
171
+ @eof
91
172
  end
92
173
 
93
- raise ArgumentError, 'data must be provided as a String, ' +
94
- 'Pathname, file path, or an object that responds to #read and #eof?'
174
+ protected
175
+
176
+ def read_all
177
+ buffer = StringIO.new
178
+ buffer << read(1024 * 1024 * 5) until eof?
179
+ buffer.rewind
180
+ buffer.read
181
+ end
95
182
 
96
183
  end
97
184
 
@@ -0,0 +1,144 @@
1
+ # Copyright 2011-2012 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
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'openssl'
15
+
16
+ module AWS
17
+ class S3
18
+ # @private
19
+ module EncryptionUtils
20
+
21
+ protected
22
+
23
+
24
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
25
+ #
26
+ # @param [String] data Data to be encrypted.
27
+ #
28
+ # @note Use check_encryption_materials before this method to check
29
+ # formatting of keys
30
+ #
31
+ # @return [String] Returns the data encrypted with the key given.
32
+ def encrypt data, key
33
+ rsa = OpenSSL::PKey::RSA
34
+ ## Encrypting data key
35
+ case key
36
+ when rsa # Asymmetric encryption
37
+ key.public_encrypt(data)
38
+ when String # Symmetric encryption
39
+ cipher = get_aes_cipher(:encrypt, :ECB, key)
40
+ cipher.update(data) + cipher.final
41
+ end
42
+ end
43
+
44
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
45
+ #
46
+ # @param [String] data Data to be encrypted.
47
+ #
48
+ # @note Use check_encryption_materials before this method to check
49
+ # formatting of keys
50
+ #
51
+ # @return [String] Returns the data decrypted with the key given.
52
+ def decrypt data, key
53
+ rsa = OpenSSL::PKey::RSA
54
+ begin
55
+ case key
56
+ when rsa # Asymmetric Decryption
57
+ key.private_decrypt(data)
58
+ when String # Symmetric Decryption
59
+ cipher = get_aes_cipher(:decrypt, :ECB, key)
60
+ cipher.update(data) + cipher.final
61
+ end
62
+ rescue OpenSSL::Cipher::CipherError
63
+ raise RuntimeError, "decryption failed, incorrect key?"
64
+ end
65
+ end
66
+
67
+ # Checks for any formatting problems for keys and initialization vectors
68
+ # supported with EncryptionUtils.
69
+ #
70
+ # @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
71
+ #
72
+ # @param [String] data Data to be encrypted.
73
+ #
74
+ def check_encryption_materials mode, key
75
+ rsa = OpenSSL::PKey::RSA
76
+ case key
77
+ when rsa
78
+ unless key.private? or mode == :encrypt
79
+ msg = "invalid key, #{rsa} requires a private key"
80
+ raise ArgumentError, msg
81
+ end
82
+ when String # no problem
83
+ else
84
+ msg = "invalid key, must be an #{rsa} or a cipher key string"
85
+ raise ArgumentError, msg
86
+ end
87
+ end
88
+
89
+ # @param [OpenSSL::Cipher] cipher The cipher with configured key and iv.
90
+ #
91
+ # @yield [String, String] key_iv_pair A randomly generated key, iv pair
92
+ # for use with the given cipher. Sets the key and iv on the cipher.
93
+ def generate_aes_key cipher, &block
94
+ key_iv_pair = [cipher.random_key, cipher.random_iv]
95
+ yield(key_iv_pair) if block_given?
96
+ end
97
+
98
+ # @param [Symbol] mode The encryption/decryption mode. Valid inputs are
99
+ # :encrypt or :decrypt
100
+ #
101
+ # @param [String] key Key for the cipher.
102
+ #
103
+ # @param [String] iv IV for the cipher.
104
+ #
105
+ # @return [OpenSSL::Cipher] Will return a configured +OpenSSL::Cipher+.
106
+ def get_aes_cipher mode, block_mode, key = nil, iv = nil
107
+
108
+ # If no key given, default to 256 bit
109
+ cipher_size = (key) ? get_cipher_size(key.length) : 256
110
+
111
+ cipher = OpenSSL::Cipher.new("AES-#{cipher_size}-#{block_mode}")
112
+
113
+ (mode == :encrypt) ? cipher.encrypt : cipher.decrypt
114
+ cipher.key = key if key
115
+ cipher.iv = iv if iv
116
+ cipher
117
+ end
118
+
119
+ # @param [Fixnum] Size of data given.
120
+ # @return [Fixnum] Returns the AES encrypted size based on a given size.
121
+ def get_encrypted_size size
122
+ # The next multiple of 16
123
+ ((size / 16) + 1) * 16
124
+ end
125
+ module_function :get_encrypted_size
126
+
127
+ private
128
+
129
+ # @param [Fixnum] key_length Length of the key given.
130
+ # @return [Fixnum] Returns the cipher size based on the key length.
131
+ def get_cipher_size(key_length)
132
+ case key_length
133
+ when 32 then 256
134
+ when 24 then 192
135
+ when 16 then 128
136
+ else
137
+ msg = "invalid key, symmetric key required to be 16, 24, or 32 bytes "
138
+ msg << "in length, saw length #{key_length}"
139
+ raise ArgumentError, msg
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -76,6 +76,20 @@ module AWS
76
76
 
77
77
  end
78
78
 
79
+ # This error is special, because S3 must first retrieve the client
80
+ # side encryption key in it's encrypted form before finding if the
81
+ # key is incorrect.
82
+ class IncorrectClientSideEncryptionKey < AWS::Errors::Base
83
+
84
+ include AWS::Errors::ClientError
85
+
86
+ def initialize(msg)
87
+ super("",
88
+ "",
89
+ "IncorrectClientSideEncryptionKey",
90
+ msg)
91
+ end
92
+ end
79
93
  end
80
94
  end
81
95
  end
@@ -110,8 +110,10 @@ module AWS
110
110
  # storage consumed by all parts.
111
111
  # @return [nil]
112
112
  def abort
113
- client.abort_multipart_upload(base_opts)
114
- @aborted = true
113
+ unless aborted?
114
+ client.abort_multipart_upload(base_opts)
115
+ @aborted = true
116
+ end
115
117
  nil
116
118
  end
117
119
  alias_method :delete, :abort
@@ -257,8 +259,9 @@ module AWS
257
259
  # returns the object. If no upload was attempted (e.g. if it
258
260
  # was aborted or if no parts were uploaded), returns +nil+.
259
261
  def close
260
- return if aborted?
261
- if completed_parts.empty?
262
+ if aborted?
263
+ nil
264
+ elsif completed_parts.empty?
262
265
  abort
263
266
  else
264
267
  complete
@@ -81,8 +81,8 @@ module AWS
81
81
  #
82
82
  # @param [String] key Where in S3 to write the object.
83
83
  # @return [S3Object]
84
- def create key, *args
85
- self[key].write(*args)
84
+ def create key, *args, &block
85
+ self[key].write(*args, &block)
86
86
  end
87
87
 
88
88
  # Returns an S3Object given its name. For example:
@@ -49,7 +49,7 @@ module AWS
49
49
  :get_bucket_notification => "s3:GetBucketNotification",
50
50
  :set_bucket_notification => "s3:PutBucketNotification"
51
51
  }
52
-
52
+
53
53
  protected
54
54
  def resource_arn resource
55
55
  prefix = 'arn:aws:s3:::'
@@ -28,9 +28,6 @@ module AWS
28
28
  # @return [String] S3 object key
29
29
  attr_accessor :key
30
30
 
31
- # @private
32
- attr_accessor :body_stream
33
-
34
31
  # @private
35
32
  attr_accessor :force_path_style
36
33
 
@@ -48,6 +45,14 @@ module AWS
48
45
  end
49
46
  end
50
47
 
48
+ def server_side_encryption= sse
49
+ if sse.is_a?(Symbol)
50
+ headers['x-amz-server-side-encryption'] = sse.to_s.upcase
51
+ elsif sse
52
+ headers['x-amz-server-side-encryption'] = sse
53
+ end
54
+ end
55
+
51
56
  def host
52
57
  path_style? ? @host : "#{bucket}.#{@host}"
53
58
  end
@@ -76,30 +81,13 @@ module AWS
76
81
 
77
82
  end
78
83
 
79
- # @param [String, IO] body The http request body. This can be a string or
80
- # any object that responds to #read and #eof? (like an IO object).
81
- def body= body
82
- @body_stream = StringIO.new(body)
83
- end
84
-
85
- # @return [String, nil] The http request body.
86
- def body
87
- if @body_stream
88
- string = @body_stream.read
89
- @body_stream.rewind
90
- string
91
- else
92
- nil
93
- end
94
- end
95
-
96
84
  # From the S3 developer guide:
97
85
  #
98
- # StringToSign =
99
- # HTTP-Verb + "\n" +
100
- # content-md5 + "\n" +
101
- # content-type + "\n" +
102
- # date + "\n" +
86
+ # StringToSign =
87
+ # HTTP-Verb + "\n" +
88
+ # content-md5 + "\n" +
89
+ # content-type + "\n" +
90
+ # date + "\n" +
103
91
  # CanonicalizedAmzHeaders + CanonicalizedResource;
104
92
  #
105
93
  def string_to_sign
@@ -124,11 +112,11 @@ module AWS
124
112
 
125
113
  # From the S3 developer guide
126
114
  #
127
- # CanonicalizedResource =
128
- # [ "/" + Bucket ] +
115
+ # CanonicalizedResource =
116
+ # [ "/" + Bucket ] +
129
117
  # <HTTP-Request-URI, from the protocol name up to the querystring> +
130
- # [ sub-resource, if present. e.g. "?acl", "?location",
131
- # "?logging", or "?torrent"];
118
+ # [ sub-resource, if present. e.g. "?acl", "?location",
119
+ # "?logging", or "?torrent"];
132
120
  #
133
121
  def canonicalized_resource
134
122
 
@@ -139,11 +127,11 @@ module AWS
139
127
  if Client.dns_compatible_bucket_name?(bucket) and !path_style?
140
128
  parts << "/#{bucket}"
141
129
  end
142
-
130
+
143
131
  # all requests require the portion of the un-decoded uri up to
144
132
  # but not including the query string
145
133
  parts << path
146
-
134
+
147
135
  # lastly any sub resource querystring params need to be appened
148
136
  # in lexigraphical ordered joined by '&' and prefixed by '?'
149
137
  params = (sub_resource_params + query_parameters_for_signature)
@@ -190,8 +178,8 @@ module AWS
190
178
  class << self
191
179
 
192
180
  def sub_resources
193
- %w(acl location logging notification partNumber policy
194
- requestPayment torrent uploadId uploads versionId
181
+ %w(acl location logging notification partNumber policy
182
+ requestPayment torrent uploadId uploads versionId
195
183
  versioning versions delete lifecycle)
196
184
  end
197
185