Skip to content

Commit 56928de

Browse files
committed
Normalize double slashes in resolvePath
1 parent 4b5bc4b commit 56928de

File tree

3 files changed

+85
-5
lines changed

3 files changed

+85
-5
lines changed

.changeset/forty-tips-admire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Normalize double-slashes in `resolvePath`

packages/react-router/__tests__/resolvePath-test.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,38 @@
11
import { resolvePath } from "react-router";
22

33
describe("resolvePath", () => {
4+
it("does not touch with protocol-less absolute paths", () => {
5+
expect(resolvePath("//google.com")).toMatchObject({
6+
pathname: "//google.com",
7+
});
8+
9+
expect(resolvePath("//google.com/../../path")).toMatchObject({
10+
pathname: "//google.com/../../path",
11+
});
12+
13+
expect(resolvePath("//google.com?q=query#hash")).toMatchObject({
14+
pathname: "//google.com",
15+
search: "?q=query",
16+
hash: "#hash",
17+
});
18+
});
19+
420
it('resolves absolute paths irrespective of the "from" pathname', () => {
521
expect(resolvePath("/search", "/inbox")).toMatchObject({
622
pathname: "/search",
723
});
24+
25+
expect(resolvePath("/search/../123", "/inbox")).toMatchObject({
26+
pathname: "/123",
27+
});
28+
29+
expect(resolvePath("/search/../../123", "/inbox")).toMatchObject({
30+
pathname: "/123",
31+
});
32+
33+
expect(resolvePath("/search/user/../../123", "/inbox")).toMatchObject({
34+
pathname: "/123",
35+
});
836
});
937

1038
it("resolves relative paths", () => {
@@ -23,6 +51,32 @@ describe("resolvePath", () => {
2351
expect(resolvePath("search", "/inbox")).toMatchObject({
2452
pathname: "/inbox/search",
2553
});
54+
55+
expect(resolvePath("search/../123", "/inbox")).toMatchObject({
56+
pathname: "/inbox/123",
57+
});
58+
59+
expect(resolvePath("search/../../123", "/inbox")).toMatchObject({
60+
pathname: "/123",
61+
});
62+
63+
expect(resolvePath("search/../../../123", "/inbox")).toMatchObject({
64+
pathname: "/123",
65+
});
66+
});
67+
68+
it("normalizes any mid-path double-slashes", () => {
69+
let spy = jest.spyOn(console, "warn").mockImplementation(() => {});
70+
71+
expect(resolvePath("/search/../..//foo")).toMatchObject({
72+
pathname: "/foo",
73+
});
74+
75+
expect(resolvePath("search/../..//foo", "/inbox")).toMatchObject({
76+
pathname: "/foo",
77+
});
78+
79+
spy.mockRestore();
2680
});
2781

2882
it('ignores trailing slashes on the "from" pathname when resolving relative paths', () => {

packages/router/utils.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,9 @@ export function stripBasename(
11281128
return pathname.slice(startIndex) || "/";
11291129
}
11301130

1131+
const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1132+
export const isAbsoluteUrl = (url: string) => ABSOLUTE_URL_REGEX.test(url);
1133+
11311134
/**
11321135
* Returns a resolved path object relative to the given pathname.
11331136
*
@@ -1140,11 +1143,29 @@ export function resolvePath(to: To, fromPathname = "/"): Path {
11401143
hash = "",
11411144
} = typeof to === "string" ? parsePath(to) : to;
11421145

1143-
let pathname = toPathname
1144-
? toPathname.startsWith("/")
1145-
? toPathname
1146-
: resolvePathname(toPathname, fromPathname)
1147-
: fromPathname;
1146+
let pathname: string;
1147+
if (toPathname) {
1148+
if (isAbsoluteUrl(toPathname)) {
1149+
pathname = toPathname;
1150+
} else {
1151+
if (toPathname.includes("//")) {
1152+
let oldPathname = toPathname;
1153+
toPathname = toPathname.replace(/\/\/+/g, "/");
1154+
warning(
1155+
false,
1156+
`Pathnames cannot have embedded double slashes - normalizing ` +
1157+
`${oldPathname} -> ${toPathname}`
1158+
);
1159+
}
1160+
if (toPathname.startsWith("/")) {
1161+
pathname = resolvePathname(toPathname.substring(1), "/");
1162+
} else {
1163+
pathname = resolvePathname(toPathname, fromPathname);
1164+
}
1165+
}
1166+
} else {
1167+
pathname = fromPathname;
1168+
}
11481169

11491170
return {
11501171
pathname,

0 commit comments

Comments
 (0)