Skip to content

Commit b20d431

Browse files
authored
fix(signature-v4): normalized the path before double encoding it (#3408)
1 parent a4cc40a commit b20d431

File tree

2 files changed

+54
-5
lines changed

2 files changed

+54
-5
lines changed

packages/signature-v4/src/SignatureV4.spec.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,19 @@ describe("SignatureV4", () => {
317317
expect(query[SIGNATURE_QUERY_PARAM]).toBe("6267d8b6f44d165d2b9f4d2c2b45fd6971de0962820243669bf685818c9c7849");
318318
});
319319

320+
it("should normalize relative path by default", async () => {
321+
const { query = {} } = await signer.presign(
322+
{ ...minimalRequest, path: "/abc/../foo%3Dbar" },
323+
presigningOptions
324+
);
325+
expect(query[SIGNATURE_QUERY_PARAM]).toBe("6267d8b6f44d165d2b9f4d2c2b45fd6971de0962820243669bf685818c9c7849");
326+
});
327+
328+
it("should normalize path with consecutive slashes by default", async () => {
329+
const { query = {} } = await signer.presign({ ...minimalRequest, path: "//foo%3Dbar" }, presigningOptions);
330+
expect(query[SIGNATURE_QUERY_PARAM]).toBe("6267d8b6f44d165d2b9f4d2c2b45fd6971de0962820243669bf685818c9c7849");
331+
});
332+
320333
it("should not URI-encode the path if URI path escaping was disabled on the signer", async () => {
321334
// Setting `uriEscapePath` to `false` creates an
322335
// S3-compatible signer. The expected signature included
@@ -573,15 +586,31 @@ describe("SignatureV4", () => {
573586
hostname: "foo.us-bar-1.amazonaws.com",
574587
});
575588

589+
const signingOptions = {
590+
signingDate: new Date("2000-01-01T00:00:00.000Z"),
591+
};
592+
576593
it("should URI-encode the path by default", async () => {
577-
const { headers } = await signer.sign(minimalRequest, {
578-
signingDate: new Date("2000-01-01T00:00:00.000Z"),
579-
});
594+
const { headers } = await signer.sign(minimalRequest, signingOptions);
580595
expect(headers[AUTH_HEADER]).toBe(
581596
"AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=fb4948cab44a9c47ce3b1a2489d01ec939fea9e79eccdb4593c11a94f207e075"
582597
);
583598
});
584599

600+
it("should normalize relative path by default", async () => {
601+
const { headers } = await signer.sign({ ...minimalRequest, path: "/abc/../foo%3Dbar" }, signingOptions);
602+
expect(headers[AUTH_HEADER]).toEqual(
603+
expect.stringContaining("Signature=fb4948cab44a9c47ce3b1a2489d01ec939fea9e79eccdb4593c11a94f207e075")
604+
);
605+
});
606+
607+
it("should normalize path with consecutive slashes by default", async () => {
608+
const { headers } = await signer.sign({ ...minimalRequest, path: "//foo%3Dbar" }, signingOptions);
609+
expect(headers[AUTH_HEADER]).toEqual(
610+
expect.stringContaining("Signature=fb4948cab44a9c47ce3b1a2489d01ec939fea9e79eccdb4593c11a94f207e075")
611+
);
612+
});
613+
585614
it("should not URI-encode the path if URI path escaping was disabled on the signer", async () => {
586615
// Setting `uriEscapePath` to `false` creates an
587616
// S3-compatible signer. The expected authorization header

packages/signature-v4/src/SignatureV4.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,30 @@ ${toHex(hashedRequest)}`;
279279

280280
private getCanonicalPath({ path }: HttpRequest): string {
281281
if (this.uriEscapePath) {
282-
const doubleEncoded = encodeURIComponent(path.replace(/^\//, ""));
283-
return `/${doubleEncoded.replace(/%2F/g, "/")}`;
282+
// Non-S3 services, we normalize the path and then double URI encode it.
283+
// Ref: "Remove Dot Segments" https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
284+
const normalizedPathSegments = [];
285+
for (const pathSegment of path.split("/")) {
286+
if (pathSegment?.length === 0) continue;
287+
if (pathSegment === ".") continue;
288+
if (pathSegment === "..") {
289+
normalizedPathSegments.pop();
290+
} else {
291+
normalizedPathSegments.push(pathSegment);
292+
}
293+
}
294+
// Joining by single slashes to remove consecutive slashes.
295+
const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${
296+
normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : ""
297+
}`;
298+
299+
const doubleEncoded = encodeURIComponent(normalizedPath);
300+
return doubleEncoded.replace(/%2F/g, "/");
284301
}
285302

303+
// For S3, we shouldn't normalize the path. For example, object name
304+
// my-object//example//photo.user should not be normalized to
305+
// my-object/example/photo.user
286306
return path;
287307
}
288308

0 commit comments

Comments
 (0)