Skip to content

Commit 920562c

Browse files
authored
Fix when_required mode for request_checksum_calculation (#3307)
1 parent 71471bc commit 920562c

File tree

5 files changed

+65
-26
lines changed

5 files changed

+65
-26
lines changed

gems/aws-sdk-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Issue - Fix `request_checksum_calculation` `when_required` mode to only calculate checksums when explicitly provided by user.
5+
46
* Issue - Add `CREDENTIALS_CODE` metric for `static_profile_` prefixed methods in default credential chain.
57

68
3.233.0 (2025-09-23)

gems/aws-sdk-core/lib/aws-sdk-core/plugins/checksum_algorithm.rb

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ def call(context)
190190
name: "x-amz-checksum-#{algorithm.downcase}",
191191
request_algorithm_header: request_algorithm_header(context)
192192
}
193-
194193
context[:http_checksum][:request_algorithm] = request_algorithm
195194
calculate_request_checksum(context, request_algorithm)
196195
end
@@ -249,6 +248,7 @@ def request_algorithm_selection(context)
249248
return unless context.operation.http_checksum
250249

251250
input_member = context.operation.http_checksum['requestAlgorithmMember']
251+
252252
context.params[input_member.to_sym] ||= DEFAULT_CHECKSUM if input_member
253253
end
254254

@@ -271,25 +271,39 @@ def operation_response_algorithms(context)
271271
context.operation.http_checksum['responseAlgorithms']
272272
end
273273

274-
def checksum_required?(context)
275-
(http_checksum = context.operation.http_checksum) &&
276-
(checksum_required = http_checksum['requestChecksumRequired']) &&
277-
(checksum_required && context.config.request_checksum_calculation == 'when_required')
278-
end
279-
280-
def checksum_optional?(context)
281-
context.operation.http_checksum &&
282-
context.config.request_checksum_calculation != 'when_required'
283-
end
284-
285274
def checksum_provided_as_header?(headers)
286275
headers.any? { |k, _| k.start_with?('x-amz-checksum-') }
287276
end
288277

278+
# Determines whether a request checksum should be calculated.
279+
# 1. **No existing checksum in header**: Skips if checksum header already present
280+
# 2. **Operation support**: Considers model, client configuration and user input.
289281
def should_calculate_request_checksum?(context)
290282
!checksum_provided_as_header?(context.http_request.headers) &&
291-
request_algorithm_selection(context) &&
292-
(checksum_required?(context) || checksum_optional?(context))
283+
checksum_applicable?(context)
284+
end
285+
286+
# Checks if checksum calculation should proceed based on operation requirements and client settings.
287+
# Returns true when any of these conditions are met:
288+
# 1. http checksum's requestChecksumRequired is true
289+
# 2. Config for request_checksum_calculation is "when_supported"
290+
# 3. Config for request_checksum_calculation is "when_required" AND user provided checksum algorithm
291+
def checksum_applicable?(context)
292+
http_checksum = context.operation.http_checksum
293+
return false unless http_checksum
294+
295+
return true if http_checksum['requestChecksumRequired']
296+
297+
return false unless (algorithm_member = http_checksum['requestAlgorithmMember'])
298+
299+
case context.config.request_checksum_calculation
300+
when 'when_supported'
301+
true
302+
when 'when_required'
303+
!context.params[algorithm_member.to_sym].nil?
304+
else
305+
false
306+
end
293307
end
294308

295309
def choose_request_algorithm!(context)

gems/aws-sdk-core/spec/aws/plugins/checksum_algorithm_spec.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ module Plugins
4747
'String' => { 'type' => 'string' },
4848
'ChecksumAlgorithm' => {
4949
'type' => 'string',
50-
'enum' => ['CRC32', 'CRC32C', 'CRC64NVME', 'SHA1', 'SHA256']
50+
'enum' => %w[CRC32 CRC32C CRC64NVME SHA1 SHA256]
5151
},
5252
'SomeInput' => {
5353
'type' => 'structure',
@@ -405,6 +405,16 @@ module Plugins
405405
expect(resp.context.http_request.headers['x-amz-checksum-crc32'])
406406
.to be_nil
407407
end
408+
409+
it 'when_required; given algorithm; include a checksum' do
410+
client = checksum_client.new(
411+
stub_responses: true,
412+
request_checksum_calculation: 'when_required'
413+
)
414+
resp = client.http_checksum_operation(checksum_algorithm: 'CRC32')
415+
expect(resp.context.http_request.headers['x-amz-checksum-crc32'])
416+
.to eq('AAAAAA==')
417+
end
408418
end
409419

410420
context 'when checksums are required' do

gems/aws-sdk-s3/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Issue - Fix multipart upload to respect `request_checksum_calculation` `when_required` mode.
5+
46
1.200.0 (2025-10-15)
57
------------------
68

gems/aws-sdk-s3/lib/aws-sdk-s3/multipart_file_uploader.rb

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,10 @@ def initiate_upload(options)
5151

5252
def complete_upload(upload_id, parts, file_size, options)
5353
@client.complete_multipart_upload(
54-
**complete_opts(options).merge(
55-
upload_id: upload_id,
56-
multipart_upload: { parts: parts },
57-
mpu_object_size: file_size
58-
)
54+
**complete_opts(options),
55+
upload_id: upload_id,
56+
multipart_upload: { parts: parts },
57+
mpu_object_size: file_size
5958
)
6059
rescue StandardError => e
6160
abort_upload(upload_id, options, [e])
@@ -79,8 +78,8 @@ def abort_upload(upload_id, options, errors)
7978
rescue MultipartUploadError => e
8079
raise e
8180
rescue StandardError => e
82-
msg = "failed to abort multipart upload: #{e.message}. "\
83-
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
81+
msg = "failed to abort multipart upload: #{e.message}. " \
82+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
8483
raise MultipartUploadError.new(msg, errors + [e])
8584
end
8685

@@ -113,8 +112,15 @@ def has_checksum_key?(keys)
113112
keys.any? { |key| checksum_key?(key) }
114113
end
115114

115+
def checksum_not_required?(options)
116+
@client.config.request_checksum_calculation == 'when_required' && !options[:checksum_algorithm]
117+
end
118+
116119
def create_opts(options)
117-
opts = { checksum_algorithm: Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM }
120+
opts = {}
121+
unless checksum_not_required?(options)
122+
opts[:checksum_algorithm] = Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM
123+
end
118124
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
119125
CREATE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
120126
end
@@ -148,9 +154,7 @@ def upload_with_executor(pending, completed, options)
148154
resp = @client.upload_part(p)
149155
p[:body].close
150156
completed_part = { etag: resp.etag, part_number: p[:part_number] }
151-
algorithm = resp.context.params[:checksum_algorithm].downcase
152-
k = "checksum_#{algorithm}".to_sym
153-
completed_part[k] = resp.send(k)
157+
apply_part_checksum(resp, completed_part)
154158
completed.push(completed_part)
155159
rescue StandardError => e
156160
abort_upload = true
@@ -164,6 +168,13 @@ def upload_with_executor(pending, completed, options)
164168
errors
165169
end
166170

171+
def apply_part_checksum(resp, part)
172+
return unless (checksum = resp.context.params[:checksum_algorithm])
173+
174+
k = :"checksum_#{checksum.downcase}"
175+
part[k] = resp.send(k)
176+
end
177+
167178
def compute_default_part_size(file_size)
168179
[(file_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
169180
end

0 commit comments

Comments
 (0)