aws-sdk-s3 1.0.0.rc1
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.
- 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
|