Skip to content

Commit f6068c8

Browse files
authoredJan 15, 2025··
chore(middleware-flexible-checksums): perform checksum calculation and validation by default (#6750)
1 parent 2293f5a commit f6068c8

9 files changed

+478
-135
lines changed
 

‎packages/middleware-flexible-checksums/src/configuration.ts

+13
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import {
44
Encoder,
55
GetAwsChunkedEncodingStream,
66
HashConstructor,
7+
Provider,
78
StreamCollector,
89
StreamHasher,
910
} from "@smithy/types";
1011

12+
import { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
13+
1114
export interface PreviouslyResolved {
1215
/**
1316
* The function that will be used to convert binary data to a base64-encoded string.
@@ -31,6 +34,16 @@ export interface PreviouslyResolved {
3134
*/
3235
md5: ChecksumConstructor | HashConstructor;
3336

37+
/**
38+
* Determines when a checksum will be calculated for request payloads
39+
*/
40+
requestChecksumCalculation: Provider<RequestChecksumCalculation>;
41+
42+
/**
43+
* Determines when a checksum will be calculated for response payloads
44+
*/
45+
responseChecksumValidation: Provider<ResponseChecksumValidation>;
46+
3447
/**
3548
* A constructor for a class implementing the {@link Hash} interface that computes SHA1 hashes.
3649
* @internal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { setFeature } from "@aws-sdk/core";
2+
import { afterEach, describe, expect, test as it, vi } from "vitest";
3+
4+
import { PreviouslyResolved } from "./configuration";
5+
import { DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
6+
import { flexibleChecksumsInputMiddleware } from "./flexibleChecksumsInputMiddleware";
7+
8+
vi.mock("@aws-sdk/core");
9+
10+
describe(flexibleChecksumsInputMiddleware.name, () => {
11+
const mockNext = vi.fn();
12+
const mockRequestValidationModeMember = "mockRequestValidationModeMember";
13+
14+
const mockConfig = {
15+
requestChecksumCalculation: () => Promise.resolve(RequestChecksumCalculation.WHEN_SUPPORTED),
16+
responseChecksumValidation: () => Promise.resolve(ResponseChecksumValidation.WHEN_SUPPORTED),
17+
} as PreviouslyResolved;
18+
19+
afterEach(() => {
20+
expect(mockNext).toHaveBeenCalledTimes(1);
21+
vi.clearAllMocks();
22+
});
23+
24+
describe("sets input.requestValidationModeMember", () => {
25+
it("when requestValidationModeMember is defined and responseChecksumValidation is supported", async () => {
26+
const mockMiddlewareConfigWithMockRequestValidationModeMember = {
27+
requestValidationModeMember: mockRequestValidationModeMember,
28+
};
29+
const handler = flexibleChecksumsInputMiddleware(
30+
mockConfig,
31+
mockMiddlewareConfigWithMockRequestValidationModeMember
32+
)(mockNext, {});
33+
await handler({ input: {} });
34+
expect(mockNext).toHaveBeenCalledWith({ input: { [mockRequestValidationModeMember]: "ENABLED" } });
35+
});
36+
});
37+
38+
describe("leaves input.requestValidationModeMember", () => {
39+
const mockArgs = { input: {} };
40+
41+
it("when requestValidationModeMember is not defined", async () => {
42+
const handler = flexibleChecksumsInputMiddleware(mockConfig, {})(mockNext, {});
43+
await handler(mockArgs);
44+
expect(mockNext).toHaveBeenCalledWith(mockArgs);
45+
});
46+
47+
it("when responseChecksumValidation is required", async () => {
48+
const mockConfigResWhenRequired = {
49+
...mockConfig,
50+
responseChecksumValidation: () => Promise.resolve(ResponseChecksumValidation.WHEN_REQUIRED),
51+
} as PreviouslyResolved;
52+
53+
const handler = flexibleChecksumsInputMiddleware(mockConfigResWhenRequired, {})(mockNext, {});
54+
await handler(mockArgs);
55+
56+
expect(mockNext).toHaveBeenCalledWith(mockArgs);
57+
});
58+
});
59+
60+
describe("set feature", () => {
61+
it.each([
62+
[
63+
"FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED",
64+
"a",
65+
"requestChecksumCalculation",
66+
RequestChecksumCalculation.WHEN_REQUIRED,
67+
],
68+
[
69+
"FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED",
70+
"Z",
71+
"requestChecksumCalculation",
72+
RequestChecksumCalculation.WHEN_SUPPORTED,
73+
],
74+
[
75+
"FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED",
76+
"c",
77+
"responseChecksumValidation",
78+
ResponseChecksumValidation.WHEN_REQUIRED,
79+
],
80+
[
81+
"FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED",
82+
"b",
83+
"responseChecksumValidation",
84+
ResponseChecksumValidation.WHEN_SUPPORTED,
85+
],
86+
])("logs %s:%s when %s=%s", async (feature, value, configKey, configValue) => {
87+
const mockConfigOverride = {
88+
...mockConfig,
89+
[configKey]: () => Promise.resolve(configValue),
90+
} as PreviouslyResolved;
91+
92+
const handler = flexibleChecksumsInputMiddleware(mockConfigOverride, {})(mockNext, {});
93+
await handler({ input: {} });
94+
95+
expect(setFeature).toHaveBeenCalledTimes(2);
96+
if (configKey === "requestChecksumCalculation") {
97+
expect(setFeature).toHaveBeenNthCalledWith(1, expect.anything(), feature, value);
98+
} else {
99+
expect(setFeature).toHaveBeenNthCalledWith(2, expect.anything(), feature, value);
100+
}
101+
});
102+
});
103+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { setFeature } from "@aws-sdk/core";
2+
import {
3+
HandlerExecutionContext,
4+
MetadataBearer,
5+
RelativeMiddlewareOptions,
6+
SerializeHandler,
7+
SerializeHandlerArguments,
8+
SerializeHandlerOutput,
9+
SerializeMiddleware,
10+
} from "@smithy/types";
11+
12+
import { PreviouslyResolved } from "./configuration";
13+
import { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
14+
15+
export interface FlexibleChecksumsInputMiddlewareConfig {
16+
/**
17+
* Defines a top-level operation input member used to opt-in to best-effort validation
18+
* of a checksum returned in the HTTP response of the operation.
19+
*/
20+
requestValidationModeMember?: string;
21+
}
22+
23+
/**
24+
* @internal
25+
*/
26+
export const flexibleChecksumsInputMiddlewareOptions: RelativeMiddlewareOptions = {
27+
name: "flexibleChecksumsInputMiddleware",
28+
toMiddleware: "serializerMiddleware",
29+
relation: "before",
30+
tags: ["BODY_CHECKSUM"],
31+
override: true,
32+
};
33+
34+
/**
35+
* @internal
36+
*
37+
* The input counterpart to the flexibleChecksumsMiddleware.
38+
*/
39+
export const flexibleChecksumsInputMiddleware =
40+
(
41+
config: PreviouslyResolved,
42+
middlewareConfig: FlexibleChecksumsInputMiddlewareConfig
43+
): SerializeMiddleware<any, any> =>
44+
<Output extends MetadataBearer>(
45+
next: SerializeHandler<any, Output>,
46+
context: HandlerExecutionContext
47+
): SerializeHandler<any, Output> =>
48+
async (args: SerializeHandlerArguments<any>): Promise<SerializeHandlerOutput<Output>> => {
49+
const input = args.input;
50+
const { requestValidationModeMember } = middlewareConfig;
51+
52+
const requestChecksumCalculation = await config.requestChecksumCalculation();
53+
const responseChecksumValidation = await config.responseChecksumValidation();
54+
55+
switch (requestChecksumCalculation) {
56+
case RequestChecksumCalculation.WHEN_REQUIRED:
57+
setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED", "a");
58+
break;
59+
case RequestChecksumCalculation.WHEN_SUPPORTED:
60+
setFeature(context, "FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED", "Z");
61+
break;
62+
}
63+
64+
switch (responseChecksumValidation) {
65+
case ResponseChecksumValidation.WHEN_REQUIRED:
66+
setFeature(context, "FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED", "c");
67+
break;
68+
case ResponseChecksumValidation.WHEN_SUPPORTED:
69+
setFeature(context, "FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED", "b");
70+
break;
71+
}
72+
73+
// The value for input member to opt-in to best-effort validation of a checksum returned in the HTTP response is not set.
74+
if (requestValidationModeMember && !input[requestValidationModeMember]) {
75+
// Set requestValidationModeMember as ENABLED only if response checksum validation is supported.
76+
if (responseChecksumValidation === ResponseChecksumValidation.WHEN_SUPPORTED) {
77+
input[requestValidationModeMember] = "ENABLED";
78+
}
79+
}
80+
81+
return next(args);
82+
};

‎packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.spec.ts

+54-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BuildHandlerArguments } from "@smithy/types";
33
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
44

55
import { PreviouslyResolved } from "./configuration";
6-
import { ChecksumAlgorithm } from "./constants";
6+
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
77
import { flexibleChecksumsMiddleware } from "./flexibleChecksumsMiddleware";
88
import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest";
99
import { getChecksumLocationName } from "./getChecksumLocationName";
@@ -13,6 +13,7 @@ import { isStreaming } from "./isStreaming";
1313
import { selectChecksumAlgorithmFunction } from "./selectChecksumAlgorithmFunction";
1414
import { stringHasher } from "./stringHasher";
1515

16+
vi.mock("@aws-sdk/core");
1617
vi.mock("@smithy/protocol-http");
1718
vi.mock("./getChecksumAlgorithmForRequest");
1819
vi.mock("./getChecksumLocationName");
@@ -28,10 +29,14 @@ describe(flexibleChecksumsMiddleware.name, () => {
2829
const mockChecksum = "mockChecksum";
2930
const mockChecksumAlgorithmFunction = vi.fn();
3031
const mockChecksumLocationName = "mock-checksum-location-name";
32+
const mockRequestAlgorithmMember = "mockRequestAlgorithmMember";
33+
const mockRequestAlgorithmMemberHttpHeader = "mock-request-algorithm-member-http-header";
3134

3235
const mockInput = {};
33-
const mockConfig = {} as PreviouslyResolved;
34-
const mockMiddlewareConfig = { requestChecksumRequired: false };
36+
const mockConfig = {
37+
requestChecksumCalculation: () => Promise.resolve(RequestChecksumCalculation.WHEN_REQUIRED),
38+
} as PreviouslyResolved;
39+
const mockMiddlewareConfig = { input: mockInput, requestChecksumRequired: false };
3540

3641
const mockBody = { body: "mockRequestBody" };
3742
const mockHeaders = { "content-length": 100, "content-encoding": "gzip" };
@@ -41,9 +46,8 @@ describe(flexibleChecksumsMiddleware.name, () => {
4146

4247
beforeEach(() => {
4348
mockNext.mockResolvedValueOnce(mockResult);
44-
const { isInstance } = HttpRequest;
45-
(isInstance as unknown as any).mockReturnValue(true);
46-
vi.mocked(getChecksumAlgorithmForRequest).mockReturnValue(ChecksumAlgorithm.MD5);
49+
vi.mocked(HttpRequest.isInstance).mockReturnValue(true);
50+
vi.mocked(getChecksumAlgorithmForRequest).mockReturnValue(ChecksumAlgorithm.CRC32);
4751
vi.mocked(getChecksumLocationName).mockReturnValue(mockChecksumLocationName);
4852
vi.mocked(hasHeader).mockReturnValue(true);
4953
vi.mocked(hasHeaderWithPrefix).mockReturnValue(false);
@@ -58,8 +62,7 @@ describe(flexibleChecksumsMiddleware.name, () => {
5862

5963
describe("skips", () => {
6064
it("if not an instance of HttpRequest", async () => {
61-
const { isInstance } = HttpRequest;
62-
(isInstance as unknown as any).mockReturnValue(false);
65+
vi.mocked(HttpRequest.isInstance).mockReturnValue(false);
6366
const handler = flexibleChecksumsMiddleware(mockConfig, mockMiddlewareConfig)(mockNext, {});
6467
await handler(mockArgs);
6568
expect(getChecksumAlgorithmForRequest).not.toHaveBeenCalled();
@@ -77,7 +80,7 @@ describe(flexibleChecksumsMiddleware.name, () => {
7780
expect(getChecksumAlgorithmForRequest).toHaveBeenCalledTimes(1);
7881
});
7982

80-
it("if header is already present", async () => {
83+
it("skip if header is already present", async () => {
8184
const handler = flexibleChecksumsMiddleware(mockConfig, mockMiddlewareConfig)(mockNext, {});
8285
vi.mocked(hasHeaderWithPrefix).mockReturnValue(true);
8386

@@ -94,11 +97,53 @@ describe(flexibleChecksumsMiddleware.name, () => {
9497

9598
describe("adds checksum in the request header", () => {
9699
afterEach(() => {
100+
expect(HttpRequest.isInstance).toHaveBeenCalledTimes(1);
101+
expect(hasHeaderWithPrefix).toHaveBeenCalledTimes(1);
97102
expect(getChecksumAlgorithmForRequest).toHaveBeenCalledTimes(1);
98103
expect(getChecksumLocationName).toHaveBeenCalledTimes(1);
99104
expect(selectChecksumAlgorithmFunction).toHaveBeenCalledTimes(1);
100105
});
101106

107+
describe("if input.requestAlgorithmMember can be set", () => {
108+
describe("input[requestAlgorithmMember] is not defined and", () => {
109+
const mockMwConfigWithReqAlgoMember = {
110+
...mockMiddlewareConfig,
111+
requestAlgorithmMember: {
112+
name: mockRequestAlgorithmMember,
113+
httpHeader: mockRequestAlgorithmMemberHttpHeader,
114+
},
115+
};
116+
117+
it("requestChecksumCalculation is supported", async () => {
118+
const handler = flexibleChecksumsMiddleware(
119+
{
120+
...mockConfig,
121+
requestChecksumCalculation: () => Promise.resolve(RequestChecksumCalculation.WHEN_SUPPORTED),
122+
},
123+
mockMwConfigWithReqAlgoMember
124+
)(mockNext, {});
125+
await handler(mockArgs);
126+
expect(mockNext.mock.calls[0][0].input[mockRequestAlgorithmMember]).toEqual(DEFAULT_CHECKSUM_ALGORITHM);
127+
expect(mockNext.mock.calls[0][0].request.headers[mockRequestAlgorithmMemberHttpHeader]).toEqual(
128+
DEFAULT_CHECKSUM_ALGORITHM
129+
);
130+
});
131+
132+
it("requestChecksumRequired is set to true", async () => {
133+
const handler = flexibleChecksumsMiddleware(mockConfig, {
134+
...mockMwConfigWithReqAlgoMember,
135+
requestChecksumRequired: true,
136+
})(mockNext, {});
137+
138+
await handler(mockArgs);
139+
expect(mockNext.mock.calls[0][0].input[mockRequestAlgorithmMember]).toEqual(DEFAULT_CHECKSUM_ALGORITHM);
140+
expect(mockNext.mock.calls[0][0].request.headers[mockRequestAlgorithmMemberHttpHeader]).toEqual(
141+
DEFAULT_CHECKSUM_ALGORITHM
142+
);
143+
});
144+
});
145+
});
146+
102147
it("for streaming body", async () => {
103148
vi.mocked(isStreaming).mockReturnValue(true);
104149
const mockUpdatedBody = { body: "mockUpdatedBody" };

‎packages/middleware-flexible-checksums/src/flexibleChecksumsMiddleware.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "@smithy/types";
1212

1313
import { PreviouslyResolved } from "./configuration";
14-
import { ChecksumAlgorithm } from "./constants";
14+
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
1515
import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest";
1616
import { getChecksumLocationName } from "./getChecksumLocationName";
1717
import { hasHeader } from "./hasHeader";
@@ -73,10 +73,26 @@ export const flexibleChecksumsMiddleware =
7373
const { body: requestBody, headers } = request;
7474
const { base64Encoder, streamHasher } = config;
7575
const { requestChecksumRequired, requestAlgorithmMember } = middlewareConfig;
76+
const requestChecksumCalculation = await config.requestChecksumCalculation();
77+
78+
const requestAlgorithmMemberName = requestAlgorithmMember?.name;
79+
const requestAlgorithmMemberHttpHeader = requestAlgorithmMember?.httpHeader;
80+
// The value for input member to configure flexible checksum is not set.
81+
if (requestAlgorithmMemberName && !input[requestAlgorithmMemberName]) {
82+
// Set requestAlgorithmMember as default checksum algorithm only if request checksum calculation is supported
83+
// or request checksum is required.
84+
if (requestChecksumCalculation === RequestChecksumCalculation.WHEN_SUPPORTED || requestChecksumRequired) {
85+
input[requestAlgorithmMemberName] = DEFAULT_CHECKSUM_ALGORITHM;
86+
if (requestAlgorithmMemberHttpHeader) {
87+
headers[requestAlgorithmMemberHttpHeader] = DEFAULT_CHECKSUM_ALGORITHM;
88+
}
89+
}
90+
}
7691

7792
const checksumAlgorithm = getChecksumAlgorithmForRequest(input, {
7893
requestChecksumRequired,
7994
requestAlgorithmMember: requestAlgorithmMember?.name,
95+
requestChecksumCalculation,
8096
});
8197
let updatedBody = requestBody;
8298
let updatedHeaders = headers;

‎packages/middleware-flexible-checksums/src/getChecksumAlgorithmForRequest.spec.ts

+49-17
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,68 @@
11
import { describe, expect, test as it } from "vitest";
22

3-
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM } from "./constants";
3+
import { DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
44
import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest";
55
import { CLIENT_SUPPORTED_ALGORITHMS } from "./types";
66

77
describe(getChecksumAlgorithmForRequest.name, () => {
88
const mockRequestAlgorithmMember = "mockRequestAlgorithmMember";
99

1010
describe("when requestAlgorithmMember is not provided", () => {
11-
it(`returns ${DEFAULT_CHECKSUM_ALGORITHM} if requestChecksumRequired is set`, () => {
12-
expect(getChecksumAlgorithmForRequest({}, { requestChecksumRequired: true })).toEqual(DEFAULT_CHECKSUM_ALGORITHM);
13-
});
11+
describe(`when requestChecksumCalculation is '${RequestChecksumCalculation.WHEN_REQUIRED}'`, () => {
12+
const mockOptions = { requestChecksumCalculation: RequestChecksumCalculation.WHEN_REQUIRED };
13+
14+
it(`returns ${DEFAULT_CHECKSUM_ALGORITHM} if requestChecksumRequired is set`, () => {
15+
expect(getChecksumAlgorithmForRequest({}, { ...mockOptions, requestChecksumRequired: true })).toEqual(
16+
DEFAULT_CHECKSUM_ALGORITHM
17+
);
18+
});
1419

15-
it("returns undefined if requestChecksumRequired is false", () => {
16-
expect(getChecksumAlgorithmForRequest({}, { requestChecksumRequired: false })).toBeUndefined();
20+
it("returns undefined if requestChecksumRequired is false", () => {
21+
expect(getChecksumAlgorithmForRequest({}, { ...mockOptions, requestChecksumRequired: false })).toBeUndefined();
22+
});
1723
});
18-
});
1924

20-
describe("when requestAlgorithmMember is not set in input", () => {
21-
const mockOptions = { requestAlgorithmMember: mockRequestAlgorithmMember };
25+
describe(`when requestChecksumCalculation is '${RequestChecksumCalculation.WHEN_SUPPORTED}'`, () => {
26+
const mockOptions = { requestChecksumCalculation: RequestChecksumCalculation.WHEN_SUPPORTED };
2227

23-
it(`returns ${DEFAULT_CHECKSUM_ALGORITHM} if requestChecksumRequired is set`, () => {
24-
expect(getChecksumAlgorithmForRequest({}, { ...mockOptions, requestChecksumRequired: true })).toEqual(
25-
DEFAULT_CHECKSUM_ALGORITHM
26-
);
28+
it(`returns ${DEFAULT_CHECKSUM_ALGORITHM} if requestChecksumRequired is set`, () => {
29+
expect(getChecksumAlgorithmForRequest({}, { ...mockOptions, requestChecksumRequired: true })).toEqual(
30+
DEFAULT_CHECKSUM_ALGORITHM
31+
);
32+
});
33+
34+
it(`returns ${DEFAULT_CHECKSUM_ALGORITHM} if requestChecksumRequired is false`, () => {
35+
expect(getChecksumAlgorithmForRequest({}, { ...mockOptions, requestChecksumRequired: false })).toEqual(
36+
DEFAULT_CHECKSUM_ALGORITHM
37+
);
38+
});
2739
});
40+
});
2841

29-
it("returns undefined if requestChecksumRequired is false", () => {
30-
expect(getChecksumAlgorithmForRequest({}, { ...mockOptions, requestChecksumRequired: false })).toBeUndefined();
42+
describe("returns undefined if input[requestAlgorithmMember] is not set", () => {
43+
describe.each([true, false])("when requestChecksumRequired='%s'", (requestChecksumRequired) => {
44+
it.each([RequestChecksumCalculation.WHEN_SUPPORTED, RequestChecksumCalculation.WHEN_REQUIRED])(
45+
"when requestChecksumCalculation='%s'",
46+
(requestChecksumCalculation) => {
47+
const mockOptions = {
48+
requestChecksumRequired,
49+
requestChecksumCalculation,
50+
requestAlgorithmMember: mockRequestAlgorithmMember,
51+
};
52+
expect(getChecksumAlgorithmForRequest({}, mockOptions)).toBeUndefined();
53+
}
54+
);
3155
});
3256
});
3357

3458
it("throws error if input[requestAlgorithmMember] if not supported by client", () => {
3559
const unsupportedAlgo = "unsupportedAlgo";
3660
const mockInput = { [mockRequestAlgorithmMember]: unsupportedAlgo };
37-
const mockOptions = { requestChecksumRequired: true, requestAlgorithmMember: mockRequestAlgorithmMember };
61+
const mockOptions = {
62+
requestChecksumRequired: true,
63+
requestAlgorithmMember: mockRequestAlgorithmMember,
64+
requestChecksumCalculation: RequestChecksumCalculation.WHEN_REQUIRED,
65+
};
3866
expect(() => {
3967
getChecksumAlgorithmForRequest(mockInput, mockOptions);
4068
}).toThrowError(
@@ -46,7 +74,11 @@ describe(getChecksumAlgorithmForRequest.name, () => {
4674
describe("returns input[requestAlgorithmMember] if supported by client", () => {
4775
it.each(CLIENT_SUPPORTED_ALGORITHMS)("Supported algorithm: %s", (supportedAlgorithm) => {
4876
const mockInput = { [mockRequestAlgorithmMember]: supportedAlgorithm };
49-
const mockOptions = { requestChecksumRequired: true, requestAlgorithmMember: mockRequestAlgorithmMember };
77+
const mockOptions = {
78+
requestChecksumRequired: true,
79+
requestAlgorithmMember: mockRequestAlgorithmMember,
80+
requestChecksumCalculation: RequestChecksumCalculation.WHEN_REQUIRED,
81+
};
5082
expect(getChecksumAlgorithmForRequest(mockInput, mockOptions)).toEqual(supportedAlgorithm);
5183
});
5284
});

‎packages/middleware-flexible-checksums/src/getChecksumAlgorithmForRequest.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM } from "./constants";
1+
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
22
import { CLIENT_SUPPORTED_ALGORITHMS } from "./types";
33

44
export interface GetChecksumAlgorithmForRequestOptions {
@@ -11,6 +11,11 @@ export interface GetChecksumAlgorithmForRequestOptions {
1111
* Defines a top-level operation input member that is used to configure request checksum behavior.
1212
*/
1313
requestAlgorithmMember?: string;
14+
15+
/**
16+
* Determines when a checksum will be calculated for request payloads
17+
*/
18+
requestChecksumCalculation: RequestChecksumCalculation;
1419
}
1520

1621
/**
@@ -20,13 +25,19 @@ export interface GetChecksumAlgorithmForRequestOptions {
2025
*/
2126
export const getChecksumAlgorithmForRequest = (
2227
input: any,
23-
{ requestChecksumRequired, requestAlgorithmMember }: GetChecksumAlgorithmForRequestOptions
28+
{ requestChecksumRequired, requestAlgorithmMember, requestChecksumCalculation }: GetChecksumAlgorithmForRequestOptions
2429
): ChecksumAlgorithm | undefined => {
25-
// Either the Operation input member that is used to configure request checksum behavior is not set, or
26-
// the value for input member to configure flexible checksum is not set.
27-
if (!requestAlgorithmMember || !input[requestAlgorithmMember]) {
28-
// Select an algorithm only if request checksum is required.
29-
return requestChecksumRequired ? DEFAULT_CHECKSUM_ALGORITHM : undefined;
30+
// The Operation input member that is used to configure request checksum behavior is not set.
31+
if (!requestAlgorithmMember) {
32+
// Select an algorithm only if request checksum calculation is supported
33+
// or request checksum is required.
34+
return requestChecksumCalculation === RequestChecksumCalculation.WHEN_SUPPORTED || requestChecksumRequired
35+
? DEFAULT_CHECKSUM_ALGORITHM
36+
: undefined;
37+
}
38+
39+
if (!input[requestAlgorithmMember]) {
40+
return undefined;
3041
}
3142

3243
const checksumAlgorithm = input[requestAlgorithmMember];

‎packages/middleware-flexible-checksums/src/getFlexibleChecksumsPlugin.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Pluggable } from "@smithy/types";
22

33
import { PreviouslyResolved } from "./configuration";
4+
import {
5+
flexibleChecksumsInputMiddleware,
6+
FlexibleChecksumsInputMiddlewareConfig,
7+
flexibleChecksumsInputMiddlewareOptions,
8+
} from "./flexibleChecksumsInputMiddleware";
49
import {
510
flexibleChecksumsMiddleware,
611
flexibleChecksumsMiddlewareOptions,
@@ -14,6 +19,7 @@ import {
1419

1520
export interface FlexibleChecksumsMiddlewareConfig
1621
extends FlexibleChecksumsRequestMiddlewareConfig,
22+
FlexibleChecksumsInputMiddlewareConfig,
1723
FlexibleChecksumsResponseMiddlewareConfig {}
1824

1925
export const getFlexibleChecksumsPlugin = (
@@ -22,6 +28,10 @@ export const getFlexibleChecksumsPlugin = (
2228
): Pluggable<any, any> => ({
2329
applyToStack: (clientStack) => {
2430
clientStack.add(flexibleChecksumsMiddleware(config, middlewareConfig), flexibleChecksumsMiddlewareOptions);
31+
clientStack.addRelativeTo(
32+
flexibleChecksumsInputMiddleware(config, middlewareConfig),
33+
flexibleChecksumsInputMiddlewareOptions
34+
);
2535
clientStack.addRelativeTo(
2636
flexibleChecksumsResponseMiddleware(config, middlewareConfig),
2737
flexibleChecksumsResponseMiddlewareOptions

‎packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts

+132-101
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Readable, Transform } from "stream";
44
import { describe, expect, test as it } from "vitest";
55

66
import { requireRequestsFrom } from "../../../private/aws-util-test/src";
7+
import { DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
78

89
describe("middleware-flexible-checksums", () => {
910
const logger = {
@@ -14,7 +15,7 @@ describe("middleware-flexible-checksums", () => {
1415
error() {},
1516
};
1617

17-
const testCases: [string, ChecksumAlgorithm, string][] = [
18+
const testCases: [string, ChecksumAlgorithm | undefined, string][] = [
1819
["", ChecksumAlgorithm.CRC32, "AAAAAA=="],
1920
["abc", ChecksumAlgorithm.CRC32, "NSRBwg=="],
2021
["Hello world", ChecksumAlgorithm.CRC32, "i9aeUg=="],
@@ -30,118 +31,146 @@ describe("middleware-flexible-checksums", () => {
3031
["", ChecksumAlgorithm.SHA256, "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="],
3132
["abc", ChecksumAlgorithm.SHA256, "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="],
3233
["Hello world", ChecksumAlgorithm.SHA256, "ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw="],
34+
35+
// Choose default checksum algorithm when explicily not provided.
36+
["", undefined, "AAAAAA=="],
37+
["abc", undefined, "NSRBwg=="],
38+
["Hello world", undefined, "i9aeUg=="],
3339
];
3440

3541
describe(S3.name, () => {
36-
const client = new S3({ region: "us-west-2", logger });
37-
3842
describe("putObject", () => {
39-
testCases.forEach(([body, checksumAlgorithm, checksumValue]) => {
40-
const checksumHeader = `x-amz-checksum-${checksumAlgorithm.toLowerCase()}`;
41-
42-
it(`sets ${checksumHeader}="${checksumValue}"" for checksum="${checksumAlgorithm}"`, async () => {
43-
requireRequestsFrom(client).toMatch({
44-
method: "PUT",
45-
hostname: "s3.us-west-2.amazonaws.com",
46-
protocol: "https:",
47-
path: "/b/k",
48-
headers: {
49-
"content-type": "application/octet-stream",
50-
...(body.length
51-
? {
52-
"content-length": body.length.toString(),
53-
Expect: "100-continue",
54-
}
55-
: {}),
56-
"x-amz-sdk-checksum-algorithm": checksumAlgorithm,
57-
[checksumHeader]: checksumValue,
58-
host: "s3.us-west-2.amazonaws.com",
59-
"x-amz-user-agent": /./,
60-
"user-agent": /./,
61-
"amz-sdk-invocation-id": /./,
62-
"amz-sdk-request": /./,
63-
"x-amz-date": /./,
64-
"x-amz-content-sha256": /./,
65-
authorization: /./,
66-
},
67-
query: {
68-
"x-id": "PutObject",
69-
},
43+
describe.each([undefined, RequestChecksumCalculation.WHEN_SUPPORTED, RequestChecksumCalculation.WHEN_REQUIRED])(
44+
`when requestChecksumCalculation='%s'`,
45+
(requestChecksumCalculation) => {
46+
testCases.forEach(([body, checksumAlgorithm, checksumValue]) => {
47+
const client = new S3({ region: "us-west-2", logger, requestChecksumCalculation });
48+
const checksumHeader = `x-amz-checksum-${(checksumAlgorithm ?? DEFAULT_CHECKSUM_ALGORITHM).toLowerCase()}`;
49+
50+
it(`tests ${checksumHeader}="${checksumValue}"" for checksum="${checksumAlgorithm}"`, async () => {
51+
requireRequestsFrom(client).toMatch({
52+
method: "PUT",
53+
hostname: "s3.us-west-2.amazonaws.com",
54+
protocol: "https:",
55+
path: "/b/k",
56+
headers: {
57+
"content-type": "application/octet-stream",
58+
...(body.length
59+
? {
60+
"content-length": body.length.toString(),
61+
Expect: "100-continue",
62+
}
63+
: {}),
64+
...(requestChecksumCalculation === RequestChecksumCalculation.WHEN_REQUIRED &&
65+
checksumAlgorithm === undefined
66+
? {}
67+
: {
68+
"x-amz-sdk-checksum-algorithm": checksumAlgorithm,
69+
[checksumHeader]: checksumValue,
70+
}),
71+
host: "s3.us-west-2.amazonaws.com",
72+
"x-amz-user-agent": /./,
73+
"user-agent": /./,
74+
"amz-sdk-invocation-id": /./,
75+
"amz-sdk-request": /./,
76+
"x-amz-date": /./,
77+
"x-amz-content-sha256": /./,
78+
authorization: /./,
79+
},
80+
query: {
81+
"x-id": "PutObject",
82+
},
83+
});
84+
85+
await client.putObject({
86+
Bucket: "b",
87+
Key: "k",
88+
Body: body,
89+
ChecksumAlgorithm: checksumAlgorithm as ChecksumAlgorithm,
90+
});
91+
92+
expect.hasAssertions();
93+
});
7094
});
71-
72-
await client.putObject({
73-
Bucket: "b",
74-
Key: "k",
75-
Body: body,
76-
ChecksumAlgorithm: checksumAlgorithm,
77-
});
78-
79-
expect.hasAssertions();
80-
});
81-
});
95+
}
96+
);
8297
});
8398

8499
describe("getObject", () => {
85-
testCases.forEach(([body, checksumAlgorithm, checksumValue]) => {
86-
const checksumHeader = `x-amz-checksum-${checksumAlgorithm.toLowerCase()}`;
87-
88-
it(`validates ${checksumHeader}="${checksumValue}"" set for checksum="${checksumAlgorithm}"`, async () => {
89-
const client = new S3({
90-
region: "us-west-2",
91-
logger,
92-
requestHandler: new (class implements HttpHandler {
93-
async handle(request: HttpRequest): Promise<any> {
94-
expect(request).toMatchObject({
95-
method: "GET",
96-
hostname: "s3.us-west-2.amazonaws.com",
97-
protocol: "https:",
98-
path: "/b/k",
99-
headers: {
100-
"x-amz-checksum-mode": "ENABLED",
101-
host: "s3.us-west-2.amazonaws.com",
102-
"x-amz-user-agent": /./,
103-
"user-agent": /./,
104-
"amz-sdk-invocation-id": /./,
105-
"amz-sdk-request": /./,
106-
"x-amz-date": /./,
107-
"x-amz-content-sha256": /./,
108-
authorization: /./,
109-
},
110-
query: {
111-
"x-id": "GetObject",
112-
},
113-
});
114-
return {
115-
response: new HttpResponse({
116-
statusCode: 200,
117-
headers: {
118-
"content-type": "application/octet-stream",
119-
"content-length": body.length.toString(),
120-
[checksumHeader]: checksumValue,
121-
},
122-
body: Readable.from([body]),
123-
}),
124-
};
125-
}
126-
updateHttpClientConfig(key: never, value: never): void {}
127-
httpHandlerConfigs() {
128-
return {};
129-
}
130-
})(),
131-
});
132-
133-
const response = await client.getObject({
134-
Bucket: "b",
135-
Key: "k",
136-
ChecksumMode: "ENABLED",
100+
describe.each([undefined, ResponseChecksumValidation.WHEN_SUPPORTED, ResponseChecksumValidation.WHEN_REQUIRED])(
101+
`when responseChecksumValidation='%s'`,
102+
(responseChecksumValidation) => {
103+
testCases.forEach(([body, checksumAlgorithm, checksumValue]) => {
104+
const checksumHeader = `x-amz-checksum-${(checksumAlgorithm ?? DEFAULT_CHECKSUM_ALGORITHM).toLowerCase()}`;
105+
106+
it(`validates ${checksumHeader}="${checksumValue}"" for checksum="${checksumAlgorithm}"`, async () => {
107+
const client = new S3({
108+
region: "us-west-2",
109+
logger,
110+
requestHandler: new (class implements HttpHandler {
111+
async handle(request: HttpRequest): Promise<any> {
112+
expect(request).toMatchObject({
113+
method: "GET",
114+
hostname: "s3.us-west-2.amazonaws.com",
115+
protocol: "https:",
116+
path: "/b/k",
117+
headers: {
118+
...(responseChecksumValidation === ResponseChecksumValidation.WHEN_REQUIRED &&
119+
!checksumAlgorithm
120+
? {}
121+
: {
122+
"x-amz-checksum-mode": "ENABLED",
123+
}),
124+
host: "s3.us-west-2.amazonaws.com",
125+
"x-amz-user-agent": /./,
126+
"user-agent": /./,
127+
"amz-sdk-invocation-id": /./,
128+
"amz-sdk-request": /./,
129+
"x-amz-date": /./,
130+
"x-amz-content-sha256": /./,
131+
authorization: /./,
132+
},
133+
query: {
134+
"x-id": "GetObject",
135+
},
136+
});
137+
return {
138+
response: new HttpResponse({
139+
statusCode: 200,
140+
headers: {
141+
"content-type": "application/octet-stream",
142+
"content-length": body.length.toString(),
143+
[checksumHeader]: checksumValue,
144+
},
145+
body: Readable.from([body]),
146+
}),
147+
};
148+
}
149+
updateHttpClientConfig(key: never, value: never): void {}
150+
httpHandlerConfigs() {
151+
return {};
152+
}
153+
})(),
154+
responseChecksumValidation,
155+
});
156+
157+
const response = await client.getObject({
158+
Bucket: "b",
159+
Key: "k",
160+
// Do not pass ChecksumMode if algorithm is not explicitly defined. It'll be set by SDK.
161+
ChecksumMode: checksumAlgorithm ? "ENABLED" : undefined,
162+
});
163+
164+
await expect(response.Body?.transformToString()).resolves.toEqual(body);
165+
});
137166
});
138-
139-
await expect(response.Body?.transformToString()).resolves.toEqual(body);
140-
});
141-
});
167+
}
168+
);
142169
});
143170

144171
it("should not set binary file content length", async () => {
172+
const client = new S3({ region: "us-west-2", logger });
173+
145174
requireRequestsFrom(client).toMatch({
146175
method: "PUT",
147176
hostname: "s3.us-west-2.amazonaws.com",
@@ -182,6 +211,8 @@ describe("middleware-flexible-checksums", () => {
182211
["CRC32C", "V"],
183212
].forEach(([algo, id]) => {
184213
it(`should feature-detect checksum ${algo}=${id}`, async () => {
214+
const client = new S3({ region: "us-west-2", logger });
215+
185216
requireRequestsFrom(client).toMatch({
186217
headers: {
187218
"user-agent": new RegExp(`(.*?) m\/(.*?)${id}(.*?)$`),

0 commit comments

Comments
 (0)
Please sign in to comment.