|
1 | 1 | import { describe, expect, test } from "vitest"; |
2 | 2 | import http from "node:http"; |
| 3 | +import http2 from "node:http2"; |
3 | 4 |
|
4 | 5 | export function addTests(opts: { |
5 | 6 | url: (path: string) => string; |
6 | 7 | runtime: string; |
7 | 8 | fetch?: typeof globalThis.fetch; |
8 | 9 | http2?: boolean; |
9 | 10 | fastResponse?: boolean; |
| 11 | + ca?: string; |
10 | 12 | }): void { |
11 | 13 | const { url, fetch: _fetch = globalThis.fetch } = opts; |
12 | 14 |
|
@@ -283,6 +285,61 @@ export function addTests(opts: { |
283 | 285 | expect(data.includes("x-foo")); |
284 | 286 | }); |
285 | 287 |
|
| 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 | + |
286 | 343 | // TODO: Write test to make sure it is forbidden for http2/tls |
287 | 344 | test.skipIf(opts.http2)("absolute path in request line", async () => { |
288 | 345 | const _url = new URL(url("/")); |
|
0 commit comments