Skip to content

Commit e4455a7

Browse files
authored
fix(middleware-flexible-checksums): split stream when validating response checksum (#5126)
1 parent 4c4bf11 commit e4455a7

9 files changed

+134
-14
lines changed

packages/middleware-flexible-checksums/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
},
1515
"main": "./dist-cjs/index.js",
1616
"module": "./dist-es/index.js",
17+
"browser": {
18+
"./dist-es/streams/create-read-stream-on-buffer": "./dist-es/streams/create-read-stream-on-buffer.browser"
19+
},
1720
"types": "./dist-types/index.d.ts",
1821
"author": {
1922
"name": "AWS SDK for JavaScript Team",
@@ -31,6 +34,7 @@
3134
"tslib": "^2.5.0"
3235
},
3336
"devDependencies": {
37+
"@smithy/node-http-handler": "^2.0.3",
3438
"concurrently": "7.0.0",
3539
"downlevel-dts": "0.10.1",
3640
"rimraf": "3.0.2",

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Encoder,
55
GetAwsChunkedEncodingStream,
66
HashConstructor,
7+
StreamCollector,
78
StreamHasher,
89
} from "@smithy/types";
910

@@ -47,4 +48,9 @@ export interface PreviouslyResolved {
4748
* @internal
4849
*/
4950
streamHasher: StreamHasher<any>;
51+
52+
/**
53+
* Collects streams into buffers.
54+
*/
55+
streamCollector: StreamCollector;
5056
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { HttpRequest } from "@smithy/protocol-http";
2-
import { BuildHandlerArguments } from "@smithy/types";
2+
import { BuildHandlerArguments, DeserializeHandlerArguments } from "@smithy/types";
33

44
import { PreviouslyResolved } from "./configuration";
55
import { ChecksumAlgorithm } from "./constants";
66
import { flexibleChecksumsMiddleware } from "./flexibleChecksumsMiddleware";
7+
import { flexibleChecksumsResponseMiddleware } from "./flexibleChecksumsResponseMiddleware";
78
import { getChecksumAlgorithmForRequest } from "./getChecksumAlgorithmForRequest";
89
import { getChecksumLocationName } from "./getChecksumLocationName";
910
import { hasHeader } from "./hasHeader";
@@ -194,14 +195,14 @@ describe(flexibleChecksumsMiddleware.name, () => {
194195
const mockInput = { [mockRequestValidationModeMember]: "ENABLED" };
195196
const mockResponseAlgorithms = ["ALGO1", "ALGO2"];
196197

197-
const handler = flexibleChecksumsMiddleware(mockConfig, {
198+
const responseHandler = flexibleChecksumsResponseMiddleware(mockConfig, {
198199
...mockMiddlewareConfig,
199200
input: mockInput,
200201
requestValidationModeMember: mockRequestValidationModeMember,
201202
responseAlgorithms: mockResponseAlgorithms,
202203
})(mockNext, {});
203204

204-
await handler(mockArgs);
205+
await responseHandler({ ...mockArgs, input: mockInput } as DeserializeHandlerArguments<any>);
205206
expect(validateChecksumFromResponse).toHaveBeenCalledWith(mockResult.response, {
206207
config: mockConfig,
207208
responseAlgorithms: mockResponseAlgorithms,

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
1+
import { HttpRequest } from "@smithy/protocol-http";
22
import {
33
BuildHandler,
44
BuildHandlerArguments,
@@ -15,8 +15,10 @@ import { hasHeader } from "./hasHeader";
1515
import { isStreaming } from "./isStreaming";
1616
import { selectChecksumAlgorithmFunction } from "./selectChecksumAlgorithmFunction";
1717
import { stringHasher } from "./stringHasher";
18-
import { validateChecksumFromResponse } from "./validateChecksumFromResponse";
1918

19+
/**
20+
* @internal
21+
*/
2022
export const flexibleChecksumsMiddleware =
2123
(config: PreviouslyResolved, middlewareConfig: FlexibleChecksumsMiddlewareConfig): BuildMiddleware<any, any> =>
2224
<Output extends MetadataBearer>(next: BuildHandler<any, Output>): BuildHandler<any, Output> =>
@@ -78,14 +80,5 @@ export const flexibleChecksumsMiddleware =
7880
},
7981
});
8082

81-
const { requestValidationModeMember, responseAlgorithms } = middlewareConfig;
82-
// @ts-ignore Element implicitly has an 'any' type for input[requestValidationModeMember]
83-
if (requestValidationModeMember && input[requestValidationModeMember] === "ENABLED") {
84-
await validateChecksumFromResponse(result.response as HttpResponse, {
85-
config,
86-
responseAlgorithms,
87-
});
88-
}
89-
9083
return result;
9184
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
2+
import {
3+
DeserializeHandler,
4+
DeserializeHandlerArguments,
5+
DeserializeHandlerOutput,
6+
DeserializeMiddleware,
7+
MetadataBearer,
8+
RelativeMiddlewareOptions,
9+
} from "@smithy/types";
10+
11+
import { PreviouslyResolved } from "./configuration";
12+
import { FlexibleChecksumsMiddlewareConfig } from "./getFlexibleChecksumsPlugin";
13+
import { isStreaming } from "./isStreaming";
14+
import { createReadStreamOnBuffer } from "./streams/create-read-stream-on-buffer";
15+
import { validateChecksumFromResponse } from "./validateChecksumFromResponse";
16+
17+
/**
18+
* @internal
19+
*/
20+
export const flexibleChecksumsResponseMiddlewareOptions: RelativeMiddlewareOptions = {
21+
name: "flexibleChecksumsResponseMiddleware",
22+
toMiddleware: "deserializerMiddleware",
23+
relation: "after",
24+
tags: ["BODY_CHECKSUM"],
25+
override: true,
26+
};
27+
28+
/**
29+
* @internal
30+
*
31+
* The validation counterpart to the flexibleChecksumsMiddleware.
32+
*/
33+
export const flexibleChecksumsResponseMiddleware =
34+
(config: PreviouslyResolved, middlewareConfig: FlexibleChecksumsMiddlewareConfig): DeserializeMiddleware<any, any> =>
35+
<Output extends MetadataBearer>(next: DeserializeHandler<any, Output>): DeserializeHandler<any, Output> =>
36+
async (args: DeserializeHandlerArguments<any>): Promise<DeserializeHandlerOutput<Output>> => {
37+
if (!HttpRequest.isInstance(args.request)) {
38+
return next(args);
39+
}
40+
41+
const input = args.input;
42+
const result = await next(args);
43+
44+
const response = result.response as HttpResponse;
45+
let collectedStream: Uint8Array | undefined = undefined;
46+
47+
const { requestValidationModeMember, responseAlgorithms } = middlewareConfig;
48+
// @ts-ignore Element implicitly has an 'any' type for input[requestValidationModeMember]
49+
if (requestValidationModeMember && input[requestValidationModeMember] === "ENABLED") {
50+
const isStreamingBody = isStreaming(response.body);
51+
52+
if (isStreamingBody) {
53+
collectedStream = await config.streamCollector(response.body);
54+
response.body = createReadStreamOnBuffer(collectedStream);
55+
}
56+
57+
await validateChecksumFromResponse(result.response as HttpResponse, {
58+
config,
59+
responseAlgorithms,
60+
});
61+
62+
if (isStreamingBody && collectedStream) {
63+
response.body = createReadStreamOnBuffer(collectedStream);
64+
}
65+
}
66+
67+
return result;
68+
};

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { BuildHandlerOptions, Pluggable } from "@smithy/types";
22

33
import { PreviouslyResolved } from "./configuration";
44
import { flexibleChecksumsMiddleware } from "./flexibleChecksumsMiddleware";
5+
import {
6+
flexibleChecksumsResponseMiddleware,
7+
flexibleChecksumsResponseMiddlewareOptions,
8+
} from "./flexibleChecksumsResponseMiddleware";
59

610
export const flexibleChecksumsMiddlewareOptions: BuildHandlerOptions = {
711
name: "flexibleChecksumsMiddleware",
@@ -45,5 +49,9 @@ export const getFlexibleChecksumsPlugin = (
4549
): Pluggable<any, any> => ({
4650
applyToStack: (clientStack) => {
4751
clientStack.add(flexibleChecksumsMiddleware(config, middlewareConfig), flexibleChecksumsMiddlewareOptions);
52+
clientStack.addRelativeTo(
53+
flexibleChecksumsResponseMiddleware(config, middlewareConfig),
54+
flexibleChecksumsResponseMiddlewareOptions
55+
);
4856
},
4957
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Convert a buffer to a readable stream.
3+
*/
4+
export function createReadStreamOnBuffer(buffer: Uint8Array): ReadableStream {
5+
return new Blob([buffer]).stream();
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { streamCollector } from "@smithy/node-http-handler";
2+
import { toUtf8 } from "@smithy/util-utf8";
3+
4+
import { createReadStreamOnBuffer } from "./create-read-stream-on-buffer";
5+
6+
describe(createReadStreamOnBuffer.name, () => {
7+
it("converts a buffer into stream of the same contents", async () => {
8+
const buffer = Buffer.from("abcd");
9+
10+
const stream = createReadStreamOnBuffer(buffer);
11+
12+
// changing the buffer here also changes the stream since it has not been collected.
13+
buffer[0] = "z".charCodeAt(0);
14+
15+
const bytes = await streamCollector(stream);
16+
17+
// changing the buffer here does not change the stream since it has been collected.
18+
buffer[1] = "z".charCodeAt(0);
19+
20+
expect(toUtf8(bytes)).not.toEqual("zzcd");
21+
expect(toUtf8(bytes)).toEqual("zbcd");
22+
});
23+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Readable, Transform } from "stream";
2+
3+
/**
4+
* Convert a buffer to a readable stream.
5+
*/
6+
export function createReadStreamOnBuffer(buffer: Uint8Array): Readable {
7+
const stream = new Transform();
8+
stream.push(buffer);
9+
stream.push(null);
10+
return stream;
11+
}

0 commit comments

Comments
 (0)