aws-sdk-s3 1.61.1 → 1.166.0

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.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1255 -0
  3. data/LICENSE.txt +202 -0
  4. data/VERSION +1 -0
  5. data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
  6. data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
  7. data/lib/aws-sdk-s3/bucket.rb +814 -112
  8. data/lib/aws-sdk-s3/bucket_acl.rb +54 -17
  9. data/lib/aws-sdk-s3/bucket_cors.rb +63 -20
  10. data/lib/aws-sdk-s3/bucket_lifecycle.rb +63 -19
  11. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +64 -19
  12. data/lib/aws-sdk-s3/bucket_logging.rb +58 -17
  13. data/lib/aws-sdk-s3/bucket_notification.rb +47 -20
  14. data/lib/aws-sdk-s3/bucket_policy.rb +104 -17
  15. data/lib/aws-sdk-s3/bucket_region_cache.rb +11 -5
  16. data/lib/aws-sdk-s3/bucket_request_payment.rb +53 -19
  17. data/lib/aws-sdk-s3/bucket_tagging.rb +60 -17
  18. data/lib/aws-sdk-s3/bucket_versioning.rb +106 -17
  19. data/lib/aws-sdk-s3/bucket_website.rb +64 -19
  20. data/lib/aws-sdk-s3/client.rb +11280 -3232
  21. data/lib/aws-sdk-s3/client_api.rb +1078 -197
  22. data/lib/aws-sdk-s3/customizations/bucket.rb +33 -45
  23. data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
  24. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  25. data/lib/aws-sdk-s3/customizations/object.rb +237 -39
  26. data/lib/aws-sdk-s3/customizations/object_summary.rb +10 -0
  27. data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
  28. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  29. data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
  30. data/lib/aws-sdk-s3/customizations.rb +27 -28
  31. data/lib/aws-sdk-s3/encryption/client.rb +25 -8
  32. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
  33. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  34. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  35. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  36. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  38. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  39. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  40. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  41. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +46 -11
  42. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  43. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  44. data/lib/aws-sdk-s3/encryption.rb +4 -0
  45. data/lib/aws-sdk-s3/encryptionV2/client.rb +570 -0
  46. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +223 -0
  47. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  48. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  49. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +65 -0
  50. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  51. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  52. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  53. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  54. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  55. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +173 -0
  56. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  57. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  58. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  59. data/lib/aws-sdk-s3/endpoint_parameters.rb +186 -0
  60. data/lib/aws-sdk-s3/endpoint_provider.rb +592 -0
  61. data/lib/aws-sdk-s3/endpoints.rb +2392 -0
  62. data/lib/aws-sdk-s3/errors.rb +27 -1
  63. data/lib/aws-sdk-s3/event_streams.rb +8 -1
  64. data/lib/aws-sdk-s3/express_credentials.rb +55 -0
  65. data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
  66. data/lib/aws-sdk-s3/file_downloader.rb +176 -44
  67. data/lib/aws-sdk-s3/file_part.rb +2 -0
  68. data/lib/aws-sdk-s3/file_uploader.rb +28 -8
  69. data/lib/aws-sdk-s3/legacy_signer.rb +17 -25
  70. data/lib/aws-sdk-s3/multipart_file_uploader.rb +69 -13
  71. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +49 -18
  72. data/lib/aws-sdk-s3/multipart_upload.rb +242 -25
  73. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  74. data/lib/aws-sdk-s3/multipart_upload_part.rb +343 -31
  75. data/lib/aws-sdk-s3/object.rb +2240 -218
  76. data/lib/aws-sdk-s3/object_acl.rb +80 -21
  77. data/lib/aws-sdk-s3/object_copier.rb +9 -5
  78. data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -22
  79. data/lib/aws-sdk-s3/object_summary.rb +1900 -178
  80. data/lib/aws-sdk-s3/object_version.rb +449 -59
  81. data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
  82. data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
  83. data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
  84. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +5 -41
  85. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +3 -6
  86. data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
  87. data/lib/aws-sdk-s3/plugins/endpoints.rb +289 -0
  88. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
  89. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +97 -0
  90. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  91. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +60 -15
  92. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +11 -27
  93. data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
  94. data/lib/aws-sdk-s3/plugins/md5s.rb +32 -26
  95. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  96. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  97. data/lib/aws-sdk-s3/plugins/s3_signer.rb +50 -88
  98. data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +31 -0
  99. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  100. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +139 -0
  101. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  102. data/lib/aws-sdk-s3/presigned_post.rb +160 -103
  103. data/lib/aws-sdk-s3/presigner.rb +136 -61
  104. data/lib/aws-sdk-s3/resource.rb +120 -15
  105. data/lib/aws-sdk-s3/types.rb +9683 -4358
  106. data/lib/aws-sdk-s3/waiters.rb +67 -1
  107. data/lib/aws-sdk-s3.rb +40 -29
  108. data/sig/bucket.rbs +213 -0
  109. data/sig/bucket_acl.rbs +78 -0
  110. data/sig/bucket_cors.rbs +69 -0
  111. data/sig/bucket_lifecycle.rbs +88 -0
  112. data/sig/bucket_lifecycle_configuration.rbs +111 -0
  113. data/sig/bucket_logging.rbs +76 -0
  114. data/sig/bucket_notification.rbs +114 -0
  115. data/sig/bucket_policy.rbs +59 -0
  116. data/sig/bucket_request_payment.rbs +54 -0
  117. data/sig/bucket_tagging.rbs +65 -0
  118. data/sig/bucket_versioning.rbs +77 -0
  119. data/sig/bucket_website.rbs +93 -0
  120. data/sig/client.rbs +2397 -0
  121. data/sig/customizations/bucket.rbs +19 -0
  122. data/sig/customizations/object.rbs +38 -0
  123. data/sig/customizations/object_summary.rbs +35 -0
  124. data/sig/errors.rbs +34 -0
  125. data/sig/multipart_upload.rbs +111 -0
  126. data/sig/multipart_upload_part.rbs +105 -0
  127. data/sig/object.rbs +443 -0
  128. data/sig/object_acl.rbs +86 -0
  129. data/sig/object_summary.rbs +335 -0
  130. data/sig/object_version.rbs +137 -0
  131. data/sig/resource.rbs +130 -0
  132. data/sig/types.rbs +2585 -0
  133. data/sig/waiters.rbs +95 -0
  134. metadata +73 -15
  135. data/lib/aws-sdk-s3/plugins/bucket_arn.rb +0 -211
