Skip to content

Commit b2660cf

Browse files
committed
Extract functions to get URL params / path
We don't want to accidentially use url.hostname (which might be localhost)
1 parent 56f1c7b commit b2660cf

File tree

7 files changed

+113
-30
lines changed

7 files changed

+113
-30
lines changed

library/helpers/buildRouteFromURL.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
import { tryParseURL } from "./tryParseURL";
1+
import { tryParseURLPath } from "./tryParseURLPath";
22

33
const UUID =
44
/(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
55
const NUMBER = /^\d+$/;
66
const DATE = /^\d{4}-\d{2}-\d{2}|\d{2}-\d{2}-\d{4}$/;
77

88
export function buildRouteFromURL(url: string) {
9-
const parsed = tryParseURL(
10-
url.startsWith("/") ? `http://localhost${url}` : url
11-
);
9+
const path = tryParseURLPath(url);
1210

13-
if (!parsed || !parsed.pathname) {
11+
if (!path) {
1412
return undefined;
1513
}
1614

17-
const route = parsed.pathname
18-
.split("/")
19-
.map(replaceURLSegmentWithParam)
20-
.join("/");
15+
const route = path.split("/").map(replaceURLSegmentWithParam).join("/");
2116

2217
if (route === "/") {
2318
return "/";

library/helpers/matchEndpoint.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Endpoint } from "../agent/Config";
22
import { Context } from "../agent/Context";
3-
import { tryParseURL } from "./tryParseURL";
3+
import { tryParseURLPath } from "./tryParseURLPath";
44

55
export type LimitedContext = Pick<Context, "url" | "method" | "route">;
66

@@ -31,11 +31,9 @@ export function matchEndpoint(context: LimitedContext, endpoints: Endpoint[]) {
3131

3232
// req.url is relative, so we need to prepend a host to make it absolute
3333
// We just match the pathname, we don't use the host for matching
34-
const url = tryParseURL(
35-
context.url.startsWith("/") ? `http://localhost${context.url}` : context.url
36-
);
34+
const path = tryParseURLPath(context.url);
3735

38-
if (!url) {
36+
if (!path) {
3937
return undefined;
4038
}
4139

@@ -52,7 +50,7 @@ export function matchEndpoint(context: LimitedContext, endpoints: Endpoint[]) {
5250
"i"
5351
);
5452

55-
if (regex.test(url.pathname)) {
53+
if (regex.test(path)) {
5654
return { endpoint: wildcard, route: wildcard.route };
5755
}
5856
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as t from "tap";
2+
import { tryParseURLParams } from "./tryParseURLParams";
3+
4+
t.test("it returns undefined if invalid URL", async () => {
5+
t.same(tryParseURLParams("abc"), new URLSearchParams());
6+
});
7+
8+
t.test("it returns search params for /", async () => {
9+
t.same(tryParseURLParams("/"), new URLSearchParams());
10+
});
11+
12+
t.test("it returns search params for relative URL", async () => {
13+
t.same(tryParseURLParams("/posts"), new URLSearchParams());
14+
});
15+
16+
t.test("it returns search params for relative URL with query", async () => {
17+
t.same(tryParseURLParams("/posts?abc=def"), new URLSearchParams("abc=def"));
18+
});
19+
20+
t.test("it returns search params", async () => {
21+
t.same(tryParseURLParams("http://localhost/posts/3"), new URLSearchParams());
22+
});
23+
24+
t.test("it returns search params with query", async () => {
25+
t.same(
26+
tryParseURLParams("http://localhost/posts/3?abc=def"),
27+
new URLSearchParams("abc=def")
28+
);
29+
});

library/helpers/tryParseURLParams.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { tryParseURL } from "./tryParseURL";
2+
3+
export function tryParseURLParams(url: string) {
4+
const parsed = tryParseURL(
5+
url.startsWith("/") ? `http://localhost${url}` : url
6+
);
7+
8+
if (!parsed) {
9+
return new URLSearchParams();
10+
}
11+
12+
return parsed.searchParams;
13+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as t from "tap";
2+
import { tryParseURLPath } from "./tryParseURLPath";
3+
4+
t.test("it returns undefined if nothing found", async () => {
5+
t.equal(tryParseURLPath("abc"), undefined);
6+
});
7+
8+
t.test("it returns pathname for /", async () => {
9+
t.equal(tryParseURLPath("/"), "/");
10+
});
11+
12+
t.test("it returns pathname for relative URL", async () => {
13+
t.equal(tryParseURLPath("/posts"), "/posts");
14+
});
15+
16+
t.test("it returns pathname for relative URL with query", async () => {
17+
t.equal(tryParseURLPath("/posts?abc=def"), "/posts");
18+
});
19+
20+
t.test("it returns pathname", async () => {
21+
t.equal(tryParseURLPath("http://localhost/posts/3"), "/posts/3");
22+
});
23+
24+
t.test("it returns pathname with query", async () => {
25+
t.equal(tryParseURLPath("http://localhost/posts/3?abc=def"), "/posts/3");
26+
});
27+
28+
t.test("it returns pathname with hash", async () => {
29+
t.equal(tryParseURLPath("http://localhost/posts/3#abc"), "/posts/3");
30+
});
31+
32+
t.test("it returns pathname with query and hash", async () => {
33+
t.equal(tryParseURLPath("http://localhost/posts/3?abc=def#ghi"), "/posts/3");
34+
});
35+
36+
t.test("it returns pathname with query and hash and no path", async () => {
37+
t.equal(tryParseURLPath("http://localhost/?abc=def#ghi"), "/");
38+
});
39+
40+
t.test("it returns pathname with query and no path", async () => {
41+
t.equal(tryParseURLPath("http://localhost?abc=def"), "/");
42+
});
43+
44+
t.test("it returns pathname with hash and no path", async () => {
45+
t.equal(tryParseURLPath("http://localhost#abc"), "/");
46+
});

library/helpers/tryParseURLPath.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { tryParseURL } from "./tryParseURL";
2+
3+
export function tryParseURLPath(url: string) {
4+
const parsed = tryParseURL(
5+
url.startsWith("/") ? `http://localhost${url}` : url
6+
);
7+
8+
if (!parsed) {
9+
return undefined;
10+
}
11+
12+
return parsed.pathname;
13+
}

library/sources/http-server/contextFromRequest.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,17 @@ import { Context } from "../../agent/Context";
33
import { buildRouteFromURL } from "../../helpers/buildRouteFromURL";
44
import { getIPAddressFromRequest } from "../../helpers/getIPAddressFromRequest";
55
import { parse } from "../../helpers/parseCookies";
6+
import { tryParseURLParams } from "../../helpers/tryParseURLParams";
67

78
export function contextFromRequest(
89
req: IncomingMessage,
910
body: string | undefined,
1011
module: string
1112
): Context {
12-
let parsedURL: URL | undefined = undefined;
13-
if (req.url) {
14-
try {
15-
// req.url is relative, so we need to prepend a host to make it absolute
16-
// We just need the searchParams, we don't use the host
17-
parsedURL = new URL(
18-
req.url.startsWith("/") ? `http://localhost${req.url}` : req.url
19-
);
20-
} catch (e) {
21-
// Ignore
22-
}
23-
}
24-
2513
const queryObject: Record<string, string> = {};
26-
if (parsedURL) {
27-
for (const [key, value] of parsedURL.searchParams.entries()) {
14+
if (req.url) {
15+
const params = tryParseURLParams(req.url);
16+
for (const [key, value] of params.entries()) {
2817
queryObject[key] = value;
2918
}
3019
}

0 commit comments

Comments
 (0)