mirror of
https://github.com/germondai/trawl.git
synced 2026-08-17 12:11:23 +02:00
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { proxySanitizeHeaders, RESPONSE_HOP_BY_HOP_HEADERS } from "../src/utils/sanitize"
|
|
|
|
describe("proxySanitizeHeaders", () => {
|
|
test("returns undefined for empty/undefined input", () => {
|
|
expect(proxySanitizeHeaders(undefined)).toBeUndefined()
|
|
expect(proxySanitizeHeaders({})).toBeUndefined()
|
|
})
|
|
|
|
test("passes through Authorization (unlike sanitizeHeaders)", () => {
|
|
const out = proxySanitizeHeaders({ Authorization: "Bearer xyz" })
|
|
expect(out).toEqual({ authorization: "Bearer xyz" })
|
|
})
|
|
|
|
test("passes through Cookie", () => {
|
|
const out = proxySanitizeHeaders({ Cookie: "session=abc" })
|
|
expect(out).toEqual({ cookie: "session=abc" })
|
|
})
|
|
|
|
test("passes through Range (critical for JDownloader-style chunked downloads)", () => {
|
|
const out = proxySanitizeHeaders({ Range: "bytes=0-1023" })
|
|
expect(out).toEqual({ range: "bytes=0-1023" })
|
|
})
|
|
|
|
test("passes through User-Agent", () => {
|
|
const out = proxySanitizeHeaders({ "User-Agent": "JDownloader/2" })
|
|
expect(out).toEqual({ "user-agent": "JDownloader/2" })
|
|
})
|
|
|
|
test("passes through Referer, Origin, custom API tokens", () => {
|
|
const out = proxySanitizeHeaders({
|
|
Referer: "https://example.com/page",
|
|
Origin: "https://example.com",
|
|
"X-Api-Key": "secret-token",
|
|
})
|
|
expect(out).toEqual({
|
|
referer: "https://example.com/page",
|
|
origin: "https://example.com",
|
|
"x-api-key": "secret-token",
|
|
})
|
|
})
|
|
|
|
test("strips hop-by-hop headers (Connection, Transfer-Encoding, Upgrade)", () => {
|
|
const out = proxySanitizeHeaders({
|
|
Connection: "keep-alive",
|
|
"Transfer-Encoding": "chunked",
|
|
Upgrade: "websocket",
|
|
"Keep-Alive": "timeout=5",
|
|
TE: "trailers",
|
|
"Proxy-Authorization": "Basic xxx",
|
|
Trailer: "Expires",
|
|
"Proxy-Connection": "close",
|
|
Accept: "*/*",
|
|
})
|
|
expect(out).toEqual({ accept: "*/*" })
|
|
})
|
|
|
|
test("strips hop-by-hop headers case-insensitively", () => {
|
|
const out = proxySanitizeHeaders({
|
|
CONNECTION: "close",
|
|
"transfer-encoding": "chunked",
|
|
Accept: "*/*",
|
|
})
|
|
expect(out).toEqual({ accept: "*/*" })
|
|
})
|
|
|
|
test("lowercases keys for cheap lookup later", () => {
|
|
const out = proxySanitizeHeaders({ "Content-Type": "application/json", "X-Custom": "v" })
|
|
expect(out).toEqual({ "content-type": "application/json", "x-custom": "v" })
|
|
})
|
|
|
|
test("strips control characters from values (prevents request smuggling)", () => {
|
|
const out = proxySanitizeHeaders({ "X-Custom": "value\r\nInjected-Header: evil" })
|
|
expect(out).toEqual({ "x-custom": "valueInjected-Header: evil" })
|
|
})
|
|
|
|
test("drops empty values", () => {
|
|
const out = proxySanitizeHeaders({ Accept: " ", "X-Real": "v" })
|
|
expect(out).toEqual({ "x-real": "v" })
|
|
})
|
|
|
|
test("drops empty header names", () => {
|
|
const out = proxySanitizeHeaders({ "": "orphan", Accept: "*/*" })
|
|
expect(out).toEqual({ accept: "*/*" })
|
|
})
|
|
|
|
test("RESPONSE_HOP_BY_HOP_HEADERS exposes the same set for response filtering", () => {
|
|
expect(RESPONSE_HOP_BY_HOP_HEADERS.has("connection")).toBe(true)
|
|
expect(RESPONSE_HOP_BY_HOP_HEADERS.has("transfer-encoding")).toBe(true)
|
|
expect(RESPONSE_HOP_BY_HOP_HEADERS.has("upgrade")).toBe(true)
|
|
expect(RESPONSE_HOP_BY_HOP_HEADERS.has("authorization")).toBe(false)
|
|
expect(RESPONSE_HOP_BY_HOP_HEADERS.has("set-cookie")).toBe(false)
|
|
})
|
|
|
|
test("does not mutate the input record", () => {
|
|
const input = { Authorization: "Bearer xyz", Accept: "*/*" }
|
|
const snapshot = { ...input }
|
|
proxySanitizeHeaders(input)
|
|
expect(input).toEqual(snapshot)
|
|
})
|
|
})
|