-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheaders.ts
More file actions
123 lines (116 loc) · 3.57 KB
/
headers.ts
File metadata and controls
123 lines (116 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* @fileoverview Header utilities for HTTP/HTTPS requests.
*
* Two pure-data helpers:
* - `parseRetryAfterHeader` turns a `Retry-After` header value
* (delay-seconds OR HTTP-date per RFC 7231 §7.1.3) into a
* millisecond delay for `onRetry` callbacks.
* - `sanitizeHeaders` redacts `Authorization`, `Cookie`, and the
* other credential-bearing headers before they reach a logger or
* telemetry payload.
*
* No I/O — these can be imported anywhere without dragging the
* Node.js `http`/`https` modules into the bundle.
*/
import { ArrayIsArray } from '../primordials/array'
import { DateCtor, DateNow } from '../primordials/date'
import { SetCtor } from '../primordials/map-set'
import { NumberIsNaN } from '../primordials/number'
import { ObjectKeys } from '../primordials/object'
const RETRY_AFTER_INT_RE = /^\d+$/
/**
* Parse a `Retry-After` HTTP header value into milliseconds.
*
* Supports both formats defined in RFC 7231 §7.1.3:
* - **delay-seconds**: integer number of seconds (e.g., `"120"`)
* - **HTTP-date**: an absolute date/time (e.g., `"Fri, 31 Dec 2027 23:59:59 GMT"`)
*
* When the header is an array (multiple values), the first element is used.
*
* @param value - The raw Retry-After header value(s)
* @returns Delay in milliseconds, or `undefined` if the value cannot be parsed
*
* @example
* ```ts
* import { setTimeout as delay } from 'node:timers/promises'
* const ms = parseRetryAfterHeader(response.headers['retry-after'])
* if (ms !== undefined) {
* await delay(ms)
* }
* ```
*/
export function parseRetryAfterHeader(
value: string | string[] | undefined,
): number | undefined {
if (!value) {
return undefined
}
// Handle array of values (take first).
const raw = ArrayIsArray(value) ? value[0] : value
if (!raw) {
return undefined
}
// Try parsing as seconds (strict integer — reject partial like "10abc").
const trimmed = raw.trim()
if (RETRY_AFTER_INT_RE.test(trimmed)) {
const seconds = Number(trimmed)
return seconds * 1000
}
// Try parsing as HTTP date.
const date = new DateCtor(raw)
if (!NumberIsNaN(date.getTime())) {
const delayMs = date.getTime() - DateNow()
if (delayMs > 0) {
return delayMs
}
}
return undefined
}
/**
* Redact sensitive HTTP headers for safe logging and telemetry.
*
* Replaces values of sensitive headers (Authorization, Cookie, etc.)
* with `[REDACTED]`. Non-sensitive headers are passed through unchanged.
* Array values are joined with `', '`.
*
* @param headers - HTTP headers to sanitize
* @returns A new object with sensitive values redacted
*
* @example
* ```ts
* const safe = sanitizeHeaders({
* 'authorization': 'Bearer secret',
* 'content-type': 'application/json'
* })
* // { authorization: '[REDACTED]', 'content-type': 'application/json' }
* ```
*/
export function sanitizeHeaders(
headers: Record<string, unknown> | undefined,
): Record<string, string> {
if (!headers) {
return {}
}
const sensitiveHeaders = new SetCtor([
'authorization',
'cookie',
'proxy-authorization',
'proxy-authenticate',
'set-cookie',
'www-authenticate',
])
const result: Record<string, string> = {
__proto__: null,
} as unknown as Record<string, string>
for (const key of ObjectKeys(headers)) {
const value = headers[key]
if (sensitiveHeaders.has(key.toLowerCase())) {
result[key] = '[REDACTED]'
} else if (ArrayIsArray(value)) {
result[key] = value.join(', ')
} else if (value !== undefined && value !== null) {
result[key] = String(value)
}
}
return result
}