@@ -1,20 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module Aws
4
6
  module S3
5
7
  module Plugins
8
+ # @api private
9
+ # This plugin is effectively deprecated in favor of modeled
10
+ # httpChecksumRequired traits.
6
11
  class Md5s < Seahorse::Client::Plugin
7
-
8
- # Amazon S3 requires these operations to have an MD5 checksum
9
- REQUIRED_OPERATIONS = [
10
- :delete_objects,
11
- :put_bucket_cors,
12
- :put_bucket_lifecycle,
13
- :put_bucket_policy,
14
- :put_bucket_tagging,
15
- :put_object_legal_hold,
16
- :put_object_lock_configuration,
17
- :put_object_retention
12
+ # These operations allow Content MD5 but are not required by
13
+ # httpChecksumRequired. This list should not grow.
14
+ OPTIONAL_OPERATIONS = [
15
+ :put_object,
16
+ :upload_part
18
17
  ]
19
18
 
20
19
  # @api private
@@ -23,9 +22,12 @@ module Aws
23
22
  CHUNK_SIZE = 1 * 1024 * 1024 # one MB
24
23
 
25
24
  def call(context)
26
- body = context.http_request.body
27
- if body.size > 0
28
- context.http_request.headers['Content-Md5'] ||= md5(body)
25
+ if !context[:checksum_algorithms] && # skip in favor of flexible checksum
26
+ !context[:s3_express_endpoint] # s3 express endpoints do not support md5
27
+ body = context.http_request.body
28
+ if body.respond_to?(:size) && body.size > 0
29
+ context.http_request.headers['Content-Md5'] ||= md5(body)
30
+ end
29
31
  end
30
32
  @handler.call(context)
31
33
  end
@@ -47,7 +49,9 @@ module Aws
47
49
  end
48
50
 
49
51
  def update_in_chunks(digest, io)
50
- while chunk = io.read(CHUNK_SIZE, buffer ||= "")
52
+ loop do
53
+ chunk = io.read(CHUNK_SIZE)
54
+ break unless chunk
51
55
  digest.update(chunk)
52
56
  end
53
57
  io.rewind
@@ -59,20 +63,22 @@ module Aws
59
63
  default: true,
60
64
  doc_type: 'Boolean',
61
65
  docstring: <<-DOCS)
