aws-sdk-s3 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/aws-sdk-s3.rb +66 -0
- data/lib/aws-sdk-s3/bucket.rb +595 -0
- data/lib/aws-sdk-s3/bucket_acl.rb +168 -0
- data/lib/aws-sdk-s3/bucket_cors.rb +146 -0
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +164 -0
- data/lib/aws-sdk-s3/bucket_logging.rb +142 -0
- data/lib/aws-sdk-s3/bucket_notification.rb +187 -0
- data/lib/aws-sdk-s3/bucket_policy.rb +138 -0
- data/lib/aws-sdk-s3/bucket_region_cache.rb +79 -0
- data/lib/aws-sdk-s3/bucket_request_payment.rb +128 -0
- data/lib/aws-sdk-s3/bucket_tagging.rb +143 -0
- data/lib/aws-sdk-s3/bucket_versioning.rb +188 -0
- data/lib/aws-sdk-s3/bucket_website.rb +177 -0
- data/lib/aws-sdk-s3/client.rb +3171 -0
- data/lib/aws-sdk-s3/client_api.rb +1991 -0
- data/lib/aws-sdk-s3/customizations.rb +29 -0
- data/lib/aws-sdk-s3/customizations/bucket.rb +127 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +42 -0
- data/lib/aws-sdk-s3/customizations/object.rb +257 -0
- data/lib/aws-sdk-s3/customizations/object_summary.rb +65 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +11 -0
- data/lib/aws-sdk-s3/encryption.rb +19 -0
- data/lib/aws-sdk-s3/encryption/client.rb +369 -0
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +178 -0
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +63 -0
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +38 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +50 -0
- data/lib/aws-sdk-s3/encryption/errors.rb +13 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +50 -0
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +29 -0
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +69 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +29 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +71 -0
- data/lib/aws-sdk-s3/encryption/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryption/utils.rb +79 -0
- data/lib/aws-sdk-s3/errors.rb +23 -0
- data/lib/aws-sdk-s3/file_part.rb +75 -0
- data/lib/aws-sdk-s3/file_uploader.rb +58 -0
- data/lib/aws-sdk-s3/legacy_signer.rb +186 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +187 -0
- data/lib/aws-sdk-s3/multipart_upload.rb +287 -0
- data/lib/aws-sdk-s3/multipart_upload_error.rb +16 -0
- data/lib/aws-sdk-s3/multipart_upload_part.rb +314 -0
- data/lib/aws-sdk-s3/object.rb +942 -0
- data/lib/aws-sdk-s3/object_acl.rb +214 -0
- data/lib/aws-sdk-s3/object_copier.rb +99 -0
- data/lib/aws-sdk-s3/object_multipart_copier.rb +179 -0
- data/lib/aws-sdk-s3/object_summary.rb +794 -0
- data/lib/aws-sdk-s3/object_version.rb +406 -0
- data/lib/aws-sdk-s3/plugins/accelerate.rb +92 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +89 -0
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +23 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +70 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +29 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +23 -0
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +47 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +33 -0
- data/lib/aws-sdk-s3/plugins/md5s.rb +79 -0
- data/lib/aws-sdk-s3/plugins/redirects.rb +41 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +208 -0
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +68 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +94 -0
- data/lib/aws-sdk-s3/presigned_post.rb +647 -0
- data/lib/aws-sdk-s3/presigner.rb +160 -0
- data/lib/aws-sdk-s3/resource.rb +96 -0
- data/lib/aws-sdk-s3/types.rb +5750 -0
- data/lib/aws-sdk-s3/waiters.rb +178 -0
- metadata +154 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
|
5
|
+
# Amazon S3 requires DNS style addressing for buckets outside of
|
6
|
+
# the classic region when possible.
|
7
|
+
class BucketDns < Seahorse::Client::Plugin
|
8
|
+
|
9
|
+
# When set to `false` DNS compatible bucket names are moved from
|
10
|
+
# the request URI path to the host as a subdomain, unless the request
|
11
|
+
# is using SSL and the bucket name contains a dot.
|
12
|
+
#
|
13
|
+
# When set to `true`, the bucket name is always forced to be part
|
14
|
+
# of the request URI path. This will not work with buckets outside
|
15
|
+
# the classic region.
|
16
|
+
option(:force_path_style,
|
17
|
+
default: false,
|
18
|
+
doc_type: 'Boolean',
|
19
|
+
docstring: <<-DOCS)
|
20
|
+
When set to `true`, the bucket name is always left in the
|
21
|
+
request URI and never moved to the host as a sub-domain.
|
22
|
+
DOCS
|
23
|
+
|
24
|
+
def add_handlers(handlers, config)
|
25
|
+
handlers.add(Handler) unless config.force_path_style
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
class Handler < Seahorse::Client::Handler
|
30
|
+
|
31
|
+
def call(context)
|
32
|
+
move_dns_compat_bucket_to_subdomain(context)
|
33
|
+
@handler.call(context)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def move_dns_compat_bucket_to_subdomain(context)
|
39
|
+
bucket_name = context.params[:bucket]
|
40
|
+
endpoint = context.http_request.endpoint
|
41
|
+
if
|
42
|
+
bucket_name &&
|
43
|
+
BucketDns.dns_compatible?(bucket_name, https?(endpoint)) &&
|
44
|
+
context.operation_name != 'get_bucket_location'
|
45
|
+
then
|
46
|
+
move_bucket_to_subdomain(bucket_name, endpoint)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def move_bucket_to_subdomain(bucket_name, endpoint)
|
51
|
+
endpoint.host = "#{bucket_name}.#{endpoint.host}"
|
52
|
+
path = endpoint.path.sub("/#{bucket_name}", '')
|
53
|
+
path = "/#{path}" unless path.match(/^\//)
|
54
|
+
endpoint.path = path
|
55
|
+
end
|
56
|
+
|
57
|
+
def https?(uri)
|
58
|
+
uri.scheme == 'https'
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
|
65
|
+
# @param [String] bucket_name
|
66
|
+
# @param [Boolean] ssl
|
67
|
+
# @return [Boolean]
|
68
|
+
def dns_compatible?(bucket_name, ssl)
|
69
|
+
if valid_subdomain?(bucket_name)
|
70
|
+
bucket_name.match(/\./) && ssl ? false : true
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def valid_subdomain?(bucket_name)
|
79
|
+
bucket_name.size < 64 &&
|
80
|
+
bucket_name =~ /^[a-z0-9][a-z0-9.-]+[a-z0-9]$/ &&
|
81
|
+
bucket_name !~ /(\d+\.){3}\d+/ &&
|
82
|
+
bucket_name !~ /[.-]{2}/
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
# @api private
|
5
|
+
class BucketNameRestrictions < Seahorse::Client::Plugin
|
6
|
+
class Handler < Seahorse::Client::Handler
|
7
|
+
|
8
|
+
def call(context)
|
9
|
+
if context.params.key?(:bucket) && context.params[:bucket].include?('/')
|
10
|
+
msg = ":bucket option must not contain a forward-slash (/)"
|
11
|
+
raise ArgumentError, msg
|
12
|
+
end
|
13
|
+
@handler.call(context)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
handler(Handler)
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
# @api private
|
5
|
+
class Dualstack < Seahorse::Client::Plugin
|
6
|
+
|
7
|
+
option(:use_dualstack_endpoint,
|
8
|
+
default: false,
|
9
|
+
doc_type: 'Boolean',
|
10
|
+
docstring: <<-DOCS)
|
11
|
+
When set to `true`, IPv6-compatible bucket endpoints will be used
|
12
|
+
for all operations.
|
13
|
+
DOCS
|
14
|
+
|
15
|
+
def add_handlers(handlers, config)
|
16
|
+
handlers.add(OptionHandler, step: :initialize)
|
17
|
+
handlers.add(DualstackHandler, step: :build, priority: 0)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
class OptionHandler < Seahorse::Client::Handler
|
22
|
+
def call(context)
|
23
|
+
dualstack = context.params.delete(:use_dualstack_endpoint)
|
24
|
+
dualstack = context.config.use_dualstack_endpoint if dualstack.nil?
|
25
|
+
context[:use_dualstack_endpoint] = dualstack
|
26
|
+
@handler.call(context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
class DualstackHandler < Seahorse::Client::Handler
|
32
|
+
def call(context)
|
33
|
+
apply_dualstack_endpoint(context) if use_dualstack_endpoint?(context)
|
34
|
+
@handler.call(context)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def apply_dualstack_endpoint(context)
|
39
|
+
bucket_name = context.params[:bucket]
|
40
|
+
region = context.config.region
|
41
|
+
force_path_style = context.config.force_path_style
|
42
|
+
dns_suffix = Aws::Partitions::EndpointProvider.dns_suffix_for(region)
|
43
|
+
|
44
|
+
if use_bucket_dns?(bucket_name, context)
|
45
|
+
host = "#{bucket_name}.s3.dualstack.#{region}.#{dns_suffix}"
|
46
|
+
else
|
47
|
+
host = "s3.dualstack.#{region}.#{dns_suffix}"
|
48
|
+
end
|
49
|
+
endpoint = URI.parse(context.http_request.endpoint.to_s)
|
50
|
+
endpoint.scheme = context.http_request.endpoint.scheme
|
51
|
+
endpoint.port = context.http_request.endpoint.port
|
52
|
+
endpoint.host = host
|
53
|
+
context.http_request.endpoint = endpoint.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def use_bucket_dns?(bucket_name, context)
|
57
|
+
ssl = context.http_request.endpoint.scheme == "https"
|
58
|
+
bucket_name && BucketDns.dns_compatible?(bucket_name, ssl) &&
|
59
|
+
!context.config.force_path_style
|
60
|
+
end
|
61
|
+
|
62
|
+
def use_dualstack_endpoint?(context)
|
63
|
+
context[:use_dualstack_endpoint] && !context[:use_accelerate_endpoint]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
class Expect100Continue < Seahorse::Client::Plugin
|
5
|
+
|
6
|
+
def add_handlers(handlers, config)
|
7
|
+
if config.http_continue_timeout && config.http_continue_timeout > 0
|
8
|
+
handlers.add(Handler)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
class Handler < Seahorse::Client::Handler
|
14
|
+
|
15
|
+
def call(context)
|
16
|
+
if
|
17
|
+
context.http_request.body &&
|
18
|
+
context.http_request.body.size > 0
|
19
|
+
then
|
20
|
+
context.http_request.headers['expect'] = '100-continue'
|
21
|
+
end
|
22
|
+
@handler.call(context)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
class GetBucketLocationFix < Seahorse::Client::Plugin
|
5
|
+
|
6
|
+
class Handler < Seahorse::Client::Handler
|
7
|
+
|
8
|
+
def call(context)
|
9
|
+
@handler.call(context).on(200) do |response|
|
10
|
+
response.data = S3::Types::GetBucketLocationOutput.new
|
11
|
+
xml = context.http_response.body_contents
|
12
|
+
matches = xml.match(/>(.+?)<\/LocationConstraint>/)
|
13
|
+
response.data[:location_constraint] = matches ? matches[1] : ''
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
handler(Handler, priority: 60, operations: [:get_bucket_location])
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
|
5
|
+
# A handful of Amazon S3 operations will respond with a 200 status
|
6
|
+
# code but will send an error in the response body. This plugin
|
7
|
+
# injects a handler that will parse 200 response bodies for potential
|
8
|
+
# errors, allowing them to be retried.
|
9
|
+
# @api private
|
10
|
+
class Http200Errors < Seahorse::Client::Plugin
|
11
|
+
|
12
|
+
class Handler < Seahorse::Client::Handler
|
13
|
+
|
14
|
+
def call(context)
|
15
|
+
@handler.call(context).on(200) do |response|
|
16
|
+
if error = check_for_error(context)
|
17
|
+
context.http_response.status_code = 500
|
18
|
+
response.data = nil
|
19
|
+
response.error = error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_for_error(context)
|
25
|
+
xml = context.http_response.body_contents
|
26
|
+
if xml.match(/<Error>/)
|
27
|
+
error_code = xml.match(/<Code>(.+?)<\/Code>/)[1]
|
28
|
+
error_message = xml.match(/<Message>(.+?)<\/Message>/)[1]
|
29
|
+
S3::Errors.error_class(error_code).new(context, error_message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
handler(Handler,
|
36
|
+
step: :sign,
|
37
|
+
operations: [
|
38
|
+
:complete_multipart_upload,
|
39
|
+
:copy_object,
|
40
|
+
:upload_part_copy,
|
41
|
+
]
|
42
|
+
)
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
|
5
|
+
# When making calls to {S3::Client#create_bucket} outside the
|
6
|
+
# "classic" region, the bucket location constraint must be specified.
|
7
|
+
# This plugin auto populates the constraint to the configured region.
|
8
|
+
class LocationConstraint < Seahorse::Client::Plugin
|
9
|
+
|
10
|
+
class Handler < Seahorse::Client::Handler
|
11
|
+
|
12
|
+
def call(context)
|
13
|
+
unless context.config.region == 'us-east-1'
|
14
|
+
populate_location_constraint(context.params, context.config.region)
|
15
|
+
end
|
16
|
+
@handler.call(context)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def populate_location_constraint(params, region)
|
22
|
+
params[:create_bucket_configuration] ||= {}
|
23
|
+
params[:create_bucket_configuration][:location_constraint] ||= region
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
handler(Handler, step: :initialize, operations: [:create_bucket])
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Aws
|
5
|
+
module S3
|
6
|
+
module Plugins
|
7
|
+
class Md5s < Seahorse::Client::Plugin
|
8
|
+
|
9
|
+
# Amazon S3 requires these operations to have an MD5 checksum
|
10
|
+
REQUIRED_OPERATIONS = [
|
11
|
+
:delete_objects,
|
12
|
+
:put_bucket_cors,
|
13
|
+
:put_bucket_lifecycle,
|
14
|
+
:put_bucket_policy,
|
15
|
+
:put_bucket_tagging,
|
16
|
+
]
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
class Handler < Seahorse::Client::Handler
|
20
|
+
|
21
|
+
CHUNK_SIZE = 1 * 1024 * 1024 # one MB
|
22
|
+
|
23
|
+
def call(context)
|
24
|
+
body = context.http_request.body
|
25
|
+
if body.size > 0
|
26
|
+
context.http_request.headers['Content-Md5'] ||= md5(body)
|
27
|
+
end
|
28
|
+
@handler.call(context)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @param [File, Tempfile, IO#read, String] value
|
34
|
+
# @return [String<MD5>]
|
35
|
+
def md5(value)
|
36
|
+
if (File === value || Tempfile === value) && !value.path.nil? && File.exist?(value.path)
|
37
|
+
Base64.encode64(OpenSSL::Digest::MD5.file(value).digest).strip
|
38
|
+
elsif value.respond_to?(:read)
|
39
|
+
md5 = OpenSSL::Digest::MD5.new
|
40
|
+
update_in_chunks(md5, value)
|
41
|
+
Base64.encode64(md5.digest).strip
|
42
|
+
else
|
43
|
+
Base64.encode64(OpenSSL::Digest::MD5.digest(value)).strip
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_in_chunks(digest, io)
|
48
|
+
while chunk = io.read(CHUNK_SIZE)
|
49
|
+
digest.update(chunk)
|
50
|
+
end
|
51
|
+
io.rewind
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
option(:compute_checksums,
|
57
|
+
default: true,
|
58
|
+
doc_type: 'Boolean',
|
59
|
+
docstring: <<-DOCS)
|
60
|
+
When `true` a MD5 checksum will be computed for every request that
|
61
|
+
sends a body. When `false`, MD5 checksums will only be computed
|
62
|
+
for operations that require them. Checksum errors returned by Amazon
|
63
|
+
S3 are automatically retried up to `:retry_limit` times.
|
64
|
+
DOCS
|
65
|
+
|
66
|
+
def add_handlers(handlers, config)
|
67
|
+
# priority set low to ensure md5 is computed AFTER the request is
|
68
|
+
# built but before it is signed
|
69
|
+
handlers.add(Handler, {
|
70
|
+
priority: 10,
|
71
|
+
step: :build,
|
72
|
+
operations: config.compute_checksums ? nil : REQUIRED_OPERATIONS,
|
73
|
+
})
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Plugins
|
4
|
+
class Redirects < Seahorse::Client::Plugin
|
5
|
+
|
6
|
+
option(:follow_redirects,
|
7
|
+
default: true,
|
8
|
+
doc_type: 'Boolean',
|
9
|
+
docstring: <<-DOCS)
|
10
|
+
When `true`, this client will follow 307 redirects returned
|
11
|
+
by Amazon S3.
|
12
|
+
DOCS
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
class Handler < Seahorse::Client::Handler
|
16
|
+
|
17
|
+
def call(context)
|
18
|
+
response = @handler.call(context)
|
19
|
+
if context.http_response.status_code == 307
|
20
|
+
endpoint = context.http_response.headers['location']
|
21
|
+
context.http_request.endpoint = endpoint
|
22
|
+
context.http_response.body.truncate(0)
|
23
|
+
@handler.call(context)
|
24
|
+
else
|
25
|
+
response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_handlers(handlers, config)
|
32
|
+
if config.follow_redirects
|
33
|
+
# we want to re-trigger request signing
|
34
|
+
handlers.add(Handler, step: :sign, priority: 90)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'aws-sigv4'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Plugins
|
6
|
+
# This plugin is an implementation detail and may be modified.
|
7
|
+
# @api private
|
8
|
+
class S3Signer < Seahorse::Client::Plugin
|
9
|
+
|
10
|
+
option(:signature_version, 'v4')
|
11
|
+
|
12
|
+
option(:sigv4_signer) do |cfg|
|
13
|
+
S3Signer.build_v4_signer(
|
14
|
+
region: cfg.sigv4_region,
|
15
|
+
credentials: cfg.credentials
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
option(:sigv4_region) do |cfg|
|
20
|
+
Aws::Partitions::EndpointProvider.signing_region(cfg.region, 's3')
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_handlers(handlers, cfg)
|
24
|
+
case cfg.signature_version
|
25
|
+
when 'v4' then add_v4_handlers(handlers)
|
26
|
+
when 's3' then add_legacy_handler(handlers)
|
27
|
+
else
|
28
|
+
msg = "unsupported signature version `#{cfg.signature_version}'"
|
29
|
+
raise ArgumentError, msg
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_v4_handlers(handlers)
|
34
|
+
handlers.add(CachedBucketRegionHandler, step: :sign, priority: 60)
|
35
|
+
handlers.add(V4Handler, step: :sign)
|
36
|
+
handlers.add(BucketRegionErrorHandler, step: :sign, priority: 40)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_legacy_handler(handlers)
|
40
|
+
handlers.add(LegacyHandler, step: :sign)
|
41
|
+
end
|
42
|
+
|
43
|
+
class LegacyHandler < Seahorse::Client::Handler
|
44
|
+
def call(context)
|
45
|
+
LegacySigner.sign(context)
|
46
|
+
@handler.call(context)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class V4Handler < Seahorse::Client::Handler
|
51
|
+
|
52
|
+
def call(context)
|
53
|
+
Aws::Plugins::SignatureV4.apply_signature(
|
54
|
+
context: context,
|
55
|
+
signer: sigv4_signer(context)
|
56
|
+
)
|
57
|
+
@handler.call(context)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def sigv4_signer(context)
|
63
|
+
# If the client was configured with the wrong region,
|
64
|
+
# we have to build a new signer.
|
65
|
+
if
|
66
|
+
context[:cached_sigv4_region] &&
|
67
|
+
context[:cached_sigv4_region] != context.config.sigv4_signer.region
|
68
|
+
then
|
69
|
+
S3Signer.build_v4_signer(
|
70
|
+
region: context[:cached_sigv4_region],
|
71
|
+
credentials: context.config.credentials
|
72
|
+
)
|
73
|
+
else
|
74
|
+
context.config.sigv4_signer
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
# This handler will update the http endpoint when the bucket region
|
81
|
+
# is known/cached.
|
82
|
+
class CachedBucketRegionHandler < Seahorse::Client::Handler
|
83
|
+
|
84
|
+
def call(context)
|
85
|
+
bucket = context.params[:bucket]
|
86
|
+
check_for_cached_region(context, bucket) if bucket
|
87
|
+
@handler.call(context)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def check_for_cached_region(context, bucket)
|
93
|
+
cached_region = S3::BUCKET_REGIONS[bucket]
|
94
|
+
if cached_region && cached_region != context.config.region
|
95
|
+
context.http_request.endpoint.host = S3Signer.new_hostname(context, cached_region)
|
96
|
+
context[:cached_sigv4_region] = cached_region
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
# This handler detects when a request fails because of a mismatched bucket
|
103
|
+
# region. It follows up by making a request to determine the correct
|
104
|
+
# region, then finally a version 4 signed request against the correct
|
105
|
+
# regional endpoint.
|
106
|
+
class BucketRegionErrorHandler < Seahorse::Client::Handler
|
107
|
+
|
108
|
+
def call(context)
|
109
|
+
response = @handler.call(context)
|
110
|
+
handle_region_errors(response)
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def handle_region_errors(response)
|
116
|
+
if wrong_sigv4_region?(response)
|
117
|
+
get_region_and_retry(response.context)
|
118
|
+
else
|
119
|
+
response
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_region_and_retry(context)
|
124
|
+
actual_region = context.http_response.headers['x-amz-bucket-region']
|
125
|
+
actual_region ||= region_from_body(context.http_response.body_contents)
|
126
|
+
update_bucket_cache(context, actual_region)
|
127
|
+
log_warning(context, actual_region)
|
128
|
+
resign_with_new_region(context, actual_region)
|
129
|
+
@handler.call(context)
|
130
|
+
end
|
131
|
+
|
132
|
+
def update_bucket_cache(context, actual_region)
|
133
|
+
S3::BUCKET_REGIONS[context.params[:bucket]] = actual_region
|
134
|
+
end
|
135
|
+
|
136
|
+
def wrong_sigv4_region?(resp)
|
137
|
+
resp.context.http_response.status_code == 400 &&
|
138
|
+
(
|
139
|
+
resp.context.http_response.headers['x-amz-bucket-region'] ||
|
140
|
+
resp.context.http_response.body_contents.match(/<Region>.+?<\/Region>/)
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
def resign_with_new_region(context, actual_region)
|
145
|
+
context.http_response.body.truncate(0)
|
146
|
+
context.http_request.endpoint.host = S3Signer.new_hostname(context, actual_region)
|
147
|
+
Aws::Plugins::SignatureV4.apply_signature(
|
148
|
+
context: context,
|
149
|
+
signer: S3Signer.build_v4_signer(
|
150
|
+
region: actual_region,
|
151
|
+
credentials: context.config.credentials
|
152
|
+
)
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
def region_from_body(body)
|
157
|
+
region = body.match(/<Region>(.+?)<\/Region>/)[1]
|
158
|
+
if region.nil? || region == ""
|
159
|
+
raise "couldn't get region from body: #{body}"
|
160
|
+
else
|
161
|
+
region
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def log_warning(context, actual_region)
|
166
|
+
msg = "S3 client configured for #{context.config.region.inspect} " +
|
167
|
+
"but the bucket #{context.params[:bucket].inspect} is in " +
|
168
|
+
"#{actual_region.inspect}; Please configure the proper region " +
|
169
|
+
"to avoid multiple unnecessary redirects and signing attempts\n"
|
170
|
+
if logger = context.config.logger
|
171
|
+
logger.warn(msg)
|
172
|
+
else
|
173
|
+
warn(msg)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
class << self
|
180
|
+
|
181
|
+
# @option options [required, String] :region
|
182
|
+
# @option options [required, #credentials] :credentials
|
183
|
+
# @api private
|
184
|
+
def build_v4_signer(options = {})
|
185
|
+
Aws::Sigv4::Signer.new({
|
186
|
+
service: 's3',
|
187
|
+
region: options[:region],
|
188
|
+
credentials_provider: options[:credentials],
|
189
|
+
uri_escape_path: false,
|
190
|
+
unsigned_headers: ['content-length'],
|
191
|
+
})
|
192
|
+
end
|
193
|
+
|
194
|
+
def new_hostname(context, region)
|
195
|
+
bucket = context.params[:bucket]
|
196
|
+
if region == 'us-east-1'
|
197
|
+
"#{bucket}.s3.amazonaws.com"
|
198
|
+
else
|
199
|
+
endpoint = Aws::Partitions::EndpointProvider.resolve(region, 's3')
|
200
|
+
bucket + '.' + URI.parse(endpoint).host
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|