@irvinebroque/http-rfc-utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/auth.d.ts +139 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +991 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/cache-status.d.ts +15 -0
  8. package/dist/cache-status.d.ts.map +1 -0
  9. package/dist/cache-status.js +152 -0
  10. package/dist/cache-status.js.map +1 -0
  11. package/dist/cache.d.ts +94 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +244 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/client-hints.d.ts +23 -0
  16. package/dist/client-hints.d.ts.map +1 -0
  17. package/dist/client-hints.js +81 -0
  18. package/dist/client-hints.js.map +1 -0
  19. package/dist/conditional.d.ts +97 -0
  20. package/dist/conditional.d.ts.map +1 -0
  21. package/dist/conditional.js +300 -0
  22. package/dist/conditional.js.map +1 -0
  23. package/dist/content-disposition.d.ts +23 -0
  24. package/dist/content-disposition.d.ts.map +1 -0
  25. package/dist/content-disposition.js +122 -0
  26. package/dist/content-disposition.js.map +1 -0
  27. package/dist/cookie.d.ts +43 -0
  28. package/dist/cookie.d.ts.map +1 -0
  29. package/dist/cookie.js +472 -0
  30. package/dist/cookie.js.map +1 -0
  31. package/dist/cors.d.ts +53 -0
  32. package/dist/cors.d.ts.map +1 -0
  33. package/dist/cors.js +170 -0
  34. package/dist/cors.js.map +1 -0
  35. package/dist/datetime.d.ts +53 -0
  36. package/dist/datetime.d.ts.map +1 -0
  37. package/dist/datetime.js +205 -0
  38. package/dist/datetime.js.map +1 -0
  39. package/dist/digest.d.ts +220 -0
  40. package/dist/digest.d.ts.map +1 -0
  41. package/dist/digest.js +355 -0
  42. package/dist/digest.js.map +1 -0
  43. package/dist/encoding.d.ts +14 -0
  44. package/dist/encoding.d.ts.map +1 -0
  45. package/dist/encoding.js +86 -0
  46. package/dist/encoding.js.map +1 -0
  47. package/dist/etag.d.ts +55 -0
  48. package/dist/etag.d.ts.map +1 -0
  49. package/dist/etag.js +182 -0
  50. package/dist/etag.js.map +1 -0
  51. package/dist/ext-value.d.ts +40 -0
  52. package/dist/ext-value.d.ts.map +1 -0
  53. package/dist/ext-value.js +119 -0
  54. package/dist/ext-value.js.map +1 -0
  55. package/dist/forwarded.d.ts +14 -0
  56. package/dist/forwarded.d.ts.map +1 -0
  57. package/dist/forwarded.js +93 -0
  58. package/dist/forwarded.js.map +1 -0
  59. package/dist/header-utils.d.ts +71 -0
  60. package/dist/header-utils.d.ts.map +1 -0
  61. package/dist/header-utils.js +143 -0
  62. package/dist/header-utils.js.map +1 -0
  63. package/dist/headers.d.ts +71 -0
  64. package/dist/headers.d.ts.map +1 -0
  65. package/dist/headers.js +134 -0
  66. package/dist/headers.js.map +1 -0
  67. package/dist/hsts.d.ts +15 -0
  68. package/dist/hsts.d.ts.map +1 -0
  69. package/dist/hsts.js +106 -0
  70. package/dist/hsts.js.map +1 -0
  71. package/dist/http-signatures.d.ts +202 -0
  72. package/dist/http-signatures.d.ts.map +1 -0
  73. package/dist/http-signatures.js +720 -0
  74. package/dist/http-signatures.js.map +1 -0
  75. package/dist/index.d.ts +41 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +125 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/json-pointer.d.ts +97 -0
  80. package/dist/json-pointer.d.ts.map +1 -0
  81. package/dist/json-pointer.js +278 -0
  82. package/dist/json-pointer.js.map +1 -0
  83. package/dist/jsonpath.d.ts +98 -0
  84. package/dist/jsonpath.d.ts.map +1 -0
  85. package/dist/jsonpath.js +1470 -0
  86. package/dist/jsonpath.js.map +1 -0
  87. package/dist/language.d.ts +14 -0
  88. package/dist/language.d.ts.map +1 -0
  89. package/dist/language.js +95 -0
  90. package/dist/language.js.map +1 -0
  91. package/dist/link.d.ts +102 -0
  92. package/dist/link.d.ts.map +1 -0
  93. package/dist/link.js +437 -0
  94. package/dist/link.js.map +1 -0
  95. package/dist/linkset.d.ts +111 -0
  96. package/dist/linkset.d.ts.map +1 -0
  97. package/dist/linkset.js +501 -0
  98. package/dist/linkset.js.map +1 -0
  99. package/dist/negotiate.d.ts +71 -0
  100. package/dist/negotiate.d.ts.map +1 -0
  101. package/dist/negotiate.js +357 -0
  102. package/dist/negotiate.js.map +1 -0
  103. package/dist/pagination.d.ts +80 -0
  104. package/dist/pagination.d.ts.map +1 -0
  105. package/dist/pagination.js +188 -0
  106. package/dist/pagination.js.map +1 -0
  107. package/dist/prefer.d.ts +18 -0
  108. package/dist/prefer.d.ts.map +1 -0
  109. package/dist/prefer.js +93 -0
  110. package/dist/prefer.js.map +1 -0
  111. package/dist/problem.d.ts +54 -0
  112. package/dist/problem.d.ts.map +1 -0
  113. package/dist/problem.js +104 -0
  114. package/dist/problem.js.map +1 -0
  115. package/dist/proxy-status.d.ts +28 -0
  116. package/dist/proxy-status.d.ts.map +1 -0
  117. package/dist/proxy-status.js +220 -0
  118. package/dist/proxy-status.js.map +1 -0
  119. package/dist/range.d.ts +28 -0
  120. package/dist/range.d.ts.map +1 -0
  121. package/dist/range.js +243 -0
  122. package/dist/range.js.map +1 -0
  123. package/dist/response.d.ts +101 -0
  124. package/dist/response.d.ts.map +1 -0
  125. package/dist/response.js +200 -0
  126. package/dist/response.js.map +1 -0
  127. package/dist/sorting.d.ts +66 -0
  128. package/dist/sorting.d.ts.map +1 -0
  129. package/dist/sorting.js +168 -0
  130. package/dist/sorting.js.map +1 -0
  131. package/dist/structured-fields.d.ts +30 -0
  132. package/dist/structured-fields.d.ts.map +1 -0
  133. package/dist/structured-fields.js +468 -0
  134. package/dist/structured-fields.js.map +1 -0
  135. package/dist/types.d.ts +772 -0
  136. package/dist/types.d.ts.map +1 -0
  137. package/dist/types.js +8 -0
  138. package/dist/types.js.map +1 -0
  139. package/dist/uri-template.d.ts +48 -0
  140. package/dist/uri-template.d.ts.map +1 -0
  141. package/dist/uri-template.js +483 -0
  142. package/dist/uri-template.js.map +1 -0
  143. package/dist/uri.d.ts +80 -0
  144. package/dist/uri.d.ts.map +1 -0
  145. package/dist/uri.js +423 -0
  146. package/dist/uri.js.map +1 -0
  147. package/package.json +66 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Shared HTTP header parsing utilities.
