@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/digest.js ADDED
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Digest Fields per RFC 9530.
3
+ * RFC 9530 §2, §3, §4, §5.
4
+ * @see https://www.rfc-editor.org/rfc/rfc9530.html
5
+ *
6
+ * Provides Content-Digest and Repr-Digest HTTP header field parsing/formatting
7
+ * for content and representation integrity verification. Also provides
8
+ * Want-Content-Digest and Want-Repr-Digest preference fields.
9
+ */
10
+ import { parseSfDict, serializeSfDict } from './structured-fields.js';
11
+ // =============================================================================
12
+ // Constants
13
+ // =============================================================================
14
+ /**
15
+ * Algorithm classification constants.
16
+ * RFC 9530 §5, §7.2.
17
+ */
18
+ export const DIGEST_ALGORITHMS = {
19
+ /** Algorithms suitable for adversarial settings */
20
+ active: ['sha-256', 'sha-512'],
21
+ /** Algorithms that MUST NOT be used in adversarial settings */
22
+ deprecated: ['md5', 'sha', 'unixsum', 'unixcksum', 'adler', 'crc32c'],
23
+ };
24
+ // =============================================================================
25
+ // Algorithm Helpers
26
+ // =============================================================================
27
+ /**
28
+ * Check if an algorithm is active (suitable for adversarial settings).
29
+ * RFC 9530 §5.
30
+ */
31
+ export function isActiveAlgorithm(algorithm) {
32
+ return DIGEST_ALGORITHMS.active.includes(algorithm.toLowerCase());
33
+ }
34
+ /**
35
+ * Check if an algorithm is deprecated.
36
+ * RFC 9530 §5.
37
+ */
38
+ export function isDeprecatedAlgorithm(algorithm) {
39
+ return DIGEST_ALGORITHMS.deprecated.includes(algorithm.toLowerCase());
40
+ }
41
+ // =============================================================================
42
+ // Parsing
43
+ // =============================================================================
44
+ /**
45
+ * Parse digest values from a structured field dictionary.
46
+ * Returns null for malformed input.
47
+ */
48
+ function parseDigestField(value) {
49
+ const dict = parseSfDict(value);
50
+ if (!dict) {
51
+ return null;
52
+ }
53
+ const digests = [];
54
+ for (const [algorithm, item] of Object.entries(dict)) {
55
+ // RFC 9530 §2, §3: value is a Byte Sequence
56
+ if ('items' in item) {
57
+ // Inner list not valid for digest values
58
+ continue;
59
+ }
60
+ const sfItem = item;
61
+ if (!(sfItem.value instanceof Uint8Array)) {
62
+ // Must be a byte sequence
63
+ continue;
64
+ }
65
+ digests.push({
66
+ algorithm: algorithm.toLowerCase(),
67
+ value: sfItem.value,
68
+ });
69
+ }
70
+ return digests.length > 0 ? digests : [];
71
+ }
72
+ /**
73
+ * Parse Content-Digest header field value.
74
+ * RFC 9530 §2.
75
+ *
76
+ * @param value - The Content-Digest header value
77
+ * @returns Array of digests, or null if malformed
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const digests = parseContentDigest('sha-256=:abc123...:');
82
+ * ```
83
+ */
84
+ export function parseContentDigest(value) {
85
+ return parseDigestField(value);
86
+ }
87
+ /**
88
+ * Parse Repr-Digest header field value.
89
+ * RFC 9530 §3.
90
+ *
91
+ * @param value - The Repr-Digest header value
92
+ * @returns Array of digests, or null if malformed
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const digests = parseReprDigest('sha-512=:xyz789...:');
97
+ * ```
98
+ */
99
+ export function parseReprDigest(value) {
100
+ return parseDigestField(value);
101
+ }
102
+ /**
103
+ * Parse digest preference values from a structured field dictionary.
104
+ * Returns null for malformed input.
105
+ */
106
+ function parseWantDigestField(value) {
107
+ const dict = parseSfDict(value);
108
+ if (!dict) {
109
+ return null;
110
+ }
111
+ const preferences = [];
112
+ for (const [algorithm, item] of Object.entries(dict)) {
113
+ // RFC 9530 §4: value is an Integer 0-10
114
+ if ('items' in item) {
115
+ // Inner list not valid for preferences
116
+ continue;
117
+ }
118
+ const sfItem = item;
119
+ if (typeof sfItem.value !== 'number') {
120
+ // Must be an integer
121
+ continue;
122
+ }
123
+ const weight = sfItem.value;
124
+ // RFC 9530 §4: must be in range 0 to 10 inclusive
125
+ if (!Number.isInteger(weight) || weight < 0 || weight > 10) {
126
+ continue;
127
+ }
128
+ preferences.push({
129
+ algorithm: algorithm.toLowerCase(),
130
+ weight,
131
+ });
132
+ }
133
+ return preferences.length > 0 ? preferences : [];
134
+ }
135
+ /**
136
+ * Parse Want-Content-Digest header field value.
137
+ * RFC 9530 §4.
138
+ *
139
+ * @param value - The Want-Content-Digest header value
140
+ * @returns Array of preferences, or null if malformed
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const prefs = parseWantContentDigest('sha-256=10, sha-512=3');
145
+ * ```
146
+ */
147
+ export function parseWantContentDigest(value) {
148
+ return parseWantDigestField(value);
149
+ }
150
+ /**
151
+ * Parse Want-Repr-Digest header field value.
152
+ * RFC 9530 §4.
153
+ *
154
+ * @param value - The Want-Repr-Digest header value
155
+ * @returns Array of preferences, or null if malformed
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * const prefs = parseWantReprDigest('sha-512=3, sha-256=10, unixsum=0');
160
+ * ```
161
+ */
162
+ export function parseWantReprDigest(value) {
163
+ return parseWantDigestField(value);
164
+ }
165
+ // =============================================================================
166
+ // Formatting
167
+ // =============================================================================
168
+ /**
169
+ * Format digests as a structured field dictionary string.
170
+ */
171
+ function formatDigestField(digests) {
172
+ const dict = {};
173
+ for (const digest of digests) {
174
+ // RFC 9530 §5: MUST NOT generate deprecated algorithms
175
+ // But we allow formatting any algorithm the caller provides
176
+ dict[digest.algorithm] = { value: digest.value };
177
+ }
178
+ return serializeSfDict(dict);
179
+ }
180
+ /**
181
+ * Format Content-Digest header field value.
182
+ * RFC 9530 §2.
183
+ *
184
+ * @param digests - Array of digests to format
185
+ * @returns Formatted header value
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * const header = formatContentDigest([
190
+ * { algorithm: 'sha-256', value: digestBytes }
191
+ * ]);
192
+ * ```
193
+ */
194
+ export function formatContentDigest(digests) {
195
+ return formatDigestField(digests);
196
+ }
197
+ /**
198
+ * Format Repr-Digest header field value.
199
+ * RFC 9530 §3.
200
+ *
201
+ * @param digests - Array of digests to format
202
+ * @returns Formatted header value
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * const header = formatReprDigest([
207
+ * { algorithm: 'sha-512', value: digestBytes }
208
+ * ]);
209
+ * ```
210
+ */
211
+ export function formatReprDigest(digests) {
212
+ return formatDigestField(digests);
213
+ }
214
+ /**
215
+ * Format preferences as a structured field dictionary string.
216
+ */
217
+ function formatWantDigestField(preferences) {
218
+ const dict = {};
219
+ for (const pref of preferences) {
220
+ // RFC 9530 §4: weight is an Integer 0-10
221
+ if (pref.weight < 0 || pref.weight > 10 || !Number.isInteger(pref.weight)) {
222
+ continue;
223
+ }
224
+ dict[pref.algorithm] = { value: pref.weight };
225
+ }
226
+ return serializeSfDict(dict);
227
+ }
228
+ /**
229
+ * Format Want-Content-Digest header field value.
230
+ * RFC 9530 §4.
231
+ *
232
+ * @param preferences - Array of preferences to format
233
+ * @returns Formatted header value
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * const header = formatWantContentDigest([
238
+ * { algorithm: 'sha-256', weight: 10 },
239
+ * { algorithm: 'sha-512', weight: 3 }
240
+ * ]);
241
+ * ```
242
+ */
243
+ export function formatWantContentDigest(preferences) {
244
+ return formatWantDigestField(preferences);
245
+ }
246
+ /**
247
+ * Format Want-Repr-Digest header field value.
248
+ * RFC 9530 §4.
249
+ *
250
+ * @param preferences - Array of preferences to format
251
+ * @returns Formatted header value
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const header = formatWantReprDigest([
256
+ * { algorithm: 'sha-512', weight: 3 },
257
+ * { algorithm: 'sha-256', weight: 10 },
258
+ * { algorithm: 'unixsum', weight: 0 }
259
+ * ]);
260
+ * ```
261
+ */
262
+ export function formatWantReprDigest(preferences) {
263
+ return formatWantDigestField(preferences);
264
+ }
265
+ // =============================================================================
266
+ // Generation and Verification
267
+ // =============================================================================
268
+ /**
269
+ * Map algorithm keys to Web Crypto algorithm names.
270
+ */
271
+ const CRYPTO_ALGORITHMS = {
272
+ 'sha-256': 'SHA-256',
273
+ 'sha-512': 'SHA-512',
274
+ };
275
+ /**
276
+ * Generate a digest for the given data.
277
+ * RFC 9530 §2, §3.
278
+ *
279
+ * Uses Web Crypto API for hashing. Only active algorithms are supported
280
+ * for generation per RFC 9530 §5.
281
+ *
282
+ * @param data - Data to hash (string, ArrayBuffer, or ArrayBufferView)
283
+ * @param algorithm - Hash algorithm (default: 'sha-256')
284
+ * @returns Promise resolving to digest
285
+ * @throws Error if algorithm is not supported for generation
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * const digest = await generateDigest('hello world');
290
+ * const headers = { 'Content-Digest': formatContentDigest([digest]) };
291
+ * ```
292
+ */
293
+ export async function generateDigest(data, algorithm = 'sha-256') {
294
+ const cryptoAlgorithm = CRYPTO_ALGORITHMS[algorithm];
295
+ if (!cryptoAlgorithm) {
296
+ throw new Error(`Unsupported algorithm for generation: ${algorithm}`);
297
+ }
298
+ let buffer;
299
+ if (typeof data === 'string') {
300
+ buffer = new TextEncoder().encode(data).buffer;
301
+ }
302
+ else if (data instanceof ArrayBuffer) {
303
+ buffer = data;
304
+ }
305
+ else {
306
+ // ArrayBufferView - create a copy to ensure it's an ArrayBuffer
307
+ const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
308
+ buffer = view.slice().buffer;
309
+ }
310
+ const hashBuffer = await globalThis.crypto.subtle.digest(cryptoAlgorithm, buffer);
311
+ return {
312
+ algorithm,
313
+ value: new Uint8Array(hashBuffer),
314
+ };
315
+ }
316
+ /**
317
+ * Verify a digest against the given data.
318
+ * RFC 9530 §2, §3.
319
+ *
320
+ * Only active algorithms (sha-256, sha-512) can be verified.
321
+ * Deprecated algorithms will return false.
322
+ *
323
+ * @param data - Data to verify
324
+ * @param digest - Digest to check against
325
+ * @returns Promise resolving to true if digest matches
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * const digests = parseContentDigest(headers.get('Content-Digest'));
330
+ * const sha256 = digests?.find(d => d.algorithm === 'sha-256');
331
+ * if (sha256 && await verifyDigest(content, sha256)) {
332
+ * // Content integrity verified
333
+ * }
334
+ * ```
335
+ */
336
+ export async function verifyDigest(data, digest) {
337
+ // RFC 9530 §5: deprecated algorithms should not be used for verification
338
+ // in adversarial settings, but we allow it for backward compatibility
339
+ if (!isActiveAlgorithm(digest.algorithm)) {
340
+ // Cannot verify deprecated algorithms
341
+ return false;
342
+ }
343
+ const computed = await generateDigest(data, digest.algorithm);
344
+ // Compare byte arrays
345
+ if (computed.value.length !== digest.value.length) {
346
+ return false;
347
+ }
348
+ for (let i = 0; i < computed.value.length; i++) {
349
+ if (computed.value[i] !== digest.value[i]) {
350
+ return false;
351
+ }
352
+ }
353
+ return true;
354
+ }
355
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Accept-Encoding utilities per RFC 9110.
3
+ * RFC 9110 §12.4.2, §12.4.3, §12.5.3.
4
+ */
5
+ import type { EncodingRange } from './types.js';
6
+ /**
7
+ * Parse an Accept-Encoding header into ranges.
8
+ */
9
+ export declare function parseAcceptEncoding(header: string): EncodingRange[];
10
+ /**
11
+ * Negotiate encoding based on Accept-Encoding.
12
+ */
13
+ export declare function negotiateEncoding(ranges: EncodingRange[], supported: string[]): string | null;
14
+ //# sourceMappingURL=encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD;;GAEG;AAEH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAsCnE;AAWD;;GAEG;AAEH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAiC7F"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Accept-Encoding utilities per RFC 9110.
3
+ * RFC 9110 §12.4.2, §12.4.3, §12.5.3.
4
+ */
5
+ import { isEmptyHeader, splitListValue, parseQValue } from './header-utils.js';
6
+ /**
7
+ * Parse an Accept-Encoding header into ranges.
8
+ */
9
+ // RFC 9110 §12.5.3: Accept-Encoding field-value parsing.
10
+ export function parseAcceptEncoding(header) {
11
+ if (isEmptyHeader(header)) {
12
+ return [];
13
+ }
14
+ const ranges = [];
15
+ const parts = splitListValue(header);
16
+ for (const part of parts) {
17
+ const segments = part.split(';').map(segment => segment.trim());
18
+ const encoding = segments[0]?.toLowerCase();
19
+ if (!encoding)
20
+ continue;
21
+ let q = 1.0;
22
+ let invalidQ = false;
23
+ for (let i = 1; i < segments.length; i++) {
24
+ const segment = segments[i];
25
+ const eqIndex = segment.indexOf('=');
26
+ if (eqIndex === -1)
27
+ continue;
28
+ const key = segment.slice(0, eqIndex).trim().toLowerCase();
29
+ if (key !== 'q')
30
+ continue;
31
+ const parsed = parseQValue(segment.slice(eqIndex + 1).trim());
32
+ if (parsed === null) {
33
+ invalidQ = true;
34
+ break;
35
+ }
36
+ q = parsed;
37
+ }
38
+ if (invalidQ) {
39
+ continue;
40
+ }
41
+ ranges.push({ encoding, q });
42
+ }
43
+ ranges.sort((a, b) => b.q - a.q);
44
+ return ranges;
45
+ }
46
+ function getQForEncoding(ranges, encoding) {
47
+ for (const range of ranges) {
48
+ if (range.encoding === encoding) {
49
+ return range.q;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Negotiate encoding based on Accept-Encoding.
56
+ */
57
+ // RFC 9110 §12.4.2, §12.4.3, §12.5.3: Content-coding selection.
58
+ export function negotiateEncoding(ranges, supported) {
59
+ if (supported.length === 0) {
60
+ return null;
61
+ }
62
+ if (ranges.length === 0) {
63
+ return supported[0] ?? null;
64
+ }
65
+ const wildcard = ranges.find(range => range.encoding === '*');
66
+ let best = null;
67
+ let bestQ = 0;
68
+ for (const encoding of supported) {
69
+ const normalized = encoding.toLowerCase();
70
+ const explicitQ = getQForEncoding(ranges, normalized);
71
+ const q = explicitQ !== null
72
+ ? explicitQ
73
+ : (normalized === 'identity'
74
+ ? (wildcard ? wildcard.q : 1.0)
75
+ : (wildcard ? wildcard.q : 0));
76
+ if (q > bestQ) {
77
+ best = encoding;
78
+ bestQ = q;
79
+ }
80
+ }
81
+ if (bestQ === 0) {
82
+ return null;
83
+ }
84
+ return best;
85
+ }
86
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"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"}
package/dist/etag.d.ts ADDED
@@ -0,0 +1,55 @@
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
+ import type { ETag } from './types.js';
7
+ export type { ETag } from './types.js';
8
+ /**
9
+ * Generate a simple ETag from data (sync, using string hash)
10
+ * Uses a simple hash algorithm suitable for most use cases.
11
+ * Returns format: "hash" (strong) or W/"hash" (if weak option specified)
12
+ */
13
+ export declare function generateETag(data: unknown, options?: {
14
+ weak?: boolean;
15
+ }): string;
16
+ /**
17
+ * Generate an ETag using Web Crypto API (async, more secure)
18
+ * Uses SHA-256 by default.
19
+ */
20
+ export declare function generateETagAsync(data: unknown, options?: {
21
+ algorithm?: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';
22
+ weak?: boolean;
23
+ }): Promise<string>;
24
+ /**
25
+ * Parse an ETag string into its components
26
+ * Handles: "abc", W/"abc", ""
27
+ * Returns null for invalid format
28
+ */
29
+ export declare function parseETag(etag: string): ETag | null;
30
+ /**
31
+ * Format an ETag object back to string
32
+ */
33
+ export declare function formatETag(etag: ETag): string;
34
+ /**
35
+ * Compare two ETags per RFC 9110 §8.8.3.
36
+ *
37
+ * Strong comparison: both must be strong AND values match
38
+ * Weak comparison: values match (regardless of weak/strong)
39
+ *
40
+ * RFC 9110 §8.8.3.2 comparison table:
41
+ * | ETag 1 | ETag 2 | Strong | Weak |
42
+ * |---------|---------|--------|-------|
43
+ * | W/"1" | W/"1" | false | true |
44
+ * | W/"1" | "1" | false | true |
45
+ * | "1" | "1" | true | true |
46
+ * | "1" | W/"1" | false | true |
47
+ * | W/"1" | W/"2" | false | false |
48
+ * | "1" | "2" | false | false |
49
+ */
50
+ export declare function compareETags(a: ETag, b: ETag, strong?: boolean): boolean;
51
+ /**
52
+ * Convenience function to compare ETag strings directly
53
+ */
54
+ export declare function compareETagStrings(a: string, b: string, strong?: boolean): boolean;
55
+ //# sourceMappingURL=etag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"etag.d.ts","sourceRoot":"","sources":["../src/etag.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,YAAY,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAoEvC;;;;GAIG;AAEH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAKhF;AAED;;;GAGG;AAEH,wBAAsB,iBAAiB,CACnC,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GACtF,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED;;;;GAIG;AAEH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAmCnD;AAED;;GAEG;AAEH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAa/E;AAED;;GAEG;AAEH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CASzF"}