62
- When `true` a MD5 checksum will be computed for every request that
63
- sends a body. When `false`, MD5 checksums will only be computed
64
- for operations that require them. Checksum errors returned by Amazon
65
- S3 are automatically retried up to `:retry_limit` times.
66
+ When `true` a MD5 checksum will be computed and sent in the Content Md5
67
+ header for :put_object and :upload_part. When `false`, MD5 checksums
68
+ will not be computed for these operations. Checksums are still computed
69
+ for operations requiring them. Checksum errors returned by Amazon S3 are
70
+ automatically retried up to `:retry_limit` times.
66
71
  DOCS
67
72
 
68
73
  def add_handlers(handlers, config)
69
- # priority set low to ensure md5 is computed AFTER the request is
70
- # built but before it is signed
71
- handlers.add(Handler, {
72
- priority: 10,
73
- step: :build,
74
- operations: config.compute_checksums ? nil : REQUIRED_OPERATIONS,
75
- })
74
+ if config.compute_checksums
75
+ # priority set low to ensure md5 is computed AFTER the request is
76
+ # built but before it is signed
77
+ handlers.add(
78
+ Handler,
79
+ priority: 10, step: :build, operations: OPTIONAL_OPERATIONS
80
+ )
81
+ end
76
82
  end
77
83
 
78
84
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Plugins
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
  module Plugins
@@ -1,26 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'aws-sigv4'
2
4
 
3
5
  module Aws
4
6
  module S3
7
+ # @api private
8
+ def self.bucket_region_cache
9
+ @bucket_region_cache ||= BucketRegionCache.new
10
+ end
11
+
5
12
  module Plugins
6
- # This plugin is an implementation detail and may be modified.
13
+ # This plugin used to have a V4 signer but it was removed in favor of
14
+ # generic Sign plugin that uses endpoint auth scheme.
15
+ #
7
16
  # @api private
8
17
  class S3Signer < Seahorse::Client::Plugin
9
18
  option(:signature_version, 'v4')
10
19
 
11
- option(:sigv4_signer) do |cfg|
12
- S3Signer.build_v4_signer(
13
- region: cfg.sigv4_region,
14
- credentials: cfg.credentials
15
- )
16
- end
17
-
18
- option(:sigv4_region) do |cfg|
19
- raise Aws::Errors::MissingRegionError if cfg.region.nil?
20
-
21
- Aws::Partitions::EndpointProvider.signing_region(cfg.region, 's3')
22
- end
23
-
24
20
  def add_handlers(handlers, cfg)
25
21
  case cfg.signature_version
26
22
  when 'v4' then add_v4_handlers(handlers)
@@ -33,11 +29,11 @@ module Aws
33
29
 
34
30
  def add_v4_handlers(handlers)
35
31
  handlers.add(CachedBucketRegionHandler, step: :sign, priority: 60)
36
- handlers.add(V4Handler, step: :sign)
37
32
  handlers.add(BucketRegionErrorHandler, step: :sign, priority: 40)
38
33
  end
39
34
 
40
35
  def add_legacy_handler(handlers)
36
+ # generic Sign plugin will be skipped if it sees sigv2
41
37
  handlers.add(LegacyHandler, step: :sign)
42
38
  end
