@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
package/dist/etag.js ADDED
@@ -0,0 +1,182 @@
1
+ /**
2
+ * ETag utilities per RFC 9110.
3
+ * RFC 9110 §8.8.3, §8.8.3.2.
4
+ * @see https://httpwg.org/specs/rfc9110.html#field.etag
5
+ */
6
+ const MAX_ETAG_CHAR = 0xFF;
7
+ // RFC 9110 §8.8.3: etagc character set validation.
8
+ function isValidETagValue(value) {
9
+ for (const char of value) {
10
+ const code = char.codePointAt(0);
11
+ if (code === undefined) {
12
+ return false;
13
+ }
14
+ if (code > MAX_ETAG_CHAR) {
15
+ return false;
16
+ }
17
+ if (code === 0x21) {
18
+ continue;
19
+ }
20
+ if (code >= 0x23 && code <= 0x7E) {
21
+ continue;
22
+ }
23
+ if (code >= 0x80 && code <= 0xFF) {
24
+ continue;
25
+ }
26
+ return false;
27
+ }
28
+ return true;
29
+ }
30
+ /**
31
+ * Simple djb2 hash algorithm for sync ETag generation
32
+ */
33
+ function djb2Hash(str) {
34
+ let hash = 5381;
35
+ for (let i = 0; i < str.length; i++) {
36
+ hash = ((hash << 5) + hash) ^ str.charCodeAt(i);
37
+ }
38
+ // Convert to unsigned 32-bit integer and then to hex
39
+ return (hash >>> 0).toString(16);
40
+ }
41
+ /**
42
+ * Convert data to a string representation for hashing
43
+ */
44
+ function dataToString(data) {
45
+ if (typeof data === 'string') {
46
+ return data;
47
+ }
48
+ if (data instanceof ArrayBuffer) {
49
+ return Array.from(new Uint8Array(data))
50
+ .map(b => String.fromCharCode(b))
51
+ .join('');
52
+ }
53
+ if (ArrayBuffer.isView(data)) {
54
+ return Array.from(new Uint8Array(data.buffer, data.byteOffset, data.byteLength))
55
+ .map(b => String.fromCharCode(b))
56
+ .join('');
57
+ }
58
+ return JSON.stringify(data) ?? String(data);
59
+ }
60
+ function toBufferSource(data) {
61
+ if (data instanceof ArrayBuffer) {
62
+ return data;
63
+ }
64
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
65
+ }
66
+ /**
67
+ * Generate a simple ETag from data (sync, using string hash)
68
+ * Uses a simple hash algorithm suitable for most use cases.
69
+ * Returns format: "hash" (strong) or W/"hash" (if weak option specified)
70
+ */
71
+ // RFC 9110 §8.8.3: Entity-tag field-value formatting.
72
+ export function generateETag(data, options) {
73
+ const str = dataToString(data);
74
+ const hash = djb2Hash(str);
75
+ const weak = options?.weak ?? false;
76
+ return weak ? `W/"${hash}"` : `"${hash}"`;
77
+ }
78
+ /**
79
+ * Generate an ETag using Web Crypto API (async, more secure)
80
+ * Uses SHA-256 by default.
81
+ */
82
+ // RFC 9110 §8.8.3: Entity-tag field-value formatting.
83
+ export async function generateETagAsync(data, options) {
84
+ const algorithm = options?.algorithm ?? 'SHA-256';
85
+ const weak = options?.weak ?? false;
86
+ let dataBuffer;
87
+ if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
88
+ dataBuffer = toBufferSource(data);
89
+ }
90
+ else {
91
+ const str = dataToString(data);
92
+ const encoder = new TextEncoder();
93
+ dataBuffer = encoder.encode(str);
94
+ }
95
+ const hashBuffer = await globalThis.crypto.subtle.digest(algorithm, dataBuffer);
96
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
97
+ // Use first 16 bytes (32 hex chars) for reasonable length
98
+ const hash = hashArray.slice(0, 16).map(b => b.toString(16).padStart(2, '0')).join('');
99
+ return weak ? `W/"${hash}"` : `"${hash}"`;
100
+ }
101
+ /**
102
+ * Parse an ETag string into its components
103
+ * Handles: "abc", W/"abc", ""
104
+ * Returns null for invalid format
105
+ */
106
+ // RFC 9110 §8.8.3: Entity-tag field parsing.
107
+ export function parseETag(etag) {
108
+ if (!etag) {
109
+ return null;
110
+ }
111
+ const trimmed = etag.trim();
112
+ // Check for weak prefix
113
+ let weak = false;
114
+ let rest = trimmed;
115
+ if (trimmed.startsWith('W/')) {
116
+ weak = true;
117
+ rest = trimmed.slice(2);
118
+ }
119
+ // Must be quoted
120
+ if (!rest.startsWith('"') || !rest.endsWith('"')) {
121
+ return null;
122
+ }
123
+ // Extract value (everything between the quotes)
124
+ const value = rest.slice(1, -1);
125
+ // Value cannot contain unescaped quotes
126
+ // Per RFC 9110, etagc = %x21 / %x23-7E / obs-text (no DQUOTE = %x22)
127
+ if (value.includes('"')) {
128
+ return null;
129
+ }
130
+ if (!isValidETagValue(value)) {
131
+ return null;
132
+ }
133
+ return { weak, value };
134
+ }
135
+ /**
136
+ * Format an ETag object back to string
137
+ */
138
+ // RFC 9110 §8.8.3: Entity-tag field formatting.
139
+ export function formatETag(etag) {
140
+ return etag.weak ? `W/"${etag.value}"` : `"${etag.value}"`;
141
+ }
142
+ /**
143
+ * Compare two ETags per RFC 9110 §8.8.3.
144
+ *
145
+ * Strong comparison: both must be strong AND values match
146
+ * Weak comparison: values match (regardless of weak/strong)
147
+ *
148
+ * RFC 9110 §8.8.3.2 comparison table:
149
+ * | ETag 1 | ETag 2 | Strong | Weak |
150
+ * |---------|---------|--------|-------|
151
+ * | W/"1" | W/"1" | false | true |
152
+ * | W/"1" | "1" | false | true |
153
+ * | "1" | "1" | true | true |
154
+ * | "1" | W/"1" | false | true |
155
+ * | W/"1" | W/"2" | false | false |
156
+ * | "1" | "2" | false | false |
157
+ */
158
+ export function compareETags(a, b, strong = false) {
159
+ // Values must always match
160
+ if (a.value !== b.value) {
161
+ return false;
162
+ }
163
+ // For weak comparison, value match is sufficient
164
+ if (!strong) {
165
+ return true;
166
+ }
167
+ // For strong comparison, both must be strong (not weak)
168
+ return !a.weak && !b.weak;
169
+ }
170
+ /**
171
+ * Convenience function to compare ETag strings directly
172
+ */
173
+ // RFC 9110 §8.8.3.2: Strong/weak comparison via parsed entity-tags.
174
+ export function compareETagStrings(a, b, strong = false) {
175
+ const parsedA = parseETag(a);
176
+ const parsedB = parseETag(b);
177
+ if (!parsedA || !parsedB) {
178
+ return false;
179
+ }
180
+ return compareETags(parsedA, parsedB, strong);
181
+ }
182
+ //# sourceMappingURL=etag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"etag.js","sourceRoot":"","sources":["../src/etag.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * RFC 8187 extended parameter value encoding/decoding.
3
+ * RFC 8187 §3.2, §3.2.1.
4
+ * @see https://www.rfc-editor.org/rfc/rfc8187.html#section-3.2
5
+ */
6
+ import type { ExtValue, ExtValueOptions } from './types.js';
7
+ /**
8
+ * Check if a character is a valid attr-char per RFC 8187 §3.2.1.
9
+ *
10
+ * @param char - Single character to check
11
+ * @returns True if char is attr-char
12
+ */
13
+ export declare function isAttrChar(char: string): boolean;
14
+ /**
15
+ * Check if a string needs extended encoding (contains non-ASCII or non-attr-char).
16
+ *
17
+ * @param value - String to check
18
+ * @returns True if extended encoding is needed
19
+ */
20
+ export declare function needsExtendedEncoding(value: string): boolean;
21
+ /**
22
+ * Decode an RFC 8187 ext-value (charset'language'value-chars).
23
+ *
24
+ * Returns null for malformed input to allow fallback to non-extended value.
25
+ *
26
+ * @param encoded - The ext-value string (e.g., "UTF-8'en'%C2%A3%20rates")
27
+ * @returns Decoded ExtValue or null if malformed
28
+ */
29
+ export declare function decodeExtValue(encoded: string): ExtValue | null;
30
+ /**
31
+ * Encode a string as RFC 8187 ext-value (UTF-8'language'value-chars).
32
+ *
33
+ * Per RFC 8187 §3.2.1, producers MUST use UTF-8 encoding.
34
+ *
35
+ * @param value - String to encode
36
+ * @param options - Encoding options (language tag)
37
+ * @returns Encoded ext-value string
38
+ */
39
+ export declare function encodeExtValue(value: string, options?: ExtValueOptions): string;
40
+ //# sourceMappingURL=ext-value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-value.d.ts","sourceRoot":"","sources":["../src/ext-value.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAU5D;;;;;GAKG;AAEH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;;GAKG;AAEH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAa5D;AAED;;;;;;;GAOG;AAEH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CA0C/D;AAED;;;;;;;;GAQG;AAEH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAmBnF"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * RFC 8187 extended parameter value encoding/decoding.
3
+ * RFC 8187 §3.2, §3.2.1.
4
+ * @see https://www.rfc-editor.org/rfc/rfc8187.html#section-3.2
5
+ */
6
+ /**
7
+ * RFC 8187 §3.2.1: attr-char characters that don't need percent-encoding.
8
+ * attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "."
9
+ * / "^" / "_" / "`" / "|" / "~"
10
+ * (token except "*" / "'" / "%")
11
+ */
12
+ const ATTR_CHAR = /^[A-Za-z0-9!#$&+\-.^_`|~]$/;
13
+ /**
14
+ * Check if a character is a valid attr-char per RFC 8187 §3.2.1.
15
+ *
16
+ * @param char - Single character to check
17
+ * @returns True if char is attr-char
18
+ */
19
+ // RFC 8187 §3.2.1: attr-char validation.
20
+ export function isAttrChar(char) {
21
+ return char.length === 1 && ATTR_CHAR.test(char);
22
+ }
23
+ /**
24
+ * Check if a string needs extended encoding (contains non-ASCII or non-attr-char).
25
+ *
26
+ * @param value - String to check
27
+ * @returns True if extended encoding is needed
28
+ */
29
+ // RFC 8187 §3.2: Determine if ext-value encoding is required.
30
+ export function needsExtendedEncoding(value) {
31
+ for (const char of value) {
32
+ const code = char.charCodeAt(0);
33
+ // Non-ASCII needs encoding
34
+ if (code > 0x7f) {
35
+ return true;
36
+ }
37
+ // Non-attr-char ASCII needs encoding
38
+ if (!ATTR_CHAR.test(char)) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ /**
45
+ * Decode an RFC 8187 ext-value (charset'language'value-chars).
46
+ *
47
+ * Returns null for malformed input to allow fallback to non-extended value.
48
+ *
49
+ * @param encoded - The ext-value string (e.g., "UTF-8'en'%C2%A3%20rates")
50
+ * @returns Decoded ExtValue or null if malformed
51
+ */
52
+ // RFC 8187 §3.2.1: Extended parameter value decoding.
53
+ export function decodeExtValue(encoded) {
54
+ // RFC 8187 §3.2.1: ext-value cannot use quoted-string notation.
55
+ // Double-quotes are not valid in charset, language, or value-chars.
56
+ if (encoded.includes('"')) {
57
+ return null;
58
+ }
59
+ // Split on single quotes - format is charset'language'value-chars
60
+ const firstQuote = encoded.indexOf("'");
61
+ if (firstQuote === -1) {
62
+ return null;
63
+ }
64
+ const secondQuote = encoded.indexOf("'", firstQuote + 1);
65
+ if (secondQuote === -1) {
66
+ return null;
67
+ }
68
+ const charset = encoded.slice(0, firstQuote);
69
+ const language = encoded.slice(firstQuote + 1, secondQuote);
70
+ const valueChars = encoded.slice(secondQuote + 1);
71
+ // RFC 8187 §3.2: charset MUST NOT be omitted
72
+ if (!charset) {
73
+ return null;
74
+ }
75
+ // Decode percent-encoded value
76
+ let decoded;
77
+ try {
78
+ decoded = decodeURIComponent(valueChars);
79
+ }
80
+ catch {
81
+ // RFC 8187 §3.2.1: Handle encoding errors robustly
82
+ return null;
83
+ }
84
+ // RFC 8187 §3.2.1: charset is case-insensitive, normalize to lowercase
85
+ return {
86
+ charset: charset.toLowerCase(),
87
+ language: language || undefined,
88
+ value: decoded,
89
+ };
90
+ }
91
+ /**
92
+ * Encode a string as RFC 8187 ext-value (UTF-8'language'value-chars).
93
+ *
94
+ * Per RFC 8187 §3.2.1, producers MUST use UTF-8 encoding.
95
+ *
96
+ * @param value - String to encode
97
+ * @param options - Encoding options (language tag)
98
+ * @returns Encoded ext-value string
99
+ */
100
+ // RFC 8187 §3.2: Extended parameter encoding.
101
+ export function encodeExtValue(value, options = {}) {
102
+ const language = options.language ?? '';
103
+ // Build percent-encoded value, preserving attr-char
104
+ let encoded = '';
105
+ const encoder = new TextEncoder();
106
+ const bytes = encoder.encode(value);
107
+ for (const byte of bytes) {
108
+ const char = String.fromCharCode(byte);
109
+ if (ATTR_CHAR.test(char)) {
110
+ encoded += char;
111
+ }
112
+ else {
113
+ // Percent-encode with uppercase hex per RFC 3986 §2.1
114
+ encoded += '%' + byte.toString(16).toUpperCase().padStart(2, '0');
115
+ }
116
+ }
117
+ return `UTF-8'${language}'${encoded}`;
118
+ }
119
+ //# sourceMappingURL=ext-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-value.js","sourceRoot":"","sources":["../src/ext-value.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Forwarded header utilities per RFC 7239.
3
+ * RFC 7239 §4.
4
+ */
5
+ import type { ForwardedElement } from './types.js';
6
+ /**
7
+ * Parse Forwarded header into elements.
8
+ */
9
+ export declare function parseForwarded(header: string): ForwardedElement[];
10
+ /**
11
+ * Format Forwarded header value from elements.
12
+ */
13
+ export declare function formatForwarded(elements: ForwardedElement[]): string;
14
+ //# sourceMappingURL=forwarded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarded.d.ts","sourceRoot":"","sources":["../src/forwarded.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnD;;GAEG;AAEH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA0DjE;AAED;;GAEG;AAEH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAyBpE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Forwarded header utilities per RFC 7239.
3
+ * RFC 7239 §4.
4
+ */
5
+ import { isEmptyHeader, splitQuotedValue, unquote, quoteIfNeeded } from './header-utils.js';
6
+ /**
7
+ * Parse Forwarded header into elements.
8
+ */
9
+ // RFC 7239 §4: Forwarded field-value parsing.
10
+ export function parseForwarded(header) {
11
+ if (isEmptyHeader(header)) {
12
+ return [];
13
+ }
14
+ const elements = [];
15
+ const parts = splitQuotedValue(header, ',');
16
+ for (const part of parts) {
17
+ const trimmed = part.trim();
18
+ if (!trimmed)
19
+ continue;
20
+ const pairs = splitQuotedValue(trimmed, ';');
21
+ const element = {};
22
+ const extensions = {};
23
+ for (const pair of pairs) {
24
+ const segment = pair.trim();
25
+ if (!segment)
26
+ continue;
27
+ const eqIndex = segment.indexOf('=');
28
+ if (eqIndex === -1)
29
+ continue;
30
+ const key = segment.slice(0, eqIndex).trim().toLowerCase();
31
+ const rawValue = segment.slice(eqIndex + 1).trim();
32
+ const value = unquote(rawValue);
33
+ if (key === 'for') {
34
+ if (element.for === undefined) {
35
+ element.for = value;
36
+ }
37
+ }
38
+ else if (key === 'by') {
39
+ if (element.by === undefined) {
40
+ element.by = value;
41
+ }
42
+ }
43
+ else if (key === 'host') {
44
+ if (element.host === undefined) {
45
+ element.host = value;
46
+ }
47
+ }
48
+ else if (key === 'proto') {
49
+ if (element.proto === undefined) {
50
+ element.proto = value;
51
+ }
52
+ }
53
+ else if (key) {
54
+ if (!(key in extensions)) {
55
+ extensions[key] = value;
56
+ }
57
+ }
58
+ }
59
+ if (Object.keys(extensions).length > 0) {
60
+ element.extensions = extensions;
61
+ }
62
+ elements.push(element);
63
+ }
64
+ return elements;
65
+ }
66
+ /**
67
+ * Format Forwarded header value from elements.
68
+ */
69
+ // RFC 7239 §4: Forwarded field-value formatting.
70
+ export function formatForwarded(elements) {
71
+ return elements.map(element => {
72
+ const parts = [];
73
+ if (element.for !== undefined) {
74
+ parts.push(`for=${quoteIfNeeded(element.for)}`);
75
+ }
76
+ if (element.by !== undefined) {
77
+ parts.push(`by=${quoteIfNeeded(element.by)}`);
78
+ }
79
+ if (element.host !== undefined) {
80
+ parts.push(`host=${quoteIfNeeded(element.host)}`);
81
+ }
82
+ if (element.proto !== undefined) {
83
+ parts.push(`proto=${quoteIfNeeded(element.proto)}`);
84
+ }
85
+ if (element.extensions) {
86
+ for (const [key, value] of Object.entries(element.extensions)) {
87
+ parts.push(`${key}=${quoteIfNeeded(value)}`);
88
+ }
89
+ }
90
+ return parts.join(';');
91
+ }).join(', ');
92
+ }
93
+ //# sourceMappingURL=forwarded.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarded.js","sourceRoot":"","sources":["../src/forwarded.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
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 declare const TOKEN_CHARS: RegExp;
14
+ /**
15
+ * RFC 9110 §12.4.2: q-value grammar.
16
+ * qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
17
+ */
18
+ export declare const QVALUE_REGEX: RegExp;
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 declare function isEmptyHeader(header: string | null | undefined): boolean;
26
+ /**
27
+ * Split a header value by delimiter, respecting quoted strings.
28
+ * Handles escape sequences within quoted strings per RFC 9110 §5.6.4.
29
+ *
30
+ * RFC 9110 §5.6.4: quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
31
+ *
32
+ * @param value - The header value to split
33
+ * @param delimiter - The delimiter character (typically ',' or ';')
34
+ * @returns Array of split parts (not trimmed)
35
+ */
36
+ export declare function splitQuotedValue(value: string, delimiter: string): string[];
37
+ /**
38
+ * Simple comma-split for headers that don't contain quoted values.
39
+ * Use this for simple list headers like Accept-Language, Accept-Encoding.
40
+ *
41
+ * @param value - The header value to split
42
+ * @returns Array of trimmed, non-empty parts
43
+ */
44
+ export declare function splitListValue(value: string): string[];
45
+ /**
46
+ * Unquote a quoted-string value, handling escape sequences.
47
+ * If the value is not quoted, returns it trimmed.
48
+ *
49
+ * RFC 9110 §5.6.4: quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
50
+ *
51
+ * @param value - The potentially quoted value
52
+ * @returns Unquoted value with escapes resolved
53
+ */
54
+ export declare function unquote(value: string): string;
55
+ /**
56
+ * Quote a value if it contains non-token characters.
57
+ * Escapes backslashes and double quotes within the value.
58
+ *
59
+ * @param value - The value to potentially quote
60
+ * @returns Token value as-is, or quoted-string if needed
61
+ */
62
+ export declare function quoteIfNeeded(value: string): string;
63
+ /**
64
+ * Parse a q-value (quality value) from a string.
65
+ * Returns null if the value is invalid per RFC 9110 §12.4.2.
66
+ *
67
+ * @param value - The q-value string (e.g., "0.9", "1", "0.123")
68
+ * @returns Parsed number between 0 and 1, or null if invalid
69
+ */
70
+ export declare function parseQValue(value: string): number | null;
71
+ //# sourceMappingURL=header-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-utils.d.ts","sourceRoot":"","sources":["../src/header-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,QAAmC,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAyC,CAAC;AAEnE;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAExE;AAED;;;;;;;;;GASG;AAEH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAoC3E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEtD;AAED;;;;;;;;GAQG;AAEH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqB7C;AAED;;;;;;GAMG;AAEH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASnD;AAED;;;;;;GAMG;AAEH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMxD"}