Skip to content

Commit b359203

Browse files
authored
Add checksum listeners before response targets (#3250)
1 parent 692d01c commit b359203

File tree

6 files changed

+103
-84
lines changed

6 files changed

+103
-84
lines changed

build_tools/services.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class ServiceEnumerator
1212
MINIMUM_CORE_VERSION = "3.216.0"
1313

1414
# Minimum `aws-sdk-core` version for new S3 gem builds
15-
MINIMUM_CORE_VERSION_S3 = "3.216.0"
15+
MINIMUM_CORE_VERSION_S3 = "3.224.1"
1616

1717
EVENTSTREAM_PLUGIN = "Aws::Plugins::EventStreamConfiguration"
1818

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 - Signal data in http response listeners prior to writing, so that data can be inspected or verified before potential mutation.
5+
46
3.224.0 (2025-05-12)
57
------------------
68

gems/aws-sdk-core/lib/seahorse/client/http/response.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def signal_headers(status_code, headers)
6666
# @param [string] chunk
6767
def signal_data(chunk)
6868
unless chunk == ''
69-
@body.write(chunk)
7069
emit(:data, chunk)
70+
@body.write(chunk)
7171
end
7272
end
7373

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 - Signal data in http response listeners prior to writing, so that data can be inspected or verified before potential mutation.
5+
46
1.186.1 (2025-05-15)
57
------------------
68
* Issue - Abort multipart download if object is modified during download.

gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/streaming_retry.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,16 @@ class StreamingRetry < Seahorse::Client::Plugin
6262
class Handler < Seahorse::Client::Handler
6363

6464
def call(context)
65-
target = context.params[:response_target] || context[:response_target]
66-
6765
# 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)
66+
if supported_target?(context) && !context.params[:range]
67+
add_event_listeners(context)
7068
end
7169
@handler.call(context)
7270
end
7371

7472
private
7573

76-
def add_event_listeners(context, target)
74+
def add_event_listeners(context)
7775
context.http_response.on_headers(200..299) do
7876
case context.http_response.body
7977
when Seahorse::Client::BlockIO then
@@ -123,8 +121,8 @@ def retryable_body?(context)
123121
context.http_response.body.is_a?(RetryableManagedFile)
124122
end
125123

126-
def supported_target?(target)
127-
case target
124+
def supported_target?(context)
125+
case context[:response_target]
128126
when Proc, String, Pathname then true
129127
else false
130128
end

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

Lines changed: 92 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,92 +4,109 @@
44

55
module Aws
66
module S3
7-
module Plugins
8-
describe ChecksumAlgorithm do
9-
let(:creds) { Aws::Credentials.new('akid', 'secret') }
10-
let(:client) { S3::Client.new(stub_responses: true) }
11-
let(:bucket) { 'bucket' }
12-
let(:key) { 'key' }
7+
describe Client do
8+
let(:creds) { Aws::Credentials.new('akid', 'secret') }
9+
let(:client) { S3::Client.new(stub_responses: true) }
10+
let(:bucket) { 'bucket' }
11+
let(:key) { 'key' }
1312

14-
let(:body) { 'hello world' }
15-
let(:digest) { 'DUoRhQ==' }
13+
let(:body) { 'hello world' }
14+
let(:digest) { 'DUoRhQ==' }
1615

17-
let(:body_part_1) { 'hello '}
18-
let(:digest_part_1) { '7YH59g==' }
16+
let(:body_part_1) { 'hello '}
17+
let(:digest_part_1) { '7YH59g==' }
1918

20-
it 'validates the checksum on an Object GET' do
21-
client.stub_responses(
22-
:get_object,
23-
[{
24-
body: body,
25-
headers: {'x-amz-checksum-crc32' => digest},
26-
status_code: 200
27-
}])
28-
resp = client.get_object(bucket: bucket, key: key, checksum_mode: 'ENABLED')
29-
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
30-
end
19+
it 'validates the checksum on an Object GET' do
20+
client.stub_responses(
21+
:get_object,
22+
[{
23+
body: body,
24+
headers: {'x-amz-checksum-crc32' => digest},
25+
status_code: 200
26+
}]
27+
)
28+
resp = client.get_object(bucket: bucket, key: key)
29+
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
30+
end
3131

32-
it 'raises when the checksum does not match on an Object GET' do
33-
client.stub_responses(
34-
:get_object,
35-
[{
36-
body: body,
37-
headers: {'x-amz-checksum-crc32' => 'invalid_value'},
38-
status_code: 200
39-
}])
40-
expect do
41-
client.get_object(bucket: bucket, key: key, checksum_mode: 'ENABLED')
42-
end.to raise_error(Aws::Errors::ChecksumError)
43-
end
32+
it 'raises when the checksum does not match on an Object GET' do
33+
client.stub_responses(
34+
:get_object,
35+
[{
36+
body: body,
37+
headers: {'x-amz-checksum-crc32' => 'invalid_value'},
38+
status_code: 200
39+
}]
40+
)
41+
expect do
42+
client.get_object(bucket: bucket, key: key)
43+
end.to raise_error(Aws::Errors::ChecksumError)
44+
end
4445

45-
it 'validates the checksum on a range GET matching the part boundary' do
46-
client.stub_responses(
47-
:get_object,
48-
[{
49-
body: body_part_1,
50-
headers: {'x-amz-checksum-crc32' => digest_part_1},
51-
status_code: 200
52-
}])
53-
resp = client.get_object(bucket: bucket, key: key, checksum_mode: 'ENABLED', range: "bytes=0-6")
54-
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
55-
end
46+
it 'validates the checksum on a range GET matching the part boundary' do
47+
client.stub_responses(
48+
:get_object,
49+
[{
50+
body: body_part_1,
51+
headers: {'x-amz-checksum-crc32' => digest_part_1},
52+
status_code: 200
53+
}]
54+
)
55+
resp = client.get_object(bucket: bucket, key: key, range: "bytes=0-6")
56+
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
57+
end
5658

57-
it 'validates the checksum on a single part GET' do
58-
client.stub_responses(
59-
:get_object,
60-
[{
61-
body: body_part_1,
62-
headers: {'x-amz-checksum-crc32' => digest_part_1},
63-
status_code: 200
64-
}])
65-
resp = client.get_object(bucket: bucket, key: key, checksum_mode: 'ENABLED', part_number: 1)
66-
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
59+
it 'validates the checksum on a single part GET' do
60+
client.stub_responses(
61+
:get_object,
62+
[{
63+
body: body_part_1,
64+
headers: {'x-amz-checksum-crc32' => digest_part_1},
65+
status_code: 200
66+
}]
67+
)
68+
resp = client.get_object(bucket: bucket, key: key, part_number: 1)
69+
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
70+
end
71+
72+
it 'validates before any mutation of response target' do
73+
client.stub_responses(
74+
:get_object,
75+
[{
76+
body: body,
77+
headers: {'x-amz-checksum-crc32' => digest},
78+
status_code: 200
79+
}]
80+
)
81+
resp = client.get_object(bucket: bucket, key: key) do |chunk|
82+
chunk.upcase!
6783
end
84+
expect(resp.context[:http_checksum][:validated]).to eq 'CRC32'
85+
end
6886

69-
context 'checksum response composite validation' do
70-
file = File.expand_path('checksum_response_composite.json', __dir__)
71-
test_cases = JSON.load_file(file)
87+
context 'checksum response composite validation' do
88+
file = File.expand_path('checksum_response_composite.json', __dir__)
89+
test_cases = JSON.load_file(file)
7290

73-
test_cases.each do |test_case|
74-
it "passes test: #{test_case['documentation']}" do
75-
if (algorithm = test_case['checksumAlgorithm'])
76-
algorithm.upcase!
77-
unless Aws::Plugins::ChecksumAlgorithm::CLIENT_ALGORITHMS.include?(algorithm)
78-
skip "Algorithm #{algorithm} not supported"
79-
end
91+
test_cases.each do |test_case|
92+
it "passes test: #{test_case['documentation']}" do
93+
if (algorithm = test_case['checksumAlgorithm'])
94+
algorithm.upcase!
95+
unless Aws::Plugins::ChecksumAlgorithm::CLIENT_ALGORITHMS.include?(algorithm)
96+
skip "Algorithm #{algorithm} not supported"
8097
end
81-
82-
client.stub_responses(
83-
:get_object,
84-
[{
85-
body: test_case['responsePayload'],
86-
headers: test_case['responseHeaders'],
87-
status_code: 200
88-
}]
89-
)
90-
resp = client.get_object(bucket: bucket, key: key, checksum_mode: 'ENABLED')
91-
expect(resp.context[:http_checksum][:validated]).to be_nil
9298
end
99+
100+
client.stub_responses(
101+
:get_object,
102+
[{
103+
body: test_case['responsePayload'],
104+
headers: test_case['responseHeaders'],
105+
status_code: 200
106+
}]
107+
)
108+
resp = client.get_object(bucket: bucket, key: key, checksum_mode: 'ENABLED')
109+
expect(resp.context[:http_checksum][:validated]).to be_nil
93110
end
94111
end
95112
end

0 commit comments

Comments
 (0)