3
+ * Common patterns per RFC 9110 §5.6.2 (tokens), §5.6.4 (quoted-strings).
4
+ * @internal - not exported from the public API
5
+ * @see https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6
6
+ */
7
+ /**
8
+ * RFC 9110 §5.6.2: token characters.
9
+ * token = 1*tchar
10
+ * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
11
+ * "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
12
+ */
13
+ export const TOKEN_CHARS = /^[!#$%&'*+\-.^_`|~A-Za-z0-9]+$/;
14
+ /**
15
+ * RFC 9110 §12.4.2: q-value grammar.
16
+ * qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
17
+ */
18
+ export const QVALUE_REGEX = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/;
19
+ /**
20
+ * Check if a header value is empty or whitespace-only.
21
+ *
22
+ * @param header - The header value to check
23
+ * @returns True if the header is null, undefined, empty, or whitespace-only
24
+ */
25
+ export function isEmptyHeader(header) {
26
+ return !header || !header.trim();
27
+ }
28
+ /**
29
+ * Split a header value by delimiter, respecting quoted strings.
30
+ * Handles escape sequences within quoted strings per RFC 9110 §5.6.4.
31
+ *
32
+ * RFC 9110 §5.6.4: quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
33
+ *
34
+ * @param value - The header value to split
35
+ * @param delimiter - The delimiter character (typically ',' or ';')
36
+ * @returns Array of split parts (not trimmed)
37
+ */
38
+ // RFC 9110 §5.6.4: quoted-string and quoted-pair handling.
39
+ export function splitQuotedValue(value, delimiter) {
40
+ const parts = [];
41
+ let current = '';
42
+ let inQuotes = false;
43
+ let escaped = false;
44
+ for (const char of value) {
45
+ if (escaped) {
46
+ current += char;
47
+ escaped = false;
48
+ continue;
49
+ }
50
+ if (char === '\\' && inQuotes) {
51
+ current += char;
52
+ escaped = true;
53
+ continue;
54
+ }
55
+ if (char === '"') {
56
+ inQuotes = !inQuotes;
57
+ current += char;
58
+ continue;
59
+ }
60
+ if (char === delimiter && !inQuotes) {
61
+ parts.push(current);
62
+ current = '';
63
+ continue;
64
+ }
65
+ current += char;
66
+ }
67
+ parts.push(current);
68
+ return parts;
69
+ }
70
+ /**
71
+ * Simple comma-split for headers that don't contain quoted values.
72
+ * Use this for simple list headers like Accept-Language, Accept-Encoding.
73
+ *
74
+ * @param value - The header value to split
75
+ * @returns Array of trimmed, non-empty parts
76
+ */
77
+ export function splitListValue(value) {
78
+ return value.split(',').map(part => part.trim()).filter(Boolean);
79
+ }
80
+ /**
81
+ * Unquote a quoted-string value, handling escape sequences.
82
+ * If the value is not quoted, returns it trimmed.
83
+ *
84
+ * RFC 9110 §5.6.4: quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
85
+ *
86
+ * @param value - The potentially quoted value
87
+ * @returns Unquoted value with escapes resolved
88
+ */
89
+ // RFC 9110 §5.6.4: quoted-string unescaping.
90
+ export function unquote(value) {
91
+ const trimmed = value.trim();
92
+ if (!trimmed.startsWith('"') || !trimmed.endsWith('"') || trimmed.length < 2) {
93
+ return trimmed;
94
+ }
95
+ const inner = trimmed.slice(1, -1);
96
+ let result = '';
97
+ let i = 0;
98
+ while (i < inner.length) {
99
+ if (inner[i] === '\\' && i + 1 < inner.length) {
100
+ result += inner[i + 1];
101
+ i += 2;
102
+ }
103
+ else {
104
+ result += inner[i];
105
+ i++;
106
+ }
107
+ }
108
+ return result;
109
+ }
110
+ /**
111
+ * Quote a value if it contains non-token characters.
112
+ * Escapes backslashes and double quotes within the value.
113
+ *
114
+ * @param value - The value to potentially quote
115
+ * @returns Token value as-is, or quoted-string if needed
116
+ */
117
+ // RFC 9110 §5.6.2, §5.6.4: token vs quoted-string selection.
118
+ export function quoteIfNeeded(value) {
119
+ if (value === '') {
120
+ return '""';
121
+ }
122
+ if (TOKEN_CHARS.test(value)) {
123
+ return value;
124
+ }
125
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
126
+ return `"${escaped}"`;
127
+ }
128
+ /**
129
+ * Parse a q-value (quality value) from a string.
130
+ * Returns null if the value is invalid per RFC 9110 §12.4.2.
131
+ *
132
+ * @param value - The q-value string (e.g., "0.9", "1", "0.123")
133
+ * @returns Parsed number between 0 and 1, or null if invalid
134
+ */
135
+ // RFC 9110 §12.4.2: qvalue parsing.
136
+ export function parseQValue(value) {
137
+ const trimmed = value.trim();
138
+ if (!QVALUE_REGEX.test(trimmed)) {
139
+ return null;
140
+ }
141
+ return Number(trimmed);
142
+ }
143
+ //# sourceMappingURL=header-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-utils.js","sourceRoot":"","sources":["../src/header-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Misc header utilities per RFC 9110, RFC 8594.
3
+ * RFC 9110 §10.2.3, §12.5.5.
4
+ * RFC 8594 §3 (Sunset header).
5
+ * @see https://www.rfc-editor.org/rfc/rfc8594.html#section-3
6
+ */
7
+ import type { RetryAfterValue } from './types.js';
8
+ /**
9
+ * Parse Retry-After header value.
10
+ */
11
+ export declare function parseRetryAfter(value: string): RetryAfterValue | null;
12
+ /**
13
+ * Format Retry-After header value.
14
+ */
15
+ export declare function formatRetryAfter(value: Date | number): string;
16
+ /**
17
+ * Merge Vary header values, preserving order and handling '*'.
18
+ */
19
+ export declare function mergeVary(existing: string | null, add: string | string[]): string;
20
+ /**
21
+ * Parse Sunset header value.
22
+ *
23
+ * The Sunset header indicates when a resource is expected to become
24
+ * unresponsive. Returns null for invalid or missing values.
25
+ *
26
+ * Per RFC 8594 Section 3, timestamps in the past mean the resource
27
+ * is expected to become unavailable at any time.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const sunset = parseSunset('Wed, 11 Nov 2026 11:11:11 GMT');
32
+ * if (sunset && sunset < new Date()) {
33
+ * console.warn('Resource sunset has passed');
34
+ * }
35
+ * ```
36
+ */
37
+ export declare function parseSunset(value: string): Date | null;
38
+ /**
39
+ * Format a Date as a Sunset header value.
40
+ *
41
+ * Uses IMF-fixdate format per RFC 9110 Section 5.6.7.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const header = formatSunset(new Date('2026-12-31T23:59:59Z'));
46
+ * // "Thu, 31 Dec 2026 23:59:59 GMT"
47
+ * ```
48
+ */
49
+ export declare function formatSunset(date: Date): string;
50
+ /**
51
+ * Check if a sunset date is imminent (approaching or past).
52
+ *
53
+ * Per RFC 8594 Section 3, timestamps in the past mean the resource
54
+ * is expected to become unavailable at any time. This helper checks
55
+ * if the sunset is within a given threshold of the current time.
56
+ *
57
+ * @param sunset - Parsed sunset date (null if no Sunset header)
58
+ * @param thresholdMs - Threshold in milliseconds (default: 0, meaning only past dates)
59
+ * @returns true if sunset is null-safe past or within threshold
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const sunset = parseSunset(response.headers.get('Sunset') ?? '');
64
+ * // Check if sunset is within 7 days
65
+ * if (isSunsetImminent(sunset, 7 * 24 * 60 * 60 * 1000)) {
66
+ * console.warn('Resource will sunset within 7 days');
67
+ * }
68
+ * ```
69
+ */
70
+ export declare function isSunsetImminent(sunset: Date | null, thresholdMs?: number): boolean;
71
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD;;GAEG;AAEH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAiBrE;AAED;;GAEG;AAEH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAO7D;AAED;;GAEG;AAEH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CA8BjF;AAED;;;;;;;;;;;;;;;;GAgBG;AAEH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAMtD;AAED;;;;;;;;;;GAUG;AAEH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,WAAW,GAAE,MAAU,GAAG,OAAO,CAMtF"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Misc header utilities per RFC 9110, RFC 8594.
3
+ * RFC 9110 §10.2.3, §12.5.5.
4
+ * RFC 8594 §3 (Sunset header).
5
+ * @see https://www.rfc-editor.org/rfc/rfc8594.html#section-3
6
+ */
7
+ import { parseHTTPDate, formatHTTPDate } from './datetime.js';
8
+ /**
9
+ * Parse Retry-After header value.
10
+ */
11
+ // RFC 9110 §10.2.3: Retry-After parsing.
12
+ export function parseRetryAfter(value) {
13
+ if (!value || !value.trim()) {
14
+ return null;
15
+ }
16
+ const trimmed = value.trim();
17
+ if (/^\d+$/.test(trimmed)) {
18
+ const seconds = parseInt(trimmed, 10);
19
+ return { delaySeconds: Math.max(0, seconds) };
20
+ }
21
+ const date = parseHTTPDate(trimmed);
22
+ if (date) {
23
+ return { date };
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Format Retry-After header value.
29
+ */
30
+ // RFC 9110 §10.2.3: Retry-After formatting.
31
+ export function formatRetryAfter(value) {
32
+ if (typeof value === 'number') {
33
+ const seconds = Math.max(0, Math.floor(value));
34
+ return String(seconds);
35
+ }
36
+ return formatHTTPDate(value);
37
+ }
38
+ /**
39
+ * Merge Vary header values, preserving order and handling '*'.
40
+ */
41
+ // RFC 9110 §12.5.5: Vary field-value combination.
42
+ export function mergeVary(existing, add) {
43
+ const existingValues = existing ? existing.split(',').map(v => v.trim()).filter(Boolean) : [];
44
+ const addValues = Array.isArray(add)
45
+ ? add
46
+ : add.split(',').map(v => v.trim()).filter(Boolean);
47
+ if (existingValues.includes('*') || addValues.includes('*')) {
48
+ return '*';
49
+ }
50
+ const seen = new Set();
51
+ const combined = [];
52
+ for (const value of existingValues) {
53
+ const key = value.toLowerCase();
54
+ if (!seen.has(key)) {
55
+ seen.add(key);
56
+ combined.push(value);
57
+ }
58
+ }
59
+ for (const value of addValues) {
60
+ const key = value.toLowerCase();
61
+ if (!seen.has(key)) {
62
+ seen.add(key);
63
+ combined.push(value);
64
+ }
65
+ }
66
+ return combined.join(', ');
67
+ }
68
+ /**
69
+ * Parse Sunset header value.
70
+ *
71
+ * The Sunset header indicates when a resource is expected to become
72
+ * unresponsive. Returns null for invalid or missing values.
73
+ *
74
+ * Per RFC 8594 Section 3, timestamps in the past mean the resource
75
+ * is expected to become unavailable at any time.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const sunset = parseSunset('Wed, 11 Nov 2026 11:11:11 GMT');
80
+ * if (sunset && sunset < new Date()) {
81
+ * console.warn('Resource sunset has passed');
82
+ * }
83
+ * ```
84
+ */
85
+ // RFC 8594 §3: Sunset = HTTP-date.
86
+ export function parseSunset(value) {
87
+ if (!value || !value.trim()) {
88
+ return null;
89
+ }
90
+ return parseHTTPDate(value.trim());
91
+ }
92
+ /**
93
+ * Format a Date as a Sunset header value.
94
+ *
95
+ * Uses IMF-fixdate format per RFC 9110 Section 5.6.7.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * const header = formatSunset(new Date('2026-12-31T23:59:59Z'));
100
+ * // "Thu, 31 Dec 2026 23:59:59 GMT"
101
+ * ```
102
+ */
103
+ // RFC 8594 §3: Sunset header uses HTTP-date format.
104
+ export function formatSunset(date) {
105
+ return formatHTTPDate(date);
106
+ }
107
+ /**
108
+ * Check if a sunset date is imminent (approaching or past).
109
+ *
110
+ * Per RFC 8594 Section 3, timestamps in the past mean the resource
111
+ * is expected to become unavailable at any time. This helper checks
112
+ * if the sunset is within a given threshold of the current time.
113
+ *
114
+ * @param sunset - Parsed sunset date (null if no Sunset header)
115
+ * @param thresholdMs - Threshold in milliseconds (default: 0, meaning only past dates)
116
+ * @returns true if sunset is null-safe past or within threshold
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const sunset = parseSunset(response.headers.get('Sunset') ?? '');
121
+ * // Check if sunset is within 7 days
122
+ * if (isSunsetImminent(sunset, 7 * 24 * 60 * 60 * 1000)) {
123
+ * console.warn('Resource will sunset within 7 days');
124
+ * }
125
+ * ```
126
+ */
127
+ // RFC 8594 §3: Past timestamps mean "now"; helper for threshold checks.
128
+ export function isSunsetImminent(sunset, thresholdMs = 0) {
129
+ if (!sunset) {
130
+ return false;
131
+ }
132
+ return sunset.getTime() <= Date.now() + thresholdMs;
133
+ }
134
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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"}
package/dist/hsts.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Strict-Transport-Security utilities per RFC 6797.
3
+ * RFC 6797 §6.1, §6.1.1, §6.1.2, §8.1.
4
+ * @see https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1
5
+ */
6
+ import type { StrictTransportSecurityOptions } from './types.js';
7
+ /**
8
+ * Parse Strict-Transport-Security header value.
9
+ */
10
+ export declare function parseStrictTransportSecurity(header: string): StrictTransportSecurityOptions | null;
11
+ /**
12
+ * Format Strict-Transport-Security header value.
13
+ */
14
+ export declare function formatStrictTransportSecurity(options: StrictTransportSecurityOptions): string;
15
+ //# sourceMappingURL=hsts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hsts.d.ts","sourceRoot":"","sources":["../src/hsts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAgCjE;;GAEG;AAEH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,8BAA8B,GAAG,IAAI,CAiElG;AAED;;GAEG;AAEH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAO7F"}
package/dist/hsts.js ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Strict-Transport-Security utilities per RFC 6797.
3
+ * RFC 6797 §6.1, §6.1.1, §6.1.2, §8.1.
4
+ * @see https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1
5
+ */
6
+ import { TOKEN_CHARS, isEmptyHeader, splitQuotedValue, unquote } from './header-utils.js';
7
+ /**
8
+ * Unquote strictly - returns null if not a valid quoted string.
9
+ * Used for HSTS directive values where unquoted values are expected.
10
+ */
11
+ function unquoteStrict(value) {
12
+ const trimmed = value.trim();
13
+ if (!trimmed.startsWith('"') || !trimmed.endsWith('"') || trimmed.length < 2) {
14
+ return null;
15
+ }
16
+ return unquote(trimmed);
17
+ }
18
+ function parseDirectiveValue(value) {
19
+ const trimmed = value.trim();
20
+ if (!trimmed) {
21
+ return null;
22
+ }
23
+ if (trimmed.startsWith('"')) {
24
+ return unquoteStrict(trimmed);
25
+ }
26
+ if (!TOKEN_CHARS.test(trimmed)) {
27
+ return null;
28
+ }
29
+ return trimmed;
30
+ }
31
+ /**
32
+ * Parse Strict-Transport-Security header value.
33
+ */
34
+ // RFC 6797 §6.1, §6.1.1, §6.1.2, §8.1: STS parsing and invalid header handling.
35
+ export function parseStrictTransportSecurity(header) {
36
+ if (isEmptyHeader(header)) {
37
+ return null;
38
+ }
39
+ const directives = splitQuotedValue(header, ';');
40
+ const seen = new Set();
41
+ let maxAge = null;
42
+ let includeSubDomains = false;
43
+ for (const directive of directives) {
44
+ const trimmed = directive.trim();
45
+ if (!trimmed) {
46
+ continue;
47
+ }
48
+ const eqIndex = trimmed.indexOf('=');
49
+ const name = (eqIndex === -1 ? trimmed : trimmed.slice(0, eqIndex)).trim();
50
+ if (!name || !TOKEN_CHARS.test(name)) {
51
+ return null;
52
+ }
53
+ const lowerName = name.toLowerCase();
54
+ if (seen.has(lowerName)) {
55
+ return null;
56
+ }
57
+ seen.add(lowerName);
58
+ if (eqIndex === -1) {
59
+ if (lowerName === 'includesubdomains') {
60
+ includeSubDomains = true;
61
+ }
62
+ else if (lowerName === 'max-age') {
63
+ return null;
64
+ }
65
+ continue;
66
+ }
67
+ const rawValue = trimmed.slice(eqIndex + 1).trim();
68
+ const value = parseDirectiveValue(rawValue);
69
+ if (value === null) {
70
+ return null;
71
+ }
72
+ if (lowerName === 'max-age') {
73
+ if (!/^\d+$/.test(value)) {
74
+ return null;
75
+ }
76
+ const parsed = Number(value);
77
+ if (!Number.isFinite(parsed)) {
78
+ return null;
79
+ }
80
+ maxAge = parsed;
81
+ }
82
+ else if (lowerName === 'includesubdomains') {
83
+ return null;
84
+ }
85
+ }
86
+ if (maxAge === null) {
87
+ return null;
88
+ }
89
+ return {
90
+ maxAge,
91
+ includeSubDomains: includeSubDomains ? true : undefined,
92
+ };
93
+ }
94
+ /**
95
+ * Format Strict-Transport-Security header value.
96
+ */
97
+ // RFC 6797 §6.1: STS field-value formatting.
98
+ export function formatStrictTransportSecurity(options) {
99
+ const maxAge = Math.max(0, Math.floor(options.maxAge));
100
+ const parts = [`max-age=${maxAge}`];
101
+ if (options.includeSubDomains) {
102
+ parts.push('includeSubDomains');
103
+ }
104
+ return parts.join('; ');
105
+ }
106
+ //# sourceMappingURL=hsts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hsts.js","sourceRoot":"","sources":["../src/hsts.ts"],"names":[],"mappings":"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"}