@remix-run/multipart-parser 0.11.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.
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/lib/buffer-search.d.ts +9 -0
- package/dist/lib/buffer-search.d.ts.map +1 -0
- package/dist/lib/multipart-request.d.ts +25 -0
- package/dist/lib/multipart-request.d.ts.map +1 -0
- package/dist/lib/multipart.d.ts +145 -0
- package/dist/lib/multipart.d.ts.map +1 -0
- package/dist/lib/multipart.node.d.ts +41 -0
- package/dist/lib/multipart.node.d.ts.map +1 -0
- package/dist/lib/read-stream.d.ts +2 -0
- package/dist/lib/read-stream.d.ts.map +1 -0
- package/dist/multipart-parser.cjs +2021 -0
- package/dist/multipart-parser.cjs.map +7 -0
- package/dist/multipart-parser.d.ts +4 -0
- package/dist/multipart-parser.d.ts.map +1 -0
- package/dist/multipart-parser.js +1985 -0
- package/dist/multipart-parser.js.map +7 -0
- package/dist/multipart-parser.node.cjs +2027 -0
- package/dist/multipart-parser.node.cjs.map +7 -0
- package/dist/multipart-parser.node.d.ts +5 -0
- package/dist/multipart-parser.node.d.ts.map +1 -0
- package/dist/multipart-parser.node.js +1991 -0
- package/dist/multipart-parser.node.js.map +7 -0
- package/package.json +66 -0
- package/src/lib/buffer-search.ts +67 -0
- package/src/lib/multipart-request.ts +55 -0
- package/src/lib/multipart.node.ts +82 -0
- package/src/lib/multipart.ts +420 -0
- package/src/lib/read-stream.ts +15 -0
- package/src/multipart-parser.node.ts +14 -0
- package/src/multipart-parser.ts +16 -0
|
@@ -0,0 +1,1991 @@
|
|
|
1
|
+
// ../headers/src/lib/param-values.ts
|
|
2
|
+
function parseParams(input, delimiter = ";") {
|
|
3
|
+
let parser = delimiter === ";" ? /(?:^|;)\s*([^=;\s]+)(\s*=\s*(?:"((?:[^"\\]|\\.)*)"|((?:[^;]|\\\;)+))?)?/g : /(?:^|,)\s*([^=,\s]+)(\s*=\s*(?:"((?:[^"\\]|\\.)*)"|((?:[^,]|\\\,)+))?)?/g;
|
|
4
|
+
let params = [];
|
|
5
|
+
let match;
|
|
6
|
+
while ((match = parser.exec(input)) !== null) {
|
|
7
|
+
let key = match[1].trim();
|
|
8
|
+
let value;
|
|
9
|
+
if (match[2]) {
|
|
10
|
+
value = (match[3] || match[4] || "").replace(/\\(.)/g, "$1").trim();
|
|
11
|
+
}
|
|
12
|
+
params.push([key, value]);
|
|
13
|
+
}
|
|
14
|
+
return params;
|
|
15
|
+
}
|
|
16
|
+
function quote(value) {
|
|
17
|
+
if (value.includes('"') || value.includes(";") || value.includes(" ")) {
|
|
18
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ../headers/src/lib/utils.ts
|
|
24
|
+
function capitalize(str) {
|
|
25
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function isIterable(value) {
|
|
28
|
+
return value != null && typeof value[Symbol.iterator] === "function";
|
|
29
|
+
}
|
|
30
|
+
function isValidDate(date) {
|
|
31
|
+
return date instanceof Date && !isNaN(date.getTime());
|
|
32
|
+
}
|
|
33
|
+
function quoteEtag(tag) {
|
|
34
|
+
return tag === "*" ? tag : /^(W\/)?".*"$/.test(tag) ? tag : `"${tag}"`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ../headers/src/lib/accept.ts
|
|
38
|
+
var Accept = class {
|
|
39
|
+
#map;
|
|
40
|
+
constructor(init) {
|
|
41
|
+
this.#map = /* @__PURE__ */ new Map();
|
|
42
|
+
if (init) {
|
|
43
|
+
if (typeof init === "string") {
|
|
44
|
+
for (let piece of init.split(/\s*,\s*/)) {
|
|
45
|
+
let params = parseParams(piece);
|
|
46
|
+
if (params.length < 1) continue;
|
|
47
|
+
let mediaType = params[0][0];
|
|
48
|
+
let weight = 1;
|
|
49
|
+
for (let i = 1; i < params.length; i++) {
|
|
50
|
+
let [key, value] = params[i];
|
|
51
|
+
if (key === "q") {
|
|
52
|
+
weight = Number(value);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.#map.set(mediaType.toLowerCase(), weight);
|
|
57
|
+
}
|
|
58
|
+
} else if (isIterable(init)) {
|
|
59
|
+
for (let mediaType of init) {
|
|
60
|
+
if (Array.isArray(mediaType)) {
|
|
61
|
+
this.#map.set(mediaType[0].toLowerCase(), mediaType[1]);
|
|
62
|
+
} else {
|
|
63
|
+
this.#map.set(mediaType.toLowerCase(), 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
for (let mediaType of Object.getOwnPropertyNames(init)) {
|
|
68
|
+
this.#map.set(mediaType.toLowerCase(), init[mediaType]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.#sort();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
#sort() {
|
|
75
|
+
this.#map = new Map([...this.#map].sort((a, b) => b[1] - a[1]));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* An array of all media types in the header.
|
|
79
|
+
*/
|
|
80
|
+
get mediaTypes() {
|
|
81
|
+
return Array.from(this.#map.keys());
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* An array of all weights (q values) in the header.
|
|
85
|
+
*/
|
|
86
|
+
get weights() {
|
|
87
|
+
return Array.from(this.#map.values());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The number of media types in the `Accept` header.
|
|
91
|
+
*/
|
|
92
|
+
get size() {
|
|
93
|
+
return this.#map.size;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns `true` if the header matches the given media type (i.e. it is "acceptable").
|
|
97
|
+
* @param mediaType The media type to check.
|
|
98
|
+
* @returns `true` if the media type is acceptable, `false` otherwise.
|
|
99
|
+
*/
|
|
100
|
+
accepts(mediaType) {
|
|
101
|
+
return this.getWeight(mediaType) > 0;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Gets the weight of a given media type. Also supports wildcards, so e.g. `text/*` will match `text/html`.
|
|
105
|
+
* @param mediaType The media type to get the weight of.
|
|
106
|
+
* @returns The weight of the media type.
|
|
107
|
+
*/
|
|
108
|
+
getWeight(mediaType) {
|
|
109
|
+
let [type, subtype] = mediaType.toLowerCase().split("/");
|
|
110
|
+
for (let [key, value] of this) {
|
|
111
|
+
let [t, s] = key.split("/");
|
|
112
|
+
if ((t === type || t === "*" || type === "*") && (s === subtype || s === "*" || subtype === "*")) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Returns the most preferred media type from the given list of media types.
|
|
120
|
+
* @param mediaTypes The list of media types to choose from.
|
|
121
|
+
* @returns The most preferred media type or `null` if none match.
|
|
122
|
+
*/
|
|
123
|
+
getPreferred(mediaTypes) {
|
|
124
|
+
let sorted = mediaTypes.map((mediaType) => [mediaType, this.getWeight(mediaType)]).sort((a, b) => b[1] - a[1]);
|
|
125
|
+
let first = sorted[0];
|
|
126
|
+
return first !== void 0 && first[1] > 0 ? first[0] : null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns the weight of a media type. If it is not in the header verbatim, this returns `null`.
|
|
130
|
+
* @param mediaType The media type to get the weight of.
|
|
131
|
+
* @returns The weight of the media type, or `null` if it is not in the header.
|
|
132
|
+
*/
|
|
133
|
+
get(mediaType) {
|
|
134
|
+
return this.#map.get(mediaType.toLowerCase()) ?? null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Sets a media type with the given weight.
|
|
138
|
+
* @param mediaType The media type to set.
|
|
139
|
+
* @param weight The weight of the media type. Defaults to 1.
|
|
140
|
+
*/
|
|
141
|
+
set(mediaType, weight = 1) {
|
|
142
|
+
this.#map.set(mediaType.toLowerCase(), weight);
|
|
143
|
+
this.#sort();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Removes the given media type from the header.
|
|
147
|
+
* @param mediaType The media type to remove.
|
|
148
|
+
*/
|
|
149
|
+
delete(mediaType) {
|
|
150
|
+
this.#map.delete(mediaType.toLowerCase());
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Checks if a media type is in the header.
|
|
154
|
+
* @param mediaType The media type to check.
|
|
155
|
+
* @returns `true` if the media type is in the header (verbatim), `false` otherwise.
|
|
156
|
+
*/
|
|
157
|
+
has(mediaType) {
|
|
158
|
+
return this.#map.has(mediaType.toLowerCase());
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Removes all media types from the header.
|
|
162
|
+
*/
|
|
163
|
+
clear() {
|
|
164
|
+
this.#map.clear();
|
|
165
|
+
}
|
|
166
|
+
entries() {
|
|
167
|
+
return this.#map.entries();
|
|
168
|
+
}
|
|
169
|
+
[Symbol.iterator]() {
|
|
170
|
+
return this.entries();
|
|
171
|
+
}
|
|
172
|
+
forEach(callback, thisArg) {
|
|
173
|
+
for (let [mediaType, weight] of this) {
|
|
174
|
+
callback.call(thisArg, mediaType, weight, this);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
toString() {
|
|
178
|
+
let pairs = [];
|
|
179
|
+
for (let [mediaType, weight] of this.#map) {
|
|
180
|
+
pairs.push(`${mediaType}${weight === 1 ? "" : `;q=${weight}`}`);
|
|
181
|
+
}
|
|
182
|
+
return pairs.join(",");
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// ../headers/src/lib/accept-encoding.ts
|
|
187
|
+
var AcceptEncoding = class {
|
|
188
|
+
#map;
|
|
189
|
+
constructor(init) {
|
|
190
|
+
this.#map = /* @__PURE__ */ new Map();
|
|
191
|
+
if (init) {
|
|
192
|
+
if (typeof init === "string") {
|
|
193
|
+
for (let piece of init.split(/\s*,\s*/)) {
|
|
194
|
+
let params = parseParams(piece);
|
|
195
|
+
if (params.length < 1) continue;
|
|
196
|
+
let encoding = params[0][0];
|
|
197
|
+
let weight = 1;
|
|
198
|
+
for (let i = 1; i < params.length; i++) {
|
|
199
|
+
let [key, value] = params[i];
|
|
200
|
+
if (key === "q") {
|
|
201
|
+
weight = Number(value);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.#map.set(encoding.toLowerCase(), weight);
|
|
206
|
+
}
|
|
207
|
+
} else if (isIterable(init)) {
|
|
208
|
+
for (let value of init) {
|
|
209
|
+
if (Array.isArray(value)) {
|
|
210
|
+
this.#map.set(value[0].toLowerCase(), value[1]);
|
|
211
|
+
} else {
|
|
212
|
+
this.#map.set(value.toLowerCase(), 1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
for (let encoding of Object.getOwnPropertyNames(init)) {
|
|
217
|
+
this.#map.set(encoding.toLowerCase(), init[encoding]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
this.#sort();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
#sort() {
|
|
224
|
+
this.#map = new Map([...this.#map].sort((a, b) => b[1] - a[1]));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* An array of all encodings in the header.
|
|
228
|
+
*/
|
|
229
|
+
get encodings() {
|
|
230
|
+
return Array.from(this.#map.keys());
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* An array of all weights (q values) in the header.
|
|
234
|
+
*/
|
|
235
|
+
get weights() {
|
|
236
|
+
return Array.from(this.#map.values());
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The number of encodings in the header.
|
|
240
|
+
*/
|
|
241
|
+
get size() {
|
|
242
|
+
return this.#map.size;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Returns `true` if the header matches the given encoding (i.e. it is "acceptable").
|
|
246
|
+
* @param encoding The encoding to check.
|
|
247
|
+
* @returns `true` if the encoding is acceptable, `false` otherwise.
|
|
248
|
+
*/
|
|
249
|
+
accepts(encoding) {
|
|
250
|
+
return encoding.toLowerCase() === "identity" || this.getWeight(encoding) > 0;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Gets the weight an encoding. Performs wildcard matching so `*` matches all encodings.
|
|
254
|
+
* @param encoding The encoding to get.
|
|
255
|
+
* @returns The weight of the encoding, or `0` if it is not in the header.
|
|
256
|
+
*/
|
|
257
|
+
getWeight(encoding) {
|
|
258
|
+
let lower = encoding.toLowerCase();
|
|
259
|
+
for (let [enc, weight] of this) {
|
|
260
|
+
if (enc === lower || enc === "*" || lower === "*") {
|
|
261
|
+
return weight;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Returns the most preferred encoding from the given list of encodings.
|
|
268
|
+
* @param encodings The encodings to choose from.
|
|
269
|
+
* @returns The most preferred encoding or `null` if none match.
|
|
270
|
+
*/
|
|
271
|
+
getPreferred(encodings) {
|
|
272
|
+
let sorted = encodings.map((encoding) => [encoding, this.getWeight(encoding)]).sort((a, b) => b[1] - a[1]);
|
|
273
|
+
let first = sorted[0];
|
|
274
|
+
return first !== void 0 && first[1] > 0 ? first[0] : null;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Gets the weight of an encoding. If it is not in the header verbatim, this returns `null`.
|
|
278
|
+
* @param encoding The encoding to get.
|
|
279
|
+
* @returns The weight of the encoding, or `null` if it is not in the header.
|
|
280
|
+
*/
|
|
281
|
+
get(encoding) {
|
|
282
|
+
return this.#map.get(encoding.toLowerCase()) ?? null;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Sets an encoding with the given weight.
|
|
286
|
+
* @param encoding The encoding to set.
|
|
287
|
+
* @param weight The weight of the encoding. Defaults to 1.
|
|
288
|
+
*/
|
|
289
|
+
set(encoding, weight = 1) {
|
|
290
|
+
this.#map.set(encoding.toLowerCase(), weight);
|
|
291
|
+
this.#sort();
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Removes the given encoding from the header.
|
|
295
|
+
* @param encoding The encoding to remove.
|
|
296
|
+
*/
|
|
297
|
+
delete(encoding) {
|
|
298
|
+
this.#map.delete(encoding.toLowerCase());
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Checks if the header contains a given encoding.
|
|
302
|
+
* @param encoding The encoding to check.
|
|
303
|
+
* @returns `true` if the encoding is in the header, `false` otherwise.
|
|
304
|
+
*/
|
|
305
|
+
has(encoding) {
|
|
306
|
+
return this.#map.has(encoding.toLowerCase());
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Removes all encodings from the header.
|
|
310
|
+
*/
|
|
311
|
+
clear() {
|
|
312
|
+
this.#map.clear();
|
|
313
|
+
}
|
|
314
|
+
entries() {
|
|
315
|
+
return this.#map.entries();
|
|
316
|
+
}
|
|
317
|
+
[Symbol.iterator]() {
|
|
318
|
+
return this.entries();
|
|
319
|
+
}
|
|
320
|
+
forEach(callback, thisArg) {
|
|
321
|
+
for (let [encoding, weight] of this) {
|
|
322
|
+
callback.call(thisArg, encoding, weight, this);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
toString() {
|
|
326
|
+
let pairs = [];
|
|
327
|
+
for (let [encoding, weight] of this.#map) {
|
|
328
|
+
pairs.push(`${encoding}${weight === 1 ? "" : `;q=${weight}`}`);
|
|
329
|
+
}
|
|
330
|
+
return pairs.join(",");
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// ../headers/src/lib/accept-language.ts
|
|
335
|
+
var AcceptLanguage = class {
|
|
336
|
+
#map;
|
|
337
|
+
constructor(init) {
|
|
338
|
+
this.#map = /* @__PURE__ */ new Map();
|
|
339
|
+
if (init) {
|
|
340
|
+
if (typeof init === "string") {
|
|
341
|
+
for (let piece of init.split(/\s*,\s*/)) {
|
|
342
|
+
let params = parseParams(piece);
|
|
343
|
+
if (params.length < 1) continue;
|
|
344
|
+
let language = params[0][0];
|
|
345
|
+
let weight = 1;
|
|
346
|
+
for (let i = 1; i < params.length; i++) {
|
|
347
|
+
let [key, value] = params[i];
|
|
348
|
+
if (key === "q") {
|
|
349
|
+
weight = Number(value);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
this.#map.set(language.toLowerCase(), weight);
|
|
354
|
+
}
|
|
355
|
+
} else if (isIterable(init)) {
|
|
356
|
+
for (let value of init) {
|
|
357
|
+
if (Array.isArray(value)) {
|
|
358
|
+
this.#map.set(value[0].toLowerCase(), value[1]);
|
|
359
|
+
} else {
|
|
360
|
+
this.#map.set(value.toLowerCase(), 1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
for (let language of Object.getOwnPropertyNames(init)) {
|
|
365
|
+
this.#map.set(language.toLowerCase(), init[language]);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
this.#sort();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
#sort() {
|
|
372
|
+
this.#map = new Map([...this.#map].sort((a, b) => b[1] - a[1]));
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* An array of all languages in the header.
|
|
376
|
+
*/
|
|
377
|
+
get languages() {
|
|
378
|
+
return Array.from(this.#map.keys());
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* An array of all weights (q values) in the header.
|
|
382
|
+
*/
|
|
383
|
+
get weights() {
|
|
384
|
+
return Array.from(this.#map.values());
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* The number of languages in the header.
|
|
388
|
+
*/
|
|
389
|
+
get size() {
|
|
390
|
+
return this.#map.size;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Returns `true` if the header matches the given language (i.e. it is "acceptable").
|
|
394
|
+
* @param language The locale identifier of the language to check.
|
|
395
|
+
* @returns `true` if the language is acceptable, `false` otherwise.
|
|
396
|
+
*/
|
|
397
|
+
accepts(language) {
|
|
398
|
+
return this.getWeight(language) > 0;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Gets the weight of a language with the given locale identifier. Performs wildcard and subtype
|
|
402
|
+
* matching, so `en` matches `en-US` and `en-GB`, and `*` matches all languages.
|
|
403
|
+
* @param language The locale identifier of the language to get.
|
|
404
|
+
* @returns The weight of the language, or `0` if it is not in the header.
|
|
405
|
+
*/
|
|
406
|
+
getWeight(language) {
|
|
407
|
+
let [base, subtype] = language.toLowerCase().split("-");
|
|
408
|
+
for (let [key, value] of this) {
|
|
409
|
+
let [b, s] = key.split("-");
|
|
410
|
+
if ((b === base || b === "*" || base === "*") && (s === subtype || s === void 0 || subtype === void 0)) {
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return 0;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Returns the most preferred language from the given list of languages.
|
|
418
|
+
* @param languages The locale identifiers of the languages to choose from.
|
|
419
|
+
* @returns The most preferred language or `null` if none match.
|
|
420
|
+
*/
|
|
421
|
+
getPreferred(languages) {
|
|
422
|
+
let sorted = languages.map((language) => [language, this.getWeight(language)]).sort((a, b) => b[1] - a[1]);
|
|
423
|
+
let first = sorted[0];
|
|
424
|
+
return first !== void 0 && first[1] > 0 ? first[0] : null;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Gets the weight of a language with the given locale identifier. If it is not in the header
|
|
428
|
+
* verbatim, this returns `null`.
|
|
429
|
+
* @param language The locale identifier of the language to get.
|
|
430
|
+
* @returns The weight of the language, or `null` if it is not in the header.
|
|
431
|
+
*/
|
|
432
|
+
get(language) {
|
|
433
|
+
return this.#map.get(language.toLowerCase()) ?? null;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Sets a language with the given weight.
|
|
437
|
+
* @param language The locale identifier of the language to set.
|
|
438
|
+
* @param weight The weight of the language. Defaults to 1.
|
|
439
|
+
*/
|
|
440
|
+
set(language, weight = 1) {
|
|
441
|
+
this.#map.set(language.toLowerCase(), weight);
|
|
442
|
+
this.#sort();
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Removes a language with the given locale identifier.
|
|
446
|
+
* @param language The locale identifier of the language to remove.
|
|
447
|
+
*/
|
|
448
|
+
delete(language) {
|
|
449
|
+
this.#map.delete(language.toLowerCase());
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Checks if the header contains a language with the given locale identifier.
|
|
453
|
+
* @param language The locale identifier of the language to check.
|
|
454
|
+
* @returns `true` if the language is in the header, `false` otherwise.
|
|
455
|
+
*/
|
|
456
|
+
has(language) {
|
|
457
|
+
return this.#map.has(language.toLowerCase());
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Removes all languages from the header.
|
|
461
|
+
*/
|
|
462
|
+
clear() {
|
|
463
|
+
this.#map.clear();
|
|
464
|
+
}
|
|
465
|
+
entries() {
|
|
466
|
+
return this.#map.entries();
|
|
467
|
+
}
|
|
468
|
+
[Symbol.iterator]() {
|
|
469
|
+
return this.entries();
|
|
470
|
+
}
|
|
471
|
+
forEach(callback, thisArg) {
|
|
472
|
+
for (let [language, weight] of this) {
|
|
473
|
+
callback.call(thisArg, language, weight, this);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
toString() {
|
|
477
|
+
let pairs = [];
|
|
478
|
+
for (let [language, weight] of this.#map) {
|
|
479
|
+
pairs.push(`${language}${weight === 1 ? "" : `;q=${weight}`}`);
|
|
480
|
+
}
|
|
481
|
+
return pairs.join(",");
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// ../headers/src/lib/cache-control.ts
|
|
486
|
+
var CacheControl = class {
|
|
487
|
+
maxAge;
|
|
488
|
+
maxStale;
|
|
489
|
+
minFresh;
|
|
490
|
+
sMaxage;
|
|
491
|
+
noCache;
|
|
492
|
+
noStore;
|
|
493
|
+
noTransform;
|
|
494
|
+
onlyIfCached;
|
|
495
|
+
mustRevalidate;
|
|
496
|
+
proxyRevalidate;
|
|
497
|
+
mustUnderstand;
|
|
498
|
+
private;
|
|
499
|
+
public;
|
|
500
|
+
immutable;
|
|
501
|
+
staleWhileRevalidate;
|
|
502
|
+
staleIfError;
|
|
503
|
+
constructor(init) {
|
|
504
|
+
if (init) {
|
|
505
|
+
if (typeof init === "string") {
|
|
506
|
+
let params = parseParams(init, ",");
|
|
507
|
+
if (params.length > 0) {
|
|
508
|
+
for (let [name, value] of params) {
|
|
509
|
+
switch (name) {
|
|
510
|
+
case "max-age":
|
|
511
|
+
this.maxAge = Number(value);
|
|
512
|
+
break;
|
|
513
|
+
case "max-stale":
|
|
514
|
+
this.maxStale = Number(value);
|
|
515
|
+
break;
|
|
516
|
+
case "min-fresh":
|
|
517
|
+
this.minFresh = Number(value);
|
|
518
|
+
break;
|
|
519
|
+
case "s-maxage":
|
|
520
|
+
this.sMaxage = Number(value);
|
|
521
|
+
break;
|
|
522
|
+
case "no-cache":
|
|
523
|
+
this.noCache = true;
|
|
524
|
+
break;
|
|
525
|
+
case "no-store":
|
|
526
|
+
this.noStore = true;
|
|
527
|
+
break;
|
|
528
|
+
case "no-transform":
|
|
529
|
+
this.noTransform = true;
|
|
530
|
+
break;
|
|
531
|
+
case "only-if-cached":
|
|
532
|
+
this.onlyIfCached = true;
|
|
533
|
+
break;
|
|
534
|
+
case "must-revalidate":
|
|
535
|
+
this.mustRevalidate = true;
|
|
536
|
+
break;
|
|
537
|
+
case "proxy-revalidate":
|
|
538
|
+
this.proxyRevalidate = true;
|
|
539
|
+
break;
|
|
540
|
+
case "must-understand":
|
|
541
|
+
this.mustUnderstand = true;
|
|
542
|
+
break;
|
|
543
|
+
case "private":
|
|
544
|
+
this.private = true;
|
|
545
|
+
break;
|
|
546
|
+
case "public":
|
|
547
|
+
this.public = true;
|
|
548
|
+
break;
|
|
549
|
+
case "immutable":
|
|
550
|
+
this.immutable = true;
|
|
551
|
+
break;
|
|
552
|
+
case "stale-while-revalidate":
|
|
553
|
+
this.staleWhileRevalidate = Number(value);
|
|
554
|
+
break;
|
|
555
|
+
case "stale-if-error":
|
|
556
|
+
this.staleIfError = Number(value);
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
this.maxAge = init.maxAge;
|
|
563
|
+
this.maxStale = init.maxStale;
|
|
564
|
+
this.minFresh = init.minFresh;
|
|
565
|
+
this.sMaxage = init.sMaxage;
|
|
566
|
+
this.noCache = init.noCache;
|
|
567
|
+
this.noStore = init.noStore;
|
|
568
|
+
this.noTransform = init.noTransform;
|
|
569
|
+
this.onlyIfCached = init.onlyIfCached;
|
|
570
|
+
this.mustRevalidate = init.mustRevalidate;
|
|
571
|
+
this.proxyRevalidate = init.proxyRevalidate;
|
|
572
|
+
this.mustUnderstand = init.mustUnderstand;
|
|
573
|
+
this.private = init.private;
|
|
574
|
+
this.public = init.public;
|
|
575
|
+
this.immutable = init.immutable;
|
|
576
|
+
this.staleWhileRevalidate = init.staleWhileRevalidate;
|
|
577
|
+
this.staleIfError = init.staleIfError;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
toString() {
|
|
582
|
+
let parts = [];
|
|
583
|
+
if (this.public) {
|
|
584
|
+
parts.push("public");
|
|
585
|
+
}
|
|
586
|
+
if (this.private) {
|
|
587
|
+
parts.push("private");
|
|
588
|
+
}
|
|
589
|
+
if (typeof this.maxAge === "number") {
|
|
590
|
+
parts.push(`max-age=${this.maxAge}`);
|
|
591
|
+
}
|
|
592
|
+
if (typeof this.sMaxage === "number") {
|
|
593
|
+
parts.push(`s-maxage=${this.sMaxage}`);
|
|
594
|
+
}
|
|
595
|
+
if (this.noCache) {
|
|
596
|
+
parts.push("no-cache");
|
|
597
|
+
}
|
|
598
|
+
if (this.noStore) {
|
|
599
|
+
parts.push("no-store");
|
|
600
|
+
}
|
|
601
|
+
if (this.noTransform) {
|
|
602
|
+
parts.push("no-transform");
|
|
603
|
+
}
|
|
604
|
+
if (this.onlyIfCached) {
|
|
605
|
+
parts.push("only-if-cached");
|
|
606
|
+
}
|
|
607
|
+
if (this.mustRevalidate) {
|
|
608
|
+
parts.push("must-revalidate");
|
|
609
|
+
}
|
|
610
|
+
if (this.proxyRevalidate) {
|
|
611
|
+
parts.push("proxy-revalidate");
|
|
612
|
+
}
|
|
613
|
+
if (this.mustUnderstand) {
|
|
614
|
+
parts.push("must-understand");
|
|
615
|
+
}
|
|
616
|
+
if (this.immutable) {
|
|
617
|
+
parts.push("immutable");
|
|
618
|
+
}
|
|
619
|
+
if (typeof this.staleWhileRevalidate === "number") {
|
|
620
|
+
parts.push(`stale-while-revalidate=${this.staleWhileRevalidate}`);
|
|
621
|
+
}
|
|
622
|
+
if (typeof this.staleIfError === "number") {
|
|
623
|
+
parts.push(`stale-if-error=${this.staleIfError}`);
|
|
624
|
+
}
|
|
625
|
+
if (typeof this.maxStale === "number") {
|
|
626
|
+
parts.push(`max-stale=${this.maxStale}`);
|
|
627
|
+
}
|
|
628
|
+
if (typeof this.minFresh === "number") {
|
|
629
|
+
parts.push(`min-fresh=${this.minFresh}`);
|
|
630
|
+
}
|
|
631
|
+
return parts.join(", ");
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// ../headers/src/lib/content-disposition.ts
|
|
636
|
+
var ContentDisposition = class {
|
|
637
|
+
filename;
|
|
638
|
+
filenameSplat;
|
|
639
|
+
name;
|
|
640
|
+
type;
|
|
641
|
+
constructor(init) {
|
|
642
|
+
if (init) {
|
|
643
|
+
if (typeof init === "string") {
|
|
644
|
+
let params = parseParams(init);
|
|
645
|
+
if (params.length > 0) {
|
|
646
|
+
this.type = params[0][0];
|
|
647
|
+
for (let [name, value] of params.slice(1)) {
|
|
648
|
+
if (name === "filename") {
|
|
649
|
+
this.filename = value;
|
|
650
|
+
} else if (name === "filename*") {
|
|
651
|
+
this.filenameSplat = value;
|
|
652
|
+
} else if (name === "name") {
|
|
653
|
+
this.name = value;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
this.filename = init.filename;
|
|
659
|
+
this.filenameSplat = init.filenameSplat;
|
|
660
|
+
this.name = init.name;
|
|
661
|
+
this.type = init.type;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* The preferred filename for the content, using the `filename*` parameter if present, falling back to the `filename` parameter.
|
|
667
|
+
*
|
|
668
|
+
* From [RFC 6266](https://tools.ietf.org/html/rfc6266):
|
|
669
|
+
*
|
|
670
|
+
* Many user agent implementations predating this specification do not understand the "filename*" parameter.
|
|
671
|
+
* Therefore, when both "filename" and "filename*" are present in a single header field value, recipients SHOULD
|
|
672
|
+
* pick "filename*" and ignore "filename". This way, senders can avoid special-casing specific user agents by
|
|
673
|
+
* sending both the more expressive "filename*" parameter, and the "filename" parameter as fallback for legacy recipients.
|
|
674
|
+
*/
|
|
675
|
+
get preferredFilename() {
|
|
676
|
+
let filenameSplat = this.filenameSplat;
|
|
677
|
+
if (filenameSplat) {
|
|
678
|
+
let decodedFilename = decodeFilenameSplat(filenameSplat);
|
|
679
|
+
if (decodedFilename) return decodedFilename;
|
|
680
|
+
}
|
|
681
|
+
return this.filename;
|
|
682
|
+
}
|
|
683
|
+
toString() {
|
|
684
|
+
if (!this.type) {
|
|
685
|
+
return "";
|
|
686
|
+
}
|
|
687
|
+
let parts = [this.type];
|
|
688
|
+
if (this.name) {
|
|
689
|
+
parts.push(`name=${quote(this.name)}`);
|
|
690
|
+
}
|
|
691
|
+
if (this.filename) {
|
|
692
|
+
parts.push(`filename=${quote(this.filename)}`);
|
|
693
|
+
}
|
|
694
|
+
if (this.filenameSplat) {
|
|
695
|
+
parts.push(`filename*=${quote(this.filenameSplat)}`);
|
|
696
|
+
}
|
|
697
|
+
return parts.join("; ");
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
function decodeFilenameSplat(value) {
|
|
701
|
+
let match = value.match(/^([\w-]+)'([^']*)'(.+)$/);
|
|
702
|
+
if (!match) return null;
|
|
703
|
+
let [, charset, , encodedFilename] = match;
|
|
704
|
+
let decodedFilename = percentDecode(encodedFilename);
|
|
705
|
+
try {
|
|
706
|
+
let decoder2 = new TextDecoder(charset);
|
|
707
|
+
let bytes = new Uint8Array(decodedFilename.split("").map((char) => char.charCodeAt(0)));
|
|
708
|
+
return decoder2.decode(bytes);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.warn(`Failed to decode filename from charset ${charset}:`, error);
|
|
711
|
+
return decodedFilename;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function percentDecode(value) {
|
|
715
|
+
return value.replace(/\+/g, " ").replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
716
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ../headers/src/lib/content-type.ts
|
|
721
|
+
var ContentType = class {
|
|
722
|
+
boundary;
|
|
723
|
+
charset;
|
|
724
|
+
mediaType;
|
|
725
|
+
constructor(init) {
|
|
726
|
+
if (init) {
|
|
727
|
+
if (typeof init === "string") {
|
|
728
|
+
let params = parseParams(init);
|
|
729
|
+
if (params.length > 0) {
|
|
730
|
+
this.mediaType = params[0][0];
|
|
731
|
+
for (let [name, value] of params.slice(1)) {
|
|
732
|
+
if (name === "boundary") {
|
|
733
|
+
this.boundary = value;
|
|
734
|
+
} else if (name === "charset") {
|
|
735
|
+
this.charset = value;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
this.boundary = init.boundary;
|
|
741
|
+
this.charset = init.charset;
|
|
742
|
+
this.mediaType = init.mediaType;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
toString() {
|
|
747
|
+
if (!this.mediaType) {
|
|
748
|
+
return "";
|
|
749
|
+
}
|
|
750
|
+
let parts = [this.mediaType];
|
|
751
|
+
if (this.charset) {
|
|
752
|
+
parts.push(`charset=${quote(this.charset)}`);
|
|
753
|
+
}
|
|
754
|
+
if (this.boundary) {
|
|
755
|
+
parts.push(`boundary=${quote(this.boundary)}`);
|
|
756
|
+
}
|
|
757
|
+
return parts.join("; ");
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
// ../headers/src/lib/cookie.ts
|
|
762
|
+
var Cookie = class {
|
|
763
|
+
#map;
|
|
764
|
+
constructor(init) {
|
|
765
|
+
this.#map = /* @__PURE__ */ new Map();
|
|
766
|
+
if (init) {
|
|
767
|
+
if (typeof init === "string") {
|
|
768
|
+
let params = parseParams(init);
|
|
769
|
+
for (let [name, value] of params) {
|
|
770
|
+
this.#map.set(name, value ?? "");
|
|
771
|
+
}
|
|
772
|
+
} else if (isIterable(init)) {
|
|
773
|
+
for (let [name, value] of init) {
|
|
774
|
+
this.#map.set(name, value);
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
for (let name of Object.getOwnPropertyNames(init)) {
|
|
778
|
+
this.#map.set(name, init[name]);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* An array of the names of the cookies in the header.
|
|
785
|
+
*/
|
|
786
|
+
get names() {
|
|
787
|
+
return Array.from(this.#map.keys());
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* An array of the values of the cookies in the header.
|
|
791
|
+
*/
|
|
792
|
+
get values() {
|
|
793
|
+
return Array.from(this.#map.values());
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* The number of cookies in the header.
|
|
797
|
+
*/
|
|
798
|
+
get size() {
|
|
799
|
+
return this.#map.size;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Gets the value of a cookie with the given name from the header.
|
|
803
|
+
* @param name The name of the cookie.
|
|
804
|
+
* @returns The value of the cookie, or `null` if the cookie does not exist.
|
|
805
|
+
*/
|
|
806
|
+
get(name) {
|
|
807
|
+
return this.#map.get(name) ?? null;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Sets a cookie with the given name and value in the header.
|
|
811
|
+
* @param name The name of the cookie.
|
|
812
|
+
* @param value The value of the cookie.
|
|
813
|
+
*/
|
|
814
|
+
set(name, value) {
|
|
815
|
+
this.#map.set(name, value);
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Removes a cookie with the given name from the header.
|
|
819
|
+
* @param name The name of the cookie.
|
|
820
|
+
*/
|
|
821
|
+
delete(name) {
|
|
822
|
+
this.#map.delete(name);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* True if a cookie with the given name exists in the header.
|
|
826
|
+
* @param name The name of the cookie.
|
|
827
|
+
* @returns True if a cookie with the given name exists in the header.
|
|
828
|
+
*/
|
|
829
|
+
has(name) {
|
|
830
|
+
return this.#map.has(name);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Removes all cookies from the header.
|
|
834
|
+
*/
|
|
835
|
+
clear() {
|
|
836
|
+
this.#map.clear();
|
|
837
|
+
}
|
|
838
|
+
entries() {
|
|
839
|
+
return this.#map.entries();
|
|
840
|
+
}
|
|
841
|
+
[Symbol.iterator]() {
|
|
842
|
+
return this.entries();
|
|
843
|
+
}
|
|
844
|
+
forEach(callback, thisArg) {
|
|
845
|
+
for (let [name, value] of this) {
|
|
846
|
+
callback.call(thisArg, name, value, this);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
toString() {
|
|
850
|
+
let pairs = [];
|
|
851
|
+
for (let [name, value] of this.#map) {
|
|
852
|
+
pairs.push(`${name}=${quote(value)}`);
|
|
853
|
+
}
|
|
854
|
+
return pairs.join("; ");
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// ../headers/src/lib/if-none-match.ts
|
|
859
|
+
var IfNoneMatch = class {
|
|
860
|
+
tags = [];
|
|
861
|
+
constructor(init) {
|
|
862
|
+
if (init) {
|
|
863
|
+
if (typeof init === "string") {
|
|
864
|
+
this.tags.push(...init.split(/\s*,\s*/).map(quoteEtag));
|
|
865
|
+
} else if (Array.isArray(init)) {
|
|
866
|
+
this.tags.push(...init.map(quoteEtag));
|
|
867
|
+
} else {
|
|
868
|
+
this.tags.push(...init.tags.map(quoteEtag));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Checks if the header contains the given entity tag.
|
|
874
|
+
*
|
|
875
|
+
* Note: This method checks only for exact matches and does not consider wildcards.
|
|
876
|
+
*
|
|
877
|
+
* @param tag The entity tag to check for.
|
|
878
|
+
* @returns `true` if the tag is present in the header, `false` otherwise.
|
|
879
|
+
*/
|
|
880
|
+
has(tag) {
|
|
881
|
+
return this.tags.includes(quoteEtag(tag));
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Checks if this header matches the given entity tag.
|
|
885
|
+
*
|
|
886
|
+
* @param tag The entity tag to check for.
|
|
887
|
+
* @returns `true` if the tag is present in the header (or the header contains a wildcard), `false` otherwise.
|
|
888
|
+
*/
|
|
889
|
+
matches(tag) {
|
|
890
|
+
return this.has(tag) || this.tags.includes("*");
|
|
891
|
+
}
|
|
892
|
+
toString() {
|
|
893
|
+
return this.tags.join(", ");
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// ../headers/src/lib/set-cookie.ts
|
|
898
|
+
var SetCookie = class {
|
|
899
|
+
domain;
|
|
900
|
+
expires;
|
|
901
|
+
httpOnly;
|
|
902
|
+
maxAge;
|
|
903
|
+
name;
|
|
904
|
+
path;
|
|
905
|
+
sameSite;
|
|
906
|
+
secure;
|
|
907
|
+
value;
|
|
908
|
+
constructor(init) {
|
|
909
|
+
if (init) {
|
|
910
|
+
if (typeof init === "string") {
|
|
911
|
+
let params = parseParams(init);
|
|
912
|
+
if (params.length > 0) {
|
|
913
|
+
this.name = params[0][0];
|
|
914
|
+
this.value = params[0][1];
|
|
915
|
+
for (let [key, value] of params.slice(1)) {
|
|
916
|
+
switch (key.toLowerCase()) {
|
|
917
|
+
case "domain":
|
|
918
|
+
this.domain = value;
|
|
919
|
+
break;
|
|
920
|
+
case "expires": {
|
|
921
|
+
if (typeof value === "string") {
|
|
922
|
+
let date = new Date(value);
|
|
923
|
+
if (isValidDate(date)) {
|
|
924
|
+
this.expires = date;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
case "httponly":
|
|
930
|
+
this.httpOnly = true;
|
|
931
|
+
break;
|
|
932
|
+
case "max-age": {
|
|
933
|
+
if (typeof value === "string") {
|
|
934
|
+
let v = parseInt(value, 10);
|
|
935
|
+
if (!isNaN(v)) this.maxAge = v;
|
|
936
|
+
}
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
case "path":
|
|
940
|
+
this.path = value;
|
|
941
|
+
break;
|
|
942
|
+
case "samesite":
|
|
943
|
+
if (typeof value === "string" && /strict|lax|none/i.test(value)) {
|
|
944
|
+
this.sameSite = capitalize(value);
|
|
945
|
+
}
|
|
946
|
+
break;
|
|
947
|
+
case "secure":
|
|
948
|
+
this.secure = true;
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
} else {
|
|
954
|
+
this.domain = init.domain;
|
|
955
|
+
this.expires = init.expires;
|
|
956
|
+
this.httpOnly = init.httpOnly;
|
|
957
|
+
this.maxAge = init.maxAge;
|
|
958
|
+
this.name = init.name;
|
|
959
|
+
this.path = init.path;
|
|
960
|
+
this.sameSite = init.sameSite;
|
|
961
|
+
this.secure = init.secure;
|
|
962
|
+
this.value = init.value;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
toString() {
|
|
967
|
+
if (!this.name) {
|
|
968
|
+
return "";
|
|
969
|
+
}
|
|
970
|
+
let parts = [`${this.name}=${quote(this.value || "")}`];
|
|
971
|
+
if (this.domain) {
|
|
972
|
+
parts.push(`Domain=${this.domain}`);
|
|
973
|
+
}
|
|
974
|
+
if (this.path) {
|
|
975
|
+
parts.push(`Path=${this.path}`);
|
|
976
|
+
}
|
|
977
|
+
if (this.expires) {
|
|
978
|
+
parts.push(`Expires=${this.expires.toUTCString()}`);
|
|
979
|
+
}
|
|
980
|
+
if (this.maxAge) {
|
|
981
|
+
parts.push(`Max-Age=${this.maxAge}`);
|
|
982
|
+
}
|
|
983
|
+
if (this.secure) {
|
|
984
|
+
parts.push("Secure");
|
|
985
|
+
}
|
|
986
|
+
if (this.httpOnly) {
|
|
987
|
+
parts.push("HttpOnly");
|
|
988
|
+
}
|
|
989
|
+
if (this.sameSite) {
|
|
990
|
+
parts.push(`SameSite=${this.sameSite}`);
|
|
991
|
+
}
|
|
992
|
+
return parts.join("; ");
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// ../headers/src/lib/header-names.ts
|
|
997
|
+
var HeaderWordCasingExceptions = {
|
|
998
|
+
ct: "CT",
|
|
999
|
+
etag: "ETag",
|
|
1000
|
+
te: "TE",
|
|
1001
|
+
www: "WWW",
|
|
1002
|
+
x: "X",
|
|
1003
|
+
xss: "XSS"
|
|
1004
|
+
};
|
|
1005
|
+
function canonicalHeaderName(name) {
|
|
1006
|
+
return name.toLowerCase().split("-").map((word) => HeaderWordCasingExceptions[word] || word.charAt(0).toUpperCase() + word.slice(1)).join("-");
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// ../headers/src/lib/super-headers.ts
|
|
1010
|
+
var CRLF = "\r\n";
|
|
1011
|
+
var AcceptKey = "accept";
|
|
1012
|
+
var AcceptEncodingKey = "accept-encoding";
|
|
1013
|
+
var AcceptLanguageKey = "accept-language";
|
|
1014
|
+
var AcceptRangesKey = "accept-ranges";
|
|
1015
|
+
var AgeKey = "age";
|
|
1016
|
+
var CacheControlKey = "cache-control";
|
|
1017
|
+
var ConnectionKey = "connection";
|
|
1018
|
+
var ContentDispositionKey = "content-disposition";
|
|
1019
|
+
var ContentEncodingKey = "content-encoding";
|
|
1020
|
+
var ContentLanguageKey = "content-language";
|
|
1021
|
+
var ContentLengthKey = "content-length";
|
|
1022
|
+
var ContentTypeKey = "content-type";
|
|
1023
|
+
var CookieKey = "cookie";
|
|
1024
|
+
var DateKey = "date";
|
|
1025
|
+
var ETagKey = "etag";
|
|
1026
|
+
var ExpiresKey = "expires";
|
|
1027
|
+
var HostKey = "host";
|
|
1028
|
+
var IfModifiedSinceKey = "if-modified-since";
|
|
1029
|
+
var IfNoneMatchKey = "if-none-match";
|
|
1030
|
+
var IfUnmodifiedSinceKey = "if-unmodified-since";
|
|
1031
|
+
var LastModifiedKey = "last-modified";
|
|
1032
|
+
var LocationKey = "location";
|
|
1033
|
+
var RefererKey = "referer";
|
|
1034
|
+
var SetCookieKey = "set-cookie";
|
|
1035
|
+
var SuperHeaders = class _SuperHeaders extends Headers {
|
|
1036
|
+
#map;
|
|
1037
|
+
#setCookies = [];
|
|
1038
|
+
constructor(init) {
|
|
1039
|
+
super();
|
|
1040
|
+
this.#map = /* @__PURE__ */ new Map();
|
|
1041
|
+
if (init) {
|
|
1042
|
+
if (typeof init === "string") {
|
|
1043
|
+
let lines = init.split(CRLF);
|
|
1044
|
+
for (let line of lines) {
|
|
1045
|
+
let match = line.match(/^([^:]+):(.*)/);
|
|
1046
|
+
if (match) {
|
|
1047
|
+
this.append(match[1].trim(), match[2].trim());
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
} else if (isIterable(init)) {
|
|
1051
|
+
for (let [name, value] of init) {
|
|
1052
|
+
this.append(name, value);
|
|
1053
|
+
}
|
|
1054
|
+
} else if (typeof init === "object") {
|
|
1055
|
+
for (let name of Object.getOwnPropertyNames(init)) {
|
|
1056
|
+
let value = init[name];
|
|
1057
|
+
let descriptor = Object.getOwnPropertyDescriptor(_SuperHeaders.prototype, name);
|
|
1058
|
+
if (descriptor?.set) {
|
|
1059
|
+
descriptor.set.call(this, value);
|
|
1060
|
+
} else {
|
|
1061
|
+
this.set(name, value.toString());
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Appends a new header value to the existing set of values for a header,
|
|
1069
|
+
* or adds the header if it does not already exist.
|
|
1070
|
+
*
|
|
1071
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/append)
|
|
1072
|
+
*/
|
|
1073
|
+
append(name, value) {
|
|
1074
|
+
let key = name.toLowerCase();
|
|
1075
|
+
if (key === SetCookieKey) {
|
|
1076
|
+
this.#setCookies.push(value);
|
|
1077
|
+
} else {
|
|
1078
|
+
let existingValue = this.#map.get(key);
|
|
1079
|
+
this.#map.set(key, existingValue ? `${existingValue}, ${value}` : value);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Removes a header.
|
|
1084
|
+
*
|
|
1085
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/delete)
|
|
1086
|
+
*/
|
|
1087
|
+
delete(name) {
|
|
1088
|
+
let key = name.toLowerCase();
|
|
1089
|
+
if (key === SetCookieKey) {
|
|
1090
|
+
this.#setCookies = [];
|
|
1091
|
+
} else {
|
|
1092
|
+
this.#map.delete(key);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Returns a string of all the values for a header, or `null` if the header does not exist.
|
|
1097
|
+
*
|
|
1098
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/get)
|
|
1099
|
+
*/
|
|
1100
|
+
get(name) {
|
|
1101
|
+
let key = name.toLowerCase();
|
|
1102
|
+
if (key === SetCookieKey) {
|
|
1103
|
+
return this.getSetCookie().join(", ");
|
|
1104
|
+
} else {
|
|
1105
|
+
let value = this.#map.get(key);
|
|
1106
|
+
if (typeof value === "string") {
|
|
1107
|
+
return value;
|
|
1108
|
+
} else if (value != null) {
|
|
1109
|
+
let str = value.toString();
|
|
1110
|
+
return str === "" ? null : str;
|
|
1111
|
+
} else {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Returns an array of all values associated with the `Set-Cookie` header. This is
|
|
1118
|
+
* useful when building headers for a HTTP response since multiple `Set-Cookie` headers
|
|
1119
|
+
* must be sent on separate lines.
|
|
1120
|
+
*
|
|
1121
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie)
|
|
1122
|
+
*/
|
|
1123
|
+
getSetCookie() {
|
|
1124
|
+
return this.#setCookies.map((v) => typeof v === "string" ? v : v.toString());
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Returns `true` if the header is present in the list of headers.
|
|
1128
|
+
*
|
|
1129
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/has)
|
|
1130
|
+
*/
|
|
1131
|
+
has(name) {
|
|
1132
|
+
let key = name.toLowerCase();
|
|
1133
|
+
return key === SetCookieKey ? this.#setCookies.length > 0 : this.get(key) != null;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Sets a new value for the given header. If the header already exists, the new value
|
|
1137
|
+
* will replace the existing value.
|
|
1138
|
+
*
|
|
1139
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/set)
|
|
1140
|
+
*/
|
|
1141
|
+
set(name, value) {
|
|
1142
|
+
let key = name.toLowerCase();
|
|
1143
|
+
if (key === SetCookieKey) {
|
|
1144
|
+
this.#setCookies = [value];
|
|
1145
|
+
} else {
|
|
1146
|
+
this.#map.set(key, value);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Returns an iterator of all header keys (lowercase).
|
|
1151
|
+
*
|
|
1152
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/keys)
|
|
1153
|
+
*/
|
|
1154
|
+
*keys() {
|
|
1155
|
+
for (let [key] of this) yield key;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Returns an iterator of all header values.
|
|
1159
|
+
*
|
|
1160
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/values)
|
|
1161
|
+
*/
|
|
1162
|
+
*values() {
|
|
1163
|
+
for (let [, value] of this) yield value;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Returns an iterator of all header key/value pairs.
|
|
1167
|
+
*
|
|
1168
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries)
|
|
1169
|
+
*/
|
|
1170
|
+
*entries() {
|
|
1171
|
+
for (let [key] of this.#map) {
|
|
1172
|
+
let str = this.get(key);
|
|
1173
|
+
if (str) yield [key, str];
|
|
1174
|
+
}
|
|
1175
|
+
for (let value of this.getSetCookie()) {
|
|
1176
|
+
yield [SetCookieKey, value];
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
[Symbol.iterator]() {
|
|
1180
|
+
return this.entries();
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Invokes the `callback` for each header key/value pair.
|
|
1184
|
+
*
|
|
1185
|
+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Headers/forEach)
|
|
1186
|
+
*/
|
|
1187
|
+
forEach(callback, thisArg) {
|
|
1188
|
+
for (let [key, value] of this) {
|
|
1189
|
+
callback.call(thisArg, value, key, this);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Returns a string representation of the headers suitable for use in a HTTP message.
|
|
1194
|
+
*/
|
|
1195
|
+
toString() {
|
|
1196
|
+
let lines = [];
|
|
1197
|
+
for (let [key, value] of this) {
|
|
1198
|
+
lines.push(`${canonicalHeaderName(key)}: ${value}`);
|
|
1199
|
+
}
|
|
1200
|
+
return lines.join(CRLF);
|
|
1201
|
+
}
|
|
1202
|
+
// Header-specific getters and setters
|
|
1203
|
+
/**
|
|
1204
|
+
* The `Accept` header is used by clients to indicate the media types that are acceptable
|
|
1205
|
+
* in the response.
|
|
1206
|
+
*
|
|
1207
|
+
* [MDN `Accept` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept)
|
|
1208
|
+
*
|
|
1209
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2)
|
|
1210
|
+
*/
|
|
1211
|
+
get accept() {
|
|
1212
|
+
return this.#getHeaderValue(AcceptKey, Accept);
|
|
1213
|
+
}
|
|
1214
|
+
set accept(value) {
|
|
1215
|
+
this.#setHeaderValue(AcceptKey, Accept, value);
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* The `Accept-Encoding` header contains information about the content encodings that the client
|
|
1219
|
+
* is willing to accept in the response.
|
|
1220
|
+
*
|
|
1221
|
+
* [MDN `Accept-Encoding` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding)
|
|
1222
|
+
*
|
|
1223
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4)
|
|
1224
|
+
*/
|
|
1225
|
+
get acceptEncoding() {
|
|
1226
|
+
return this.#getHeaderValue(AcceptEncodingKey, AcceptEncoding);
|
|
1227
|
+
}
|
|
1228
|
+
set acceptEncoding(value) {
|
|
1229
|
+
this.#setHeaderValue(AcceptEncodingKey, AcceptEncoding, value);
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* The `Accept-Language` header contains information about preferred natural language for the
|
|
1233
|
+
* response.
|
|
1234
|
+
*
|
|
1235
|
+
* [MDN `Accept-Language` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)
|
|
1236
|
+
*
|
|
1237
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5)
|
|
1238
|
+
*/
|
|
1239
|
+
get acceptLanguage() {
|
|
1240
|
+
return this.#getHeaderValue(AcceptLanguageKey, AcceptLanguage);
|
|
1241
|
+
}
|
|
1242
|
+
set acceptLanguage(value) {
|
|
1243
|
+
this.#setHeaderValue(AcceptLanguageKey, AcceptLanguage, value);
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* The `Accept-Ranges` header indicates the server's acceptance of range requests.
|
|
1247
|
+
*
|
|
1248
|
+
* [MDN `Accept-Ranges` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges)
|
|
1249
|
+
*
|
|
1250
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7233#section-2.3)
|
|
1251
|
+
*/
|
|
1252
|
+
get acceptRanges() {
|
|
1253
|
+
return this.#getStringValue(AcceptRangesKey);
|
|
1254
|
+
}
|
|
1255
|
+
set acceptRanges(value) {
|
|
1256
|
+
this.#setStringValue(AcceptRangesKey, value);
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* The `Age` header contains the time in seconds an object was in a proxy cache.
|
|
1260
|
+
*
|
|
1261
|
+
* [MDN `Age` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Age)
|
|
1262
|
+
*
|
|
1263
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7234#section-5.1)
|
|
1264
|
+
*/
|
|
1265
|
+
get age() {
|
|
1266
|
+
return this.#getNumberValue(AgeKey);
|
|
1267
|
+
}
|
|
1268
|
+
set age(value) {
|
|
1269
|
+
this.#setNumberValue(AgeKey, value);
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* The `Cache-Control` header contains directives for caching mechanisms in both requests and responses.
|
|
1273
|
+
*
|
|
1274
|
+
* [MDN `Cache-Control` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
|
|
1275
|
+
*
|
|
1276
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7234#section-5.2)
|
|
1277
|
+
*/
|
|
1278
|
+
get cacheControl() {
|
|
1279
|
+
return this.#getHeaderValue(CacheControlKey, CacheControl);
|
|
1280
|
+
}
|
|
1281
|
+
set cacheControl(value) {
|
|
1282
|
+
this.#setHeaderValue(CacheControlKey, CacheControl, value);
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* The `Connection` header controls whether the network connection stays open after the current
|
|
1286
|
+
* transaction finishes.
|
|
1287
|
+
*
|
|
1288
|
+
* [MDN `Connection` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection)
|
|
1289
|
+
*
|
|
1290
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7230#section-6.1)
|
|
1291
|
+
*/
|
|
1292
|
+
get connection() {
|
|
1293
|
+
return this.#getStringValue(ConnectionKey);
|
|
1294
|
+
}
|
|
1295
|
+
set connection(value) {
|
|
1296
|
+
this.#setStringValue(ConnectionKey, value);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* The `Content-Disposition` header is a response-type header that describes how the payload is displayed.
|
|
1300
|
+
*
|
|
1301
|
+
* [MDN `Content-Disposition` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition)
|
|
1302
|
+
*
|
|
1303
|
+
* [RFC 6266](https://datatracker.ietf.org/doc/html/rfc6266)
|
|
1304
|
+
*/
|
|
1305
|
+
get contentDisposition() {
|
|
1306
|
+
return this.#getHeaderValue(ContentDispositionKey, ContentDisposition);
|
|
1307
|
+
}
|
|
1308
|
+
set contentDisposition(value) {
|
|
1309
|
+
this.#setHeaderValue(ContentDispositionKey, ContentDisposition, value);
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* The `Content-Encoding` header specifies the encoding of the resource.
|
|
1313
|
+
*
|
|
1314
|
+
* Note: If multiple encodings have been used, this value may be a comma-separated list. However, most often this
|
|
1315
|
+
* header will only contain a single value.
|
|
1316
|
+
*
|
|
1317
|
+
* [MDN `Content-Encoding` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding)
|
|
1318
|
+
*
|
|
1319
|
+
* [HTTP/1.1 Specification](https://httpwg.org/specs/rfc9110.html#field.content-encoding)
|
|
1320
|
+
*/
|
|
1321
|
+
get contentEncoding() {
|
|
1322
|
+
return this.#getStringValue(ContentEncodingKey);
|
|
1323
|
+
}
|
|
1324
|
+
set contentEncoding(value) {
|
|
1325
|
+
this.#setStringValue(ContentEncodingKey, Array.isArray(value) ? value.join(", ") : value);
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* The `Content-Language` header describes the natural language(s) of the intended audience for the response content.
|
|
1329
|
+
*
|
|
1330
|
+
* Note: If the response content is intended for multiple audiences, this value may be a comma-separated list. However,
|
|
1331
|
+
* most often this header will only contain a single value.
|
|
1332
|
+
*
|
|
1333
|
+
* [MDN `Content-Language` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language)
|
|
1334
|
+
*
|
|
1335
|
+
* [HTTP/1.1 Specification](https://httpwg.org/specs/rfc9110.html#field.content-language)
|
|
1336
|
+
*/
|
|
1337
|
+
get contentLanguage() {
|
|
1338
|
+
return this.#getStringValue(ContentLanguageKey);
|
|
1339
|
+
}
|
|
1340
|
+
set contentLanguage(value) {
|
|
1341
|
+
this.#setStringValue(ContentLanguageKey, Array.isArray(value) ? value.join(", ") : value);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* The `Content-Length` header indicates the size of the entity-body in bytes.
|
|
1345
|
+
*
|
|
1346
|
+
* [MDN `Content-Length` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
1347
|
+
*
|
|
1348
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2)
|
|
1349
|
+
*/
|
|
1350
|
+
get contentLength() {
|
|
1351
|
+
return this.#getNumberValue(ContentLengthKey);
|
|
1352
|
+
}
|
|
1353
|
+
set contentLength(value) {
|
|
1354
|
+
this.#setNumberValue(ContentLengthKey, value);
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* The `Content-Type` header indicates the media type of the resource.
|
|
1358
|
+
*
|
|
1359
|
+
* [MDN `Content-Type` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
|
|
1360
|
+
*
|
|
1361
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5)
|
|
1362
|
+
*/
|
|
1363
|
+
get contentType() {
|
|
1364
|
+
return this.#getHeaderValue(ContentTypeKey, ContentType);
|
|
1365
|
+
}
|
|
1366
|
+
set contentType(value) {
|
|
1367
|
+
this.#setHeaderValue(ContentTypeKey, ContentType, value);
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* The `Cookie` request header contains stored HTTP cookies previously sent by the server with
|
|
1371
|
+
* the `Set-Cookie` header.
|
|
1372
|
+
*
|
|
1373
|
+
* [MDN `Cookie` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie)
|
|
1374
|
+
*
|
|
1375
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc6265#section-5.4)
|
|
1376
|
+
*/
|
|
1377
|
+
get cookie() {
|
|
1378
|
+
return this.#getHeaderValue(CookieKey, Cookie);
|
|
1379
|
+
}
|
|
1380
|
+
set cookie(value) {
|
|
1381
|
+
this.#setHeaderValue(CookieKey, Cookie, value);
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* The `Date` header contains the date and time at which the message was sent.
|
|
1385
|
+
*
|
|
1386
|
+
* [MDN `Date` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date)
|
|
1387
|
+
*
|
|
1388
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)
|
|
1389
|
+
*/
|
|
1390
|
+
get date() {
|
|
1391
|
+
return this.#getDateValue(DateKey);
|
|
1392
|
+
}
|
|
1393
|
+
set date(value) {
|
|
1394
|
+
this.#setDateValue(DateKey, value);
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* The `ETag` header provides a unique identifier for the current version of the resource.
|
|
1398
|
+
*
|
|
1399
|
+
* [MDN `ETag` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag)
|
|
1400
|
+
*
|
|
1401
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3)
|
|
1402
|
+
*/
|
|
1403
|
+
get etag() {
|
|
1404
|
+
return this.#getStringValue(ETagKey);
|
|
1405
|
+
}
|
|
1406
|
+
set etag(value) {
|
|
1407
|
+
this.#setStringValue(ETagKey, typeof value === "string" ? quoteEtag(value) : value);
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* The `Expires` header contains the date/time after which the response is considered stale.
|
|
1411
|
+
*
|
|
1412
|
+
* [MDN `Expires` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires)
|
|
1413
|
+
*
|
|
1414
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7234#section-5.3)
|
|
1415
|
+
*/
|
|
1416
|
+
get expires() {
|
|
1417
|
+
return this.#getDateValue(ExpiresKey);
|
|
1418
|
+
}
|
|
1419
|
+
set expires(value) {
|
|
1420
|
+
this.#setDateValue(ExpiresKey, value);
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* The `Host` header specifies the domain name of the server and (optionally) the TCP port number.
|
|
1424
|
+
*
|
|
1425
|
+
* [MDN `Host` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host)
|
|
1426
|
+
*
|
|
1427
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7230#section-5.4)
|
|
1428
|
+
*/
|
|
1429
|
+
get host() {
|
|
1430
|
+
return this.#getStringValue(HostKey);
|
|
1431
|
+
}
|
|
1432
|
+
set host(value) {
|
|
1433
|
+
this.#setStringValue(HostKey, value);
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* The `If-Modified-Since` header makes a request conditional on the last modification date of the
|
|
1437
|
+
* requested resource.
|
|
1438
|
+
*
|
|
1439
|
+
* [MDN `If-Modified-Since` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since)
|
|
1440
|
+
*
|
|
1441
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7232#section-3.3)
|
|
1442
|
+
*/
|
|
1443
|
+
get ifModifiedSince() {
|
|
1444
|
+
return this.#getDateValue(IfModifiedSinceKey);
|
|
1445
|
+
}
|
|
1446
|
+
set ifModifiedSince(value) {
|
|
1447
|
+
this.#setDateValue(IfModifiedSinceKey, value);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* The `If-None-Match` header makes a request conditional on the absence of a matching ETag.
|
|
1451
|
+
*
|
|
1452
|
+
* [MDN `If-None-Match` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
|
|
1453
|
+
*
|
|
1454
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7232#section-3.2)
|
|
1455
|
+
*/
|
|
1456
|
+
get ifNoneMatch() {
|
|
1457
|
+
return this.#getHeaderValue(IfNoneMatchKey, IfNoneMatch);
|
|
1458
|
+
}
|
|
1459
|
+
set ifNoneMatch(value) {
|
|
1460
|
+
this.#setHeaderValue(IfNoneMatchKey, IfNoneMatch, value);
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* The `If-Unmodified-Since` header makes a request conditional on the last modification date of the
|
|
1464
|
+
* requested resource.
|
|
1465
|
+
*
|
|
1466
|
+
* [MDN `If-Unmodified-Since` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since)
|
|
1467
|
+
*
|
|
1468
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4)
|
|
1469
|
+
*/
|
|
1470
|
+
get ifUnmodifiedSince() {
|
|
1471
|
+
return this.#getDateValue(IfUnmodifiedSinceKey);
|
|
1472
|
+
}
|
|
1473
|
+
set ifUnmodifiedSince(value) {
|
|
1474
|
+
this.#setDateValue(IfUnmodifiedSinceKey, value);
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* The `Last-Modified` header contains the date and time at which the resource was last modified.
|
|
1478
|
+
*
|
|
1479
|
+
* [MDN `Last-Modified` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified)
|
|
1480
|
+
*
|
|
1481
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2)
|
|
1482
|
+
*/
|
|
1483
|
+
get lastModified() {
|
|
1484
|
+
return this.#getDateValue(LastModifiedKey);
|
|
1485
|
+
}
|
|
1486
|
+
set lastModified(value) {
|
|
1487
|
+
this.#setDateValue(LastModifiedKey, value);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* The `Location` header indicates the URL to redirect to.
|
|
1491
|
+
*
|
|
1492
|
+
* [MDN `Location` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
|
|
1493
|
+
*
|
|
1494
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2)
|
|
1495
|
+
*/
|
|
1496
|
+
get location() {
|
|
1497
|
+
return this.#getStringValue(LocationKey);
|
|
1498
|
+
}
|
|
1499
|
+
set location(value) {
|
|
1500
|
+
this.#setStringValue(LocationKey, value);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* The `Referer` header contains the address of the previous web page from which a link to the
|
|
1504
|
+
* currently requested page was followed.
|
|
1505
|
+
*
|
|
1506
|
+
* [MDN `Referer` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer)
|
|
1507
|
+
*
|
|
1508
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.2)
|
|
1509
|
+
*/
|
|
1510
|
+
get referer() {
|
|
1511
|
+
return this.#getStringValue(RefererKey);
|
|
1512
|
+
}
|
|
1513
|
+
set referer(value) {
|
|
1514
|
+
this.#setStringValue(RefererKey, value);
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* The `Set-Cookie` header is used to send cookies from the server to the user agent.
|
|
1518
|
+
*
|
|
1519
|
+
* [MDN `Set-Cookie` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)
|
|
1520
|
+
*
|
|
1521
|
+
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1)
|
|
1522
|
+
*/
|
|
1523
|
+
get setCookie() {
|
|
1524
|
+
let setCookies = this.#setCookies;
|
|
1525
|
+
for (let i = 0; i < setCookies.length; ++i) {
|
|
1526
|
+
if (typeof setCookies[i] === "string") {
|
|
1527
|
+
setCookies[i] = new SetCookie(setCookies[i]);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return setCookies;
|
|
1531
|
+
}
|
|
1532
|
+
set setCookie(value) {
|
|
1533
|
+
if (value != null) {
|
|
1534
|
+
this.#setCookies = (Array.isArray(value) ? value : [value]).map(
|
|
1535
|
+
(v) => typeof v === "string" ? v : new SetCookie(v)
|
|
1536
|
+
);
|
|
1537
|
+
} else {
|
|
1538
|
+
this.#setCookies = [];
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
// Helpers
|
|
1542
|
+
#getHeaderValue(key, ctor) {
|
|
1543
|
+
let value = this.#map.get(key);
|
|
1544
|
+
if (value !== void 0) {
|
|
1545
|
+
if (typeof value === "string") {
|
|
1546
|
+
let obj2 = new ctor(value);
|
|
1547
|
+
this.#map.set(key, obj2);
|
|
1548
|
+
return obj2;
|
|
1549
|
+
} else {
|
|
1550
|
+
return value;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
let obj = new ctor();
|
|
1554
|
+
this.#map.set(key, obj);
|
|
1555
|
+
return obj;
|
|
1556
|
+
}
|
|
1557
|
+
#setHeaderValue(key, ctor, value) {
|
|
1558
|
+
if (value != null) {
|
|
1559
|
+
this.#map.set(key, typeof value === "string" ? value : new ctor(value));
|
|
1560
|
+
} else {
|
|
1561
|
+
this.#map.delete(key);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
#getDateValue(key) {
|
|
1565
|
+
let value = this.#map.get(key);
|
|
1566
|
+
return value === void 0 ? null : new Date(value);
|
|
1567
|
+
}
|
|
1568
|
+
#setDateValue(key, value) {
|
|
1569
|
+
if (value != null) {
|
|
1570
|
+
this.#map.set(
|
|
1571
|
+
key,
|
|
1572
|
+
typeof value === "string" ? value : (typeof value === "number" ? new Date(value) : value).toUTCString()
|
|
1573
|
+
);
|
|
1574
|
+
} else {
|
|
1575
|
+
this.#map.delete(key);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
#getNumberValue(key) {
|
|
1579
|
+
let value = this.#map.get(key);
|
|
1580
|
+
return value === void 0 ? null : parseInt(value, 10);
|
|
1581
|
+
}
|
|
1582
|
+
#setNumberValue(key, value) {
|
|
1583
|
+
if (value != null) {
|
|
1584
|
+
this.#map.set(key, typeof value === "string" ? value : value.toString());
|
|
1585
|
+
} else {
|
|
1586
|
+
this.#map.delete(key);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
#getStringValue(key) {
|
|
1590
|
+
let value = this.#map.get(key);
|
|
1591
|
+
return value === void 0 ? null : value;
|
|
1592
|
+
}
|
|
1593
|
+
#setStringValue(key, value) {
|
|
1594
|
+
if (value != null) {
|
|
1595
|
+
this.#map.set(key, value);
|
|
1596
|
+
} else {
|
|
1597
|
+
this.#map.delete(key);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
// src/lib/read-stream.ts
|
|
1603
|
+
async function* readStream(stream) {
|
|
1604
|
+
let reader = stream.getReader();
|
|
1605
|
+
try {
|
|
1606
|
+
while (true) {
|
|
1607
|
+
const { done, value } = await reader.read();
|
|
1608
|
+
if (done) break;
|
|
1609
|
+
yield value;
|
|
1610
|
+
}
|
|
1611
|
+
} finally {
|
|
1612
|
+
reader.releaseLock();
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/lib/buffer-search.ts
|
|
1617
|
+
function createSearch(pattern) {
|
|
1618
|
+
let needle = new TextEncoder().encode(pattern);
|
|
1619
|
+
let search;
|
|
1620
|
+
if ("Buffer" in globalThis && !("Bun" in globalThis || "Deno" in globalThis)) {
|
|
1621
|
+
search = (haystack, start = 0) => Buffer.prototype.indexOf.call(haystack, needle, start);
|
|
1622
|
+
} else {
|
|
1623
|
+
let needleEnd = needle.length - 1;
|
|
1624
|
+
let skipTable = new Uint8Array(256).fill(needle.length);
|
|
1625
|
+
for (let i = 0; i < needleEnd; ++i) {
|
|
1626
|
+
skipTable[needle[i]] = needleEnd - i;
|
|
1627
|
+
}
|
|
1628
|
+
search = (haystack, start = 0) => {
|
|
1629
|
+
let haystackLength = haystack.length;
|
|
1630
|
+
let i = start + needleEnd;
|
|
1631
|
+
while (i < haystackLength) {
|
|
1632
|
+
for (let j = needleEnd, k = i; j >= 0 && haystack[k] === needle[j]; --j, --k) {
|
|
1633
|
+
if (j === 0) return k;
|
|
1634
|
+
}
|
|
1635
|
+
i += skipTable[haystack[i]];
|
|
1636
|
+
}
|
|
1637
|
+
return -1;
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
return search;
|
|
1641
|
+
}
|
|
1642
|
+
function createPartialTailSearch(pattern) {
|
|
1643
|
+
let needle = new TextEncoder().encode(pattern);
|
|
1644
|
+
let byteIndexes = {};
|
|
1645
|
+
for (let i = 0; i < needle.length; ++i) {
|
|
1646
|
+
let byte = needle[i];
|
|
1647
|
+
if (byteIndexes[byte] === void 0) byteIndexes[byte] = [];
|
|
1648
|
+
byteIndexes[byte].push(i);
|
|
1649
|
+
}
|
|
1650
|
+
return function(haystack) {
|
|
1651
|
+
let haystackEnd = haystack.length - 1;
|
|
1652
|
+
if (haystack[haystackEnd] in byteIndexes) {
|
|
1653
|
+
let indexes = byteIndexes[haystack[haystackEnd]];
|
|
1654
|
+
for (let i = indexes.length - 1; i >= 0; --i) {
|
|
1655
|
+
for (let j = indexes[i], k = haystackEnd; j >= 0 && haystack[k] === needle[j]; --j, --k) {
|
|
1656
|
+
if (j === 0) return k;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return -1;
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// src/lib/multipart.ts
|
|
1665
|
+
var MultipartParseError = class extends Error {
|
|
1666
|
+
constructor(message) {
|
|
1667
|
+
super(message);
|
|
1668
|
+
this.name = "MultipartParseError";
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
var MaxHeaderSizeExceededError = class extends MultipartParseError {
|
|
1672
|
+
constructor(maxHeaderSize) {
|
|
1673
|
+
super(`Multipart header size exceeds maximum allowed size of ${maxHeaderSize} bytes`);
|
|
1674
|
+
this.name = "MaxHeaderSizeExceededError";
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
var MaxFileSizeExceededError = class extends MultipartParseError {
|
|
1678
|
+
constructor(maxFileSize) {
|
|
1679
|
+
super(`File size exceeds maximum allowed size of ${maxFileSize} bytes`);
|
|
1680
|
+
this.name = "MaxFileSizeExceededError";
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
function* parseMultipart(message, options) {
|
|
1684
|
+
let parser = new MultipartParser(options.boundary, {
|
|
1685
|
+
maxHeaderSize: options.maxHeaderSize,
|
|
1686
|
+
maxFileSize: options.maxFileSize
|
|
1687
|
+
});
|
|
1688
|
+
if (message instanceof Uint8Array) {
|
|
1689
|
+
if (message.length === 0) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
yield* parser.write(message);
|
|
1693
|
+
} else {
|
|
1694
|
+
for (let chunk of message) {
|
|
1695
|
+
yield* parser.write(chunk);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
parser.finish();
|
|
1699
|
+
}
|
|
1700
|
+
async function* parseMultipartStream(stream, options) {
|
|
1701
|
+
let parser = new MultipartParser(options.boundary, {
|
|
1702
|
+
maxHeaderSize: options.maxHeaderSize,
|
|
1703
|
+
maxFileSize: options.maxFileSize
|
|
1704
|
+
});
|
|
1705
|
+
for await (let chunk of readStream(stream)) {
|
|
1706
|
+
if (chunk.length === 0) {
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
yield* parser.write(chunk);
|
|
1710
|
+
}
|
|
1711
|
+
parser.finish();
|
|
1712
|
+
}
|
|
1713
|
+
var MultipartParserStateStart = 0;
|
|
1714
|
+
var MultipartParserStateAfterBoundary = 1;
|
|
1715
|
+
var MultipartParserStateHeader = 2;
|
|
1716
|
+
var MultipartParserStateBody = 3;
|
|
1717
|
+
var MultipartParserStateDone = 4;
|
|
1718
|
+
var findDoubleNewline = createSearch("\r\n\r\n");
|
|
1719
|
+
var oneKb = 1024;
|
|
1720
|
+
var oneMb = 1024 * oneKb;
|
|
1721
|
+
var MultipartParser = class {
|
|
1722
|
+
boundary;
|
|
1723
|
+
maxHeaderSize;
|
|
1724
|
+
maxFileSize;
|
|
1725
|
+
#findOpeningBoundary;
|
|
1726
|
+
#openingBoundaryLength;
|
|
1727
|
+
#findBoundary;
|
|
1728
|
+
#findPartialTailBoundary;
|
|
1729
|
+
#boundaryLength;
|
|
1730
|
+
#state = MultipartParserStateStart;
|
|
1731
|
+
#buffer = null;
|
|
1732
|
+
#currentPart = null;
|
|
1733
|
+
#contentLength = 0;
|
|
1734
|
+
constructor(boundary, options) {
|
|
1735
|
+
this.boundary = boundary;
|
|
1736
|
+
this.maxHeaderSize = options?.maxHeaderSize ?? 8 * oneKb;
|
|
1737
|
+
this.maxFileSize = options?.maxFileSize ?? 2 * oneMb;
|
|
1738
|
+
this.#findOpeningBoundary = createSearch(`--${boundary}`);
|
|
1739
|
+
this.#openingBoundaryLength = 2 + boundary.length;
|
|
1740
|
+
this.#findBoundary = createSearch(`\r
|
|
1741
|
+
--${boundary}`);
|
|
1742
|
+
this.#findPartialTailBoundary = createPartialTailSearch(`\r
|
|
1743
|
+
--${boundary}`);
|
|
1744
|
+
this.#boundaryLength = 4 + boundary.length;
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Write a chunk of data to the parser.
|
|
1748
|
+
*
|
|
1749
|
+
* @param chunk A chunk of data to write to the parser
|
|
1750
|
+
* @return A generator yielding `MultipartPart` objects as they are parsed
|
|
1751
|
+
*/
|
|
1752
|
+
*write(chunk) {
|
|
1753
|
+
if (this.#state === MultipartParserStateDone) {
|
|
1754
|
+
throw new MultipartParseError("Unexpected data after end of stream");
|
|
1755
|
+
}
|
|
1756
|
+
let index = 0;
|
|
1757
|
+
let chunkLength = chunk.length;
|
|
1758
|
+
if (this.#buffer !== null) {
|
|
1759
|
+
let newChunk = new Uint8Array(this.#buffer.length + chunkLength);
|
|
1760
|
+
newChunk.set(this.#buffer, 0);
|
|
1761
|
+
newChunk.set(chunk, this.#buffer.length);
|
|
1762
|
+
chunk = newChunk;
|
|
1763
|
+
chunkLength = chunk.length;
|
|
1764
|
+
this.#buffer = null;
|
|
1765
|
+
}
|
|
1766
|
+
while (true) {
|
|
1767
|
+
if (this.#state === MultipartParserStateBody) {
|
|
1768
|
+
if (chunkLength - index < this.#boundaryLength) {
|
|
1769
|
+
this.#buffer = chunk.subarray(index);
|
|
1770
|
+
break;
|
|
1771
|
+
}
|
|
1772
|
+
let boundaryIndex = this.#findBoundary(chunk, index);
|
|
1773
|
+
if (boundaryIndex === -1) {
|
|
1774
|
+
let partialTailIndex = this.#findPartialTailBoundary(chunk);
|
|
1775
|
+
if (partialTailIndex === -1) {
|
|
1776
|
+
this.#append(index === 0 ? chunk : chunk.subarray(index));
|
|
1777
|
+
} else {
|
|
1778
|
+
this.#append(chunk.subarray(index, partialTailIndex));
|
|
1779
|
+
this.#buffer = chunk.subarray(partialTailIndex);
|
|
1780
|
+
}
|
|
1781
|
+
break;
|
|
1782
|
+
}
|
|
1783
|
+
this.#append(chunk.subarray(index, boundaryIndex));
|
|
1784
|
+
yield this.#currentPart;
|
|
1785
|
+
index = boundaryIndex + this.#boundaryLength;
|
|
1786
|
+
this.#state = MultipartParserStateAfterBoundary;
|
|
1787
|
+
}
|
|
1788
|
+
if (this.#state === MultipartParserStateAfterBoundary) {
|
|
1789
|
+
if (chunkLength - index < 2) {
|
|
1790
|
+
this.#buffer = chunk.subarray(index);
|
|
1791
|
+
break;
|
|
1792
|
+
}
|
|
1793
|
+
if (chunk[index] === 45 && chunk[index + 1] === 45) {
|
|
1794
|
+
this.#state = MultipartParserStateDone;
|
|
1795
|
+
break;
|
|
1796
|
+
}
|
|
1797
|
+
index += 2;
|
|
1798
|
+
this.#state = MultipartParserStateHeader;
|
|
1799
|
+
}
|
|
1800
|
+
if (this.#state === MultipartParserStateHeader) {
|
|
1801
|
+
if (chunkLength - index < 4) {
|
|
1802
|
+
this.#buffer = chunk.subarray(index);
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
let headerEndIndex = findDoubleNewline(chunk, index);
|
|
1806
|
+
if (headerEndIndex === -1) {
|
|
1807
|
+
if (chunkLength - index > this.maxHeaderSize) {
|
|
1808
|
+
throw new MaxHeaderSizeExceededError(this.maxHeaderSize);
|
|
1809
|
+
}
|
|
1810
|
+
this.#buffer = chunk.subarray(index);
|
|
1811
|
+
break;
|
|
1812
|
+
}
|
|
1813
|
+
if (headerEndIndex - index > this.maxHeaderSize) {
|
|
1814
|
+
throw new MaxHeaderSizeExceededError(this.maxHeaderSize);
|
|
1815
|
+
}
|
|
1816
|
+
this.#currentPart = new MultipartPart(chunk.subarray(index, headerEndIndex), []);
|
|
1817
|
+
this.#contentLength = 0;
|
|
1818
|
+
index = headerEndIndex + 4;
|
|
1819
|
+
this.#state = MultipartParserStateBody;
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
if (this.#state === MultipartParserStateStart) {
|
|
1823
|
+
if (chunkLength < this.#openingBoundaryLength) {
|
|
1824
|
+
this.#buffer = chunk;
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
if (this.#findOpeningBoundary(chunk) !== 0) {
|
|
1828
|
+
throw new MultipartParseError("Invalid multipart stream: missing initial boundary");
|
|
1829
|
+
}
|
|
1830
|
+
index = this.#openingBoundaryLength;
|
|
1831
|
+
this.#state = MultipartParserStateAfterBoundary;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
#append(chunk) {
|
|
1836
|
+
if (this.#contentLength + chunk.length > this.maxFileSize) {
|
|
1837
|
+
throw new MaxFileSizeExceededError(this.maxFileSize);
|
|
1838
|
+
}
|
|
1839
|
+
this.#currentPart.content.push(chunk);
|
|
1840
|
+
this.#contentLength += chunk.length;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Should be called after all data has been written to the parser.
|
|
1844
|
+
*
|
|
1845
|
+
* Note: This will throw if the multipart message is incomplete or
|
|
1846
|
+
* wasn't properly terminated.
|
|
1847
|
+
*
|
|
1848
|
+
* @return void
|
|
1849
|
+
*/
|
|
1850
|
+
finish() {
|
|
1851
|
+
if (this.#state !== MultipartParserStateDone) {
|
|
1852
|
+
throw new MultipartParseError("Multipart stream not finished");
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1857
|
+
var MultipartPart = class {
|
|
1858
|
+
/**
|
|
1859
|
+
* The raw content of this part as an array of `Uint8Array` chunks.
|
|
1860
|
+
*/
|
|
1861
|
+
content;
|
|
1862
|
+
#header;
|
|
1863
|
+
#headers;
|
|
1864
|
+
constructor(header, content) {
|
|
1865
|
+
this.#header = header;
|
|
1866
|
+
this.content = content;
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* The content of this part as an `ArrayBuffer`.
|
|
1870
|
+
*/
|
|
1871
|
+
get arrayBuffer() {
|
|
1872
|
+
return this.bytes.buffer;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* The content of this part as a single `Uint8Array`. In `multipart/form-data` messages, this is useful
|
|
1876
|
+
* for reading the value of files that were uploaded using `<input type="file">` fields.
|
|
1877
|
+
*/
|
|
1878
|
+
get bytes() {
|
|
1879
|
+
let buffer = new Uint8Array(this.size);
|
|
1880
|
+
let offset = 0;
|
|
1881
|
+
for (let chunk of this.content) {
|
|
1882
|
+
buffer.set(chunk, offset);
|
|
1883
|
+
offset += chunk.length;
|
|
1884
|
+
}
|
|
1885
|
+
return buffer;
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* The headers associated with this part.
|
|
1889
|
+
*/
|
|
1890
|
+
get headers() {
|
|
1891
|
+
if (!this.#headers) {
|
|
1892
|
+
this.#headers = new SuperHeaders(decoder.decode(this.#header));
|
|
1893
|
+
}
|
|
1894
|
+
return this.#headers;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* True if this part originated from a file upload.
|
|
1898
|
+
*/
|
|
1899
|
+
get isFile() {
|
|
1900
|
+
return this.filename !== void 0 || this.mediaType === "application/octet-stream";
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* True if this part originated from a text input field in a form submission.
|
|
1904
|
+
*/
|
|
1905
|
+
get isText() {
|
|
1906
|
+
return !this.isFile;
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* The filename of the part, if it is a file upload.
|
|
1910
|
+
*/
|
|
1911
|
+
get filename() {
|
|
1912
|
+
return this.headers.contentDisposition.preferredFilename;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* The media type of the part.
|
|
1916
|
+
*/
|
|
1917
|
+
get mediaType() {
|
|
1918
|
+
return this.headers.contentType.mediaType;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* The name of the part, usually the `name` of the field in the `<form>` that submitted the request.
|
|
1922
|
+
*/
|
|
1923
|
+
get name() {
|
|
1924
|
+
return this.headers.contentDisposition.name;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* The size of the content in bytes.
|
|
1928
|
+
*/
|
|
1929
|
+
get size() {
|
|
1930
|
+
let size = 0;
|
|
1931
|
+
for (let chunk of this.content) {
|
|
1932
|
+
size += chunk.length;
|
|
1933
|
+
}
|
|
1934
|
+
return size;
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* The content of this part as a string. In `multipart/form-data` messages, this is useful for
|
|
1938
|
+
* reading the value of parts that originated from `<input type="text">` fields.
|
|
1939
|
+
*
|
|
1940
|
+
* Note: Do not use this for binary data, use `part.bytes` or `part.arrayBuffer` instead.
|
|
1941
|
+
*/
|
|
1942
|
+
get text() {
|
|
1943
|
+
return decoder.decode(this.bytes);
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1947
|
+
// src/lib/multipart-request.ts
|
|
1948
|
+
function getMultipartBoundary(contentType) {
|
|
1949
|
+
let match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType);
|
|
1950
|
+
return match ? match[1] ?? match[2] : null;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// src/lib/multipart.node.ts
|
|
1954
|
+
import { Readable } from "node:stream";
|
|
1955
|
+
function* parseMultipart2(message, options) {
|
|
1956
|
+
yield* parseMultipart(message, options);
|
|
1957
|
+
}
|
|
1958
|
+
async function* parseMultipartStream2(stream, options) {
|
|
1959
|
+
yield* parseMultipartStream(Readable.toWeb(stream), options);
|
|
1960
|
+
}
|
|
1961
|
+
function isMultipartRequest(req) {
|
|
1962
|
+
let contentType = req.headers["content-type"];
|
|
1963
|
+
return contentType != null && /^multipart\//i.test(contentType);
|
|
1964
|
+
}
|
|
1965
|
+
async function* parseMultipartRequest(req, options) {
|
|
1966
|
+
if (!isMultipartRequest(req)) {
|
|
1967
|
+
throw new MultipartParseError("Request is not a multipart request");
|
|
1968
|
+
}
|
|
1969
|
+
let boundary = getMultipartBoundary(req.headers["content-type"]);
|
|
1970
|
+
if (!boundary) {
|
|
1971
|
+
throw new MultipartParseError("Invalid Content-Type header: missing boundary");
|
|
1972
|
+
}
|
|
1973
|
+
yield* parseMultipartStream2(req, {
|
|
1974
|
+
boundary,
|
|
1975
|
+
maxHeaderSize: options?.maxHeaderSize,
|
|
1976
|
+
maxFileSize: options?.maxFileSize
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
export {
|
|
1980
|
+
MaxFileSizeExceededError,
|
|
1981
|
+
MaxHeaderSizeExceededError,
|
|
1982
|
+
MultipartParseError,
|
|
1983
|
+
MultipartParser,
|
|
1984
|
+
MultipartPart,
|
|
1985
|
+
getMultipartBoundary,
|
|
1986
|
+
isMultipartRequest,
|
|
1987
|
+
parseMultipart2 as parseMultipart,
|
|
1988
|
+
parseMultipartRequest,
|
|
1989
|
+
parseMultipartStream2 as parseMultipartStream
|
|
1990
|
+
};
|
|
1991
|
+
//# sourceMappingURL=multipart-parser.node.js.map
|