43
39
 
@@ -48,32 +44,6 @@ module Aws
48
44
  end
49
45
  end
50
46
 
51
- class V4Handler < Seahorse::Client::Handler
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 context[:cached_sigv4_region] &&
66
- context[:cached_sigv4_region] != context.config.sigv4_signer.region
67
- S3Signer.build_v4_signer(
68
- region: context[:cached_sigv4_region],
69
- credentials: context.config.credentials
70
- )
71
- else
72
- context.config.sigv4_signer
73
- end
74
- end
75
- end
76
-
77
47
  # This handler will update the http endpoint when the bucket region
78
48
  # is known/cached.
79
49
  class CachedBucketRegionHandler < Seahorse::Client::Handler
@@ -86,10 +56,14 @@ module Aws
86
56
  private
87
57
 
88
58
  def check_for_cached_region(context, bucket)
89
- cached_region = S3::BUCKET_REGIONS[bucket]
90
- if cached_region && cached_region != context.config.region
91
- context.http_request.endpoint.host = S3Signer.new_hostname(context, cached_region)
92
- context[:cached_sigv4_region] = cached_region
59
+ cached_region = Aws::S3.bucket_region_cache[bucket]
60
+ if cached_region &&
61
+ cached_region != context.config.region &&
62
+ !S3Signer.custom_endpoint?(context)
63
+ context.http_request.endpoint.host = S3Signer.new_hostname(
64
+ context, cached_region
65
+ )
66
+ context[:sigv4_region] = cached_region # Sign plugin will use this
93
67
  end
94
68
  end
95
69
  end
@@ -97,7 +71,8 @@ module Aws
97
71
  # This handler detects when a request fails because of a mismatched bucket
98
72
  # region. It follows up by making a request to determine the correct
99
73
  # region, then finally a version 4 signed request against the correct
100
- # regional endpoint.
74
+ # regional endpoint. This is intended for s3's global endpoint which
75
+ # will return 400 if the bucket is not in region.
101
76
  class BucketRegionErrorHandler < Seahorse::Client::Handler
102
77
  def call(context)
103
78
  response = @handler.call(context)
@@ -109,7 +84,8 @@ module Aws
109
84
  def handle_region_errors(response)
110
85
  if wrong_sigv4_region?(response) &&
111
86
  !fips_region?(response) &&
112
- !custom_endpoint?(response)
87
+ !S3Signer.custom_endpoint?(response.context) &&
88
+ !expired_credentials?(response)
113
89
  get_region_and_retry(response.context)
114
90
  else
115
91
  response
@@ -126,18 +102,15 @@ module Aws
126
102
  end
127
103
 
128
104
  def update_bucket_cache(context, actual_region)
129
- S3::BUCKET_REGIONS[context.params[:bucket]] = actual_region
105
+ Aws::S3.bucket_region_cache[context.params[:bucket]] = actual_region
130
106
  end
131
107
 
132
108
  def fips_region?(resp)
133
- resp.context.http_request.endpoint.host.include?('fips')
109
+ resp.context.http_request.endpoint.host.include?('s3-fips.')
134
110
  end
135
111
 
136
- def custom_endpoint?(resp)
137
- resolved_suffix = Aws::Partitions::EndpointProvider.dns_suffix_for(
138
- resp.context.config.region
139
- )
140
- !resp.context.http_request.endpoint.hostname.include?(resolved_suffix)
112
+ def expired_credentials?(resp)
113
+ resp.context.http_response.body_contents.match(/<Code>ExpiredToken<\/Code>/)
141
114
  end
142
115
 
143
116
  def wrong_sigv4_region?(resp)
@@ -148,15 +121,18 @@ module Aws
148
121
 
149
122
  def resign_with_new_region(context, actual_region)
150
123
  context.http_response.body.truncate(0)
151
- context.http_request.endpoint.host = S3Signer.new_hostname(context, actual_region)
124
+ context.http_request.endpoint.host = S3Signer.new_hostname(
125
+ context, actual_region
126
+ )
152
127
  context.metadata[:redirect_region] = actual_region
