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.
- data/lib/aws/core.rb +13 -2
- data/lib/aws/core/autoloader.rb +1 -1
- data/lib/aws/core/client.rb +69 -30
- data/lib/aws/core/configuration.rb +12 -1
- data/lib/aws/core/http/handler.rb +28 -16
- data/lib/aws/core/http/net_http_handler.rb +31 -11
- data/lib/aws/core/http/request.rb +52 -16
- data/lib/aws/core/http/response.rb +20 -16
- data/lib/aws/core/indifferent_hash.rb +14 -14
- data/lib/aws/core/query_client.rb +1 -0
- data/lib/aws/core/response.rb +32 -14
- data/lib/aws/core/signature/version_2.rb +1 -0
- data/lib/aws/core/signature/version_4.rb +16 -16
- data/lib/aws/dynamo_db/client.rb +2 -2
- data/lib/aws/dynamo_db/request.rb +0 -6
- data/lib/aws/ec2/security_group/ip_permission.rb +4 -1
- data/lib/aws/rails.rb +10 -10
- data/lib/aws/s3.rb +44 -29
- data/lib/aws/s3/bucket.rb +171 -6
- data/lib/aws/s3/cipher_io.rb +119 -0
- data/lib/aws/s3/client.rb +75 -45
- data/lib/aws/s3/config.rb +6 -0
- data/lib/aws/s3/data_options.rb +136 -49
- data/lib/aws/s3/encryption_utils.rb +144 -0
- data/lib/aws/s3/errors.rb +14 -0
- data/lib/aws/s3/multipart_upload.rb +7 -4
- data/lib/aws/s3/object_collection.rb +2 -2
- data/lib/aws/s3/policy.rb +1 -1
- data/lib/aws/s3/request.rb +21 -33
- data/lib/aws/s3/s3_object.rb +797 -237
- data/lib/aws/simple_email_service/request.rb +0 -2
- data/lib/aws/simple_workflow/request.rb +0 -3
- data/lib/net/http/connection_pool.rb +63 -75
- data/lib/net/http/connection_pool/connection.rb +69 -15
- data/lib/net/http/connection_pool/session.rb +39 -6
- metadata +4 -2
data/lib/aws/s3/config.rb
CHANGED
data/lib/aws/s3/data_options.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
30
|
+
options = convert_args_to_options_hash(*args)
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
111
|
+
file = options[:file]
|
70
112
|
|
71
|
-
raise ArgumentError, 'data passed multiple ways' if
|
72
|
-
[data,
|
113
|
+
raise ArgumentError, 'Object data passed multiple ways.' if
|
114
|
+
[data, file, block].compact.count > 1
|
73
115
|
|
74
|
-
|
75
|
-
return if block and block.arity == 1
|
116
|
+
data = file if file
|
76
117
|
|
77
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
94
|
-
|
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
|
data/lib/aws/s3/errors.rb
CHANGED
@@ -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
|
-
|
114
|
-
|
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
|
-
|
261
|
-
|
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:
|
data/lib/aws/s3/policy.rb
CHANGED
data/lib/aws/s3/request.rb
CHANGED
@@ -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
|
|