From a73baca408733b2ee0397b898af5ececd84afc43 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 17 Jul 2023 12:27:40 -0400 Subject: [PATCH] Add defensiveless for malformed hash urls without a leading slash --- .changeset/hash-leading-slash.md | 5 +++++ packages/router/__tests__/hash-test.ts | 16 ++++++++++++++++ packages/router/history.ts | 11 +++++++++++ 3 files changed, 32 insertions(+) create mode 100644 .changeset/hash-leading-slash.md diff --git a/.changeset/hash-leading-slash.md b/.changeset/hash-leading-slash.md new file mode 100644 index 0000000000..7d180ee401 --- /dev/null +++ b/.changeset/hash-leading-slash.md @@ -0,0 +1,5 @@ +--- +"@remix-run/router": patch +--- + +Ensure hash history always includes a leading slash on hash pathnames diff --git a/packages/router/__tests__/hash-test.ts b/packages/router/__tests__/hash-test.ts index f4b503d9a0..76d60255a0 100644 --- a/packages/router/__tests__/hash-test.ts +++ b/packages/router/__tests__/hash-test.ts @@ -65,6 +65,22 @@ describe("a hash history", () => { expect(unencodedHref).toEqual("#/#abc"); }); + it("prefixes raw hash values with /", () => { + dom.window.history.replaceState(null, "", "#hello"); + history = createHashHistory({ window: dom.window as unknown as Window }); + expect(history.location.pathname).toBe("/hello"); + + history.push("world"); + expect(history.location.pathname).toBe("/world"); + + // Not supported but ensure we don't prefix here + history.push("./relative"); + expect(history.location.pathname).toBe("./relative"); + + history.push("../relative"); + expect(history.location.pathname).toBe("../relative"); + }); + describe("listen", () => { it("does not immediately call listeners", () => { Listen(history); diff --git a/packages/router/history.ts b/packages/router/history.ts index f12845324a..8a9ef89e38 100644 --- a/packages/router/history.ts +++ b/packages/router/history.ts @@ -423,6 +423,17 @@ export function createHashHistory( search = "", hash = "", } = parsePath(window.location.hash.substr(1)); + + // Hash URL should always have a leading / just like window.location.pathname + // does, so if an app ends up at a route like /#something then we add a + // leading slash so all of our path-matching behaves the same as if it would + // in a browser router. This is particularly important when there exists a + // root splat route () since that matches internally against + // "/*" and we'd expect /#something to 404 in a hash router app. + if (!pathname.startsWith("/") && !pathname.startsWith(".")) { + pathname = "/" + pathname; + } + return createLocation( "", { pathname, search, hash },