153
- Aws::Plugins::SignatureV4.apply_signature(
154
- context: context,
155
- signer: S3Signer.build_v4_signer(
156
- region: actual_region,
157
- credentials: context.config.credentials
158
- )
128
+
129
+ signer = Aws::Plugins::Sign.signer_for(
130
+ context[:auth_scheme],
131
+ context.config,
132
+ actual_region
159
133
  )
134
+
135
+ signer.sign(context)
160
136
  end
161
137
 
162
138
  def region_from_body(body)
@@ -182,36 +158,22 @@ module Aws
182
158
  end
183
159
 
184
160
  class << self
185
- # @option options [required, String] :region
186
- # @option options [required, #credentials] :credentials
187
- # @api private
188
- def build_v4_signer(options = {})
189
- Aws::Sigv4::Signer.new(
190
- service: 's3',
191
- region: options[:region],
192
- credentials_provider: options[:credentials],
193
- uri_escape_path: false,
194
- unsigned_headers: ['content-length', 'x-amzn-trace-id']
195
- )
161
+ def new_hostname(context, region)
162
+ endpoint_params = context[:endpoint_params].dup
163
+ endpoint_params.region = region
164
+ endpoint_params.endpoint = nil
165
+ endpoint =
166
+ context.config.endpoint_provider.resolve_endpoint(endpoint_params)
167
+ URI(endpoint.url).host
196
168
  end
197
169
 
198
- def new_hostname(context, region)
199
- # Check to see if the bucket is actually an ARN and resolve it
200
- # Otherwise it will retry with the ARN as the bucket name.
201
- resolved_bucket, resolved_region, arn = BucketARN.resolve_arn!(
202
- context.params[:bucket],
203
- region,
204
- context.config.s3_use_arn_region
205
- )
206
- uri = URI.parse(
207
- Aws::Partitions::EndpointProvider.resolve(resolved_region, 's3')
208
- )
170
+ def custom_endpoint?(context)
171
+ region = context.config.region
172
+ partition = Aws::Endpoints::Matchers.aws_partition(region)
173
+ endpoint = context.http_request.endpoint
209
174
 
210
- if arn
211
- BucketARN.resolve_url!(uri, arn).host
212
- else
213
- resolved_bucket + '.' + uri.host
214
- end
175
+ !endpoint.hostname.include?(partition['dnsSuffix']) &&
176
+ !endpoint.hostname.include?(partition['dualStackDnsSuffix'])
215
177
  end
216
178
  end
217
179
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ module Plugins
6
+
7
+ # S3 GetObject results for whole Multipart Objects contain a checksum
8
+ # that cannot be validated. These should be skipped by the
9
+ # ChecksumAlgorithm plugin.
10
+ class SkipWholeMultipartGetChecksums < Seahorse::Client::Plugin
11
+
12
+ class Handler < Seahorse::Client::Handler
13
+
14
+ def call(context)
15
+ context[:http_checksum] ||= {}
16
+ context[:http_checksum][:skip_on_suffix] = true
17
+
18
+ @handler.call(context)
19
+ end
20
+
21
+ end
22
+
23
+ handler(
24
+ Handler,
25
+ step: :initialize,
26
+ operations: [:get_object]
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'openssl'
3
5
 
@@ -18,7 +20,7 @@ This should only be disabled for local testing.
18
20
  class Handler < Seahorse::Client::Handler
19
21
 
20
22
  def call(context)
21
- compute_key_md5(context)
23
+ compute_key_md5(context) if context.params.is_a?(Hash)
22
24
  @handler.call(context)
23
25
  end
24
26
 
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Aws
6
+ module S3
7
+ module Plugins
8
+
9
+ # A wrapper around BlockIO that adds no-ops for truncate and rewind
10
+ # @api private
11
+ class RetryableBlockIO
12
+ extend Forwardable
13
+ def_delegators :@block_io, :write, :read, :size
14
+
15
+ def initialize(block_io)
16
+ @block_io = block_io
17
+ end
18
+
19
+ def truncate(_integer); end
20
+
21
+ def rewind; end
22
+ end
23
+
24
+ # A wrapper around ManagedFile that adds no-ops for truncate and rewind
25
+ # @api private
26
+ class RetryableManagedFile
27
+ extend Forwardable
28
+ def_delegators :@file, :write, :read, :size, :open?, :close
29
+
30
+ def initialize(managed_file)
31
+ @file = managed_file
32
+ end
33
+
34
+ def truncate(_integer); end
35
+
36
+ def rewind; end
37
+ end
38
+
39
+ class NonRetryableStreamingError < StandardError
40
+
41
+ def initialize(error)
42
+ super('Unable to retry request - retry could result in processing duplicated chunks.')
43
+ set_backtrace(error.backtrace)
44
+ @original_error = error
45
+ end
46
+
47
+ attr_reader :original_error
48
+ end
49
+
50
+ # This handler works with the ResponseTarget plugin to provide smart
51
+ # retries of S3 streaming operations that support the range parameter
52
+ # (currently only: get_object). When a 200 OK with a TruncatedBodyError
53
+ # is received this handler will add a range header that excludes the
54
+ # data that has already been processed (written to file or sent to
55
+ # the target Proc).
56
+ # It is important to not write data to the custom target in the case of
57
+ # a non-success response. We do not want to write an XML error
58
+ # message to someone's file or pass it to a user's Proc.
59
+ # @api private
60
+ class StreamingRetry < Seahorse::Client::Plugin
61
+
62
+ class Handler < Seahorse::Client::Handler
63
+
64
+ def call(context)
65
+ target = context.params[:response_target] || context[:response_target]
66
+
67
+ # retry is only supported when range is NOT set on the initial request
68
+ if supported_target?(target) && !context.params[:range]
69
+ add_event_listeners(context, target)
70
+ end
71
+ @handler.call(context)
72
+ end
73
+
74
+ private
75
+
76
+ def add_event_listeners(context, target)
77
+ context.http_response.on_headers(200..299) do
78
+ case context.http_response.body
79
+ when Seahorse::Client::BlockIO then
80
+ context.http_response.body = RetryableBlockIO.new(context.http_response.body)
81
+ when Seahorse::Client::ManagedFile then
82
+ context.http_response.body = RetryableManagedFile.new(context.http_response.body)
83
+ end
84
+ end
85
+
86
+ context.http_response.on_headers(400..599) do
87
+ context.http_response.body = StringIO.new # something to write the error to
88
+ end
89
+
90
+ context.http_response.on_success(200..299) do
91
+ body = context.http_response.body
92
+ if body.is_a?(RetryableManagedFile) && body.open?
93
+ body.close
94
+ end
95
+ end
96
+
97
+ context.http_response.on_error do |error|
98
+ if retryable_body?(context)
99
+ if truncated_body?(error)
100
+ context.http_request.headers[:range] = "bytes=#{context.http_response.body.size}-"
101
+ else
102
+ case context.http_response.body
103
+ when RetryableManagedFile
104
+ # call rewind on the underlying file
105
+ context.http_response.body.instance_variable_get(:@file).rewind
106
+ else
107
+ raise NonRetryableStreamingError, error
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def truncated_body?(error)
115
+ error.is_a?(Seahorse::Client::NetworkingError) &&
116
+ error.original_error.is_a?(
117
+ Seahorse::Client::NetHttp::Handler::TruncatedBodyError
118
+ )
119
+ end
120
+
121
+ def retryable_body?(context)
122
+ context.http_response.body.is_a?(RetryableBlockIO) ||
123
+ context.http_response.body.is_a?(RetryableManagedFile)
124
+ end
125
+
126
+ def supported_target?(target)
127
+ case target
128
+ when Proc, String, Pathname then true
129
+ else false
130
+ end
131
+ end
132
+ end
133
+
134
+ handler(Handler, step: :sign, operations: [:get_object], priority: 10)
135
+
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'cgi'
3
5