Skip to content

Commit

Permalink
Fix Pages duplicating hash in redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
WalshyDev committed Sep 11, 2024
1 parent 648cfdd commit 8acab3b
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-birds-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/pages-shared": patch
---

fix: fix Pages redirects going to a hash location to be duped. This means if you have a rule like `/foo/bar /foo#bar` it will no longer result in `/foo#bar#bar` but the correct `/foo#bar`.
147 changes: 113 additions & 34 deletions packages/pages-shared/__tests__/asset-server/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,40 +390,6 @@ describe("asset-server handler", () => {
});
}

// test("Returns a redirect without duplicating the hash component", async () => {
// const { response, spies } = await getTestResponse({
// request: "https://foo.com/bar",
// metadata: createMetadataObjectWithRedirects([
// { from: "/bar", to: "https://foobar.com/##heading-7", status: 301 },
// ]),
// });

// expect(spies.fetchAsset).toBe(0);
// expect(spies.findAssetEntryForPath).toBe(0);
// expect(spies.getAssetKey).toBe(0);
// expect(spies.negotiateContent).toBe(0);
// expect(response.status).toBe(301);
// expect(response.headers.get("Location")).toBe(
// "https://foobar.com/##heading-7"
// );
// });

test("it should redirect uri-encoded paths", async () => {
const { response, spies } = await getTestResponse({
request: "https://foo.com/some%20page",
metadata: createMetadataObjectWithRedirects([
{ from: "/some%20page", to: "/home", status: 301 },
]),
});

expect(spies.fetchAsset).toBe(0);
expect(spies.findAssetEntryForPath).toBe(0);
expect(spies.getAssetKey).toBe(0);
expect(spies.negotiateContent).toBe(0);
expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe("/home");
});

// test("getResponseFromMatch - same origin paths specified as root-relative", () => {
// const res = getResponseFromMatch(
// {
Expand Down Expand Up @@ -920,6 +886,119 @@ describe("asset-server handler", () => {
);
});
});

describe("redirects", () => {
test("it should redirect uri-encoded paths", async () => {
const { response, spies } = await getTestResponse({
request: "https://foo.com/some%20page",
metadata: createMetadataObjectWithRedirects([
{ from: "/some%20page", to: "/home", status: 301 },
]),
});

expect(spies.fetchAsset).toBe(0);
expect(spies.findAssetEntryForPath).toBe(0);
expect(spies.getAssetKey).toBe(0);
expect(spies.negotiateContent).toBe(0);
expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe("/home");
});

test("redirects to a query string same-origin", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "/?test=abc", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe("/?test=abc");
});

test("redirects to a query string cross-origin", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "https://foobar.com/?test=abc", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe(
"https://foobar.com/?test=abc"
);
});

test("redirects to hash component same-origin", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "https://foo.com/##heading-7", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe("/##heading-7");
});

test("redirects to hash component cross-origin", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "https://foobar.com/##heading-7", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe(
"https://foobar.com/##heading-7"
);
});

test("redirects to a query string and hash same-origin", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "/?test=abc#def", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe("/?test=abc#def");
});

test("redirects to a query string and hash cross-origin", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "https://foobar.com/?test=abc#def", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe(
"https://foobar.com/?test=abc#def"
);
});

// Query strings must be before the hash to be considered query strings
// https://www.rfc-editor.org/rfc/rfc3986#section-4.1
// Behaviour in Chrome is that the .hash is "#def?test=abc" and .search is ""
test("redirects to a query string and hash against rfc", async () => {
const { response } = await getTestResponse({
request: "https://foo.com/bar",
metadata: createMetadataObjectWithRedirects([
{ from: "/bar", to: "https://foobar.com/#def?test=abc", status: 301 },
]),
});

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe(
"https://foobar.com/#def?test=abc"
);
});
});
});

interface HandlerSpies {
Expand Down
5 changes: 2 additions & 3 deletions packages/pages-shared/asset-server/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,8 @@ export async function generateHandler<
? `${destination.pathname}${destination.search || search}${
destination.hash
}`
: `${destination.href}${destination.search ? "" : search}${
destination.hash
}`;
: `${destination.href}${destination.search ? "" : search}`;

switch (status) {
case 301:
return new MovedPermanentlyResponse(location, undefined, {
Expand Down

0 comments on commit 8acab3b

Please sign in to comment.