fix(fasturl, node): normalize pathname if necessary · h3js/srvx@bd8c0d3 · GitHub
Skip to content

Commit bd8c0d3

Browse files
committed
fix(fasturl, node): normalize pathname if necessary
1 parent a8a0225 commit bd8c0d3

5 files changed

Lines changed: 132 additions & 0 deletions

File tree

‎src/_url.ts‎

Lines changed: 10 additions & 0 deletions

‎test/_fixture.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ export const fixture: (
257257
headers: inspect(req.headers),
258258
});
259259
}
260+
case "/bar/baz": {
261+
return Response.json({
262+
pathname: url.pathname,
263+
url: req.url,
264+
});
265+
}
260266
}
261267
return new _Response("404", { status: 404 });
262268
},

‎test/_tests.ts‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { describe, expect, test } from "vitest";
22
import http from "node:http";
3+
import http2 from "node:http2";
34

45
export function addTests(opts: {
56
url: (path: string) => string;
67
runtime: string;
78
fetch?: typeof globalThis.fetch;
89
http2?: boolean;
910
fastResponse?: boolean;
11+
ca?: string;
1012
}): void {
1113
const { url, fetch: _fetch = globalThis.fetch } = opts;
1214

@@ -283,6 +285,61 @@ export function addTests(opts: {
283285
expect(data.includes("x-foo"));
284286
});
285287

288+
test("normalize traversal in request path", async () => {
289+
const _url = new URL(url("/"));
290+
291+
let body: string;
292+
let statusCode: number | undefined;
293+
294+
if (opts.http2) {
295+
const client = http2.connect(_url.origin, { ca: opts.ca });
296+
try {
297+
const req = client.request({ ":path": "/foo/../bar/baz", ":authority": "localhost" });
298+
const res = await new Promise<{ headers: http2.IncomingHttpHeaders; body: string }>(
299+
(resolve, reject) => {
300+
let headers: http2.IncomingHttpHeaders;
301+
const chunks: Uint8Array[] = [];
302+
req.on("response", (h) => {
303+
headers = h;
304+
});
305+
req.on("data", (chunk: Uint8Array) => chunks.push(chunk));
306+
req.on("end", () =>
307+
resolve({ headers: headers!, body: Buffer.concat(chunks).toString("utf8") }),
308+
);
309+
req.on("error", reject);
310+
},
311+
);
312+
statusCode = res.headers[":status"] as number | undefined;
313+
body = res.body;
314+
} finally {
315+
client.close();
316+
}
317+
} else {
318+
const res = await new Promise<http.IncomingMessage>((resolve, reject) => {
319+
const req = http.request({
320+
method: "GET",
321+
path: "/foo/../bar/baz",
322+
hostname: "localhost",
323+
port: _url.port,
324+
});
325+
req.end();
326+
req.on("response", resolve);
327+
req.on("error", reject);
328+
});
329+
statusCode = res.statusCode;
330+
body = await new Promise<string>((resolve, reject) => {
331+
const chunks: Uint8Array[] = [];
332+
res.on("data", (chunk) => chunks.push(chunk));
333+
res.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
334+
res.on("error", reject);
335+
});
336+
}
337+
338+
expect(statusCode).toBe(200);
339+
const data = JSON.parse(body);
340+
expect(data.pathname).toBe("/bar/baz");
341+
});
342+
286343
// TODO: Write test to make sure it is forbidden for http2/tls
287344
test.skipIf(opts.http2)("absolute path in request line", async () => {
288345
const _url = new URL(url("/"));

‎test/node.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ for (const config of testConfigs) {
7171
http2: config.http2,
7272
fetch: client.fetch,
7373
fastResponse: config.fastResponse,
74+
ca: tls.ca,
7475
});
7576
});
7677
}

‎test/url.test.ts‎

Lines changed: 58 additions & 0 deletions

0 commit comments

Comments
 (0)