@better-media/plugin-validation 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.
package/dist/index.mjs ADDED
@@ -0,0 +1,2949 @@
1
+ import fs from 'fs/promises';
2
+ import crypto, { randomUUID } from 'crypto';
3
+ import { markFileContentVerified, EXTENSION_TO_MIME_MAP } from '@better-media/core';
4
+ import 'stream/web';
5
+ import 'stream';
6
+ import path from 'path';
7
+ import { imageSize } from 'image-size';
8
+
9
+ var __create = Object.create;
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __getProtoOf = Object.getPrototypeOf;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __commonJS = (cb, mod) => function __require() {
16
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ __defProp(target, "default", { value: mod, enumerable: true }) ,
32
+ mod
33
+ ));
34
+
35
+ // ../../../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
36
+ var require_ieee754 = __commonJS({
37
+ "../../../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js"(exports$1) {
38
+ exports$1.read = function(buffer, offset, isLE, mLen, nBytes) {
39
+ var e, m;
40
+ var eLen = nBytes * 8 - mLen - 1;
41
+ var eMax = (1 << eLen) - 1;
42
+ var eBias = eMax >> 1;
43
+ var nBits = -7;
44
+ var i = isLE ? nBytes - 1 : 0;
45
+ var d = isLE ? -1 : 1;
46
+ var s = buffer[offset + i];
47
+ i += d;
48
+ e = s & (1 << -nBits) - 1;
49
+ s >>= -nBits;
50
+ nBits += eLen;
51
+ for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {
52
+ }
53
+ m = e & (1 << -nBits) - 1;
54
+ e >>= -nBits;
55
+ nBits += mLen;
56
+ for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {
57
+ }
58
+ if (e === 0) {
59
+ e = 1 - eBias;
60
+ } else if (e === eMax) {
61
+ return m ? NaN : (s ? -1 : 1) * Infinity;
62
+ } else {
63
+ m = m + Math.pow(2, mLen);
64
+ e = e - eBias;
65
+ }
66
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
67
+ };
68
+ exports$1.write = function(buffer, value, offset, isLE, mLen, nBytes) {
69
+ var e, m, c;
70
+ var eLen = nBytes * 8 - mLen - 1;
71
+ var eMax = (1 << eLen) - 1;
72
+ var eBias = eMax >> 1;
73
+ var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0;
74
+ var i = isLE ? 0 : nBytes - 1;
75
+ var d = isLE ? 1 : -1;
76
+ var s = value < 0 || value === 0 && 1 / value < 0 ? 1 : 0;
77
+ value = Math.abs(value);
78
+ if (isNaN(value) || value === Infinity) {
79
+ m = isNaN(value) ? 1 : 0;
80
+ e = eMax;
81
+ } else {
82
+ e = Math.floor(Math.log(value) / Math.LN2);
83
+ if (value * (c = Math.pow(2, -e)) < 1) {
84
+ e--;
85
+ c *= 2;
86
+ }
87
+ if (e + eBias >= 1) {
88
+ value += rt / c;
89
+ } else {
90
+ value += rt * Math.pow(2, 1 - eBias);
91
+ }
92
+ if (value * c >= 2) {
93
+ e++;
94
+ c /= 2;
95
+ }
96
+ if (e + eBias >= eMax) {
97
+ m = 0;
98
+ e = eMax;
99
+ } else if (e + eBias >= 1) {
100
+ m = (value * c - 1) * Math.pow(2, mLen);
101
+ e = e + eBias;
102
+ } else {
103
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
104
+ e = 0;
105
+ }
106
+ }
107
+ for (; mLen >= 8; buffer[offset + i] = m & 255, i += d, m /= 256, mLen -= 8) {
108
+ }
109
+ e = e << mLen | m;
110
+ eLen += mLen;
111
+ for (; eLen > 0; buffer[offset + i] = e & 255, i += d, e /= 256, eLen -= 8) {
112
+ }
113
+ buffer[offset + i - d] |= s * 128;
114
+ };
115
+ }
116
+ });
117
+
118
+ // ../../../node_modules/.pnpm/peek-readable@5.4.2/node_modules/peek-readable/lib/EndOfStreamError.js
119
+ var defaultMessages = "End-Of-Stream";
120
+ var EndOfStreamError = class extends Error {
121
+ constructor() {
122
+ super(defaultMessages);
123
+ }
124
+ };
125
+
126
+ // ../../../node_modules/.pnpm/peek-readable@5.4.2/node_modules/peek-readable/lib/AbstractStreamReader.js
127
+ var AbstractStreamReader = class {
128
+ constructor() {
129
+ this.maxStreamReadSize = 1 * 1024 * 1024;
130
+ this.endOfStream = false;
131
+ this.peekQueue = [];
132
+ }
133
+ async peek(uint8Array, offset, length) {
134
+ const bytesRead = await this.read(uint8Array, offset, length);
135
+ this.peekQueue.push(uint8Array.subarray(offset, offset + bytesRead));
136
+ return bytesRead;
137
+ }
138
+ async read(buffer, offset, length) {
139
+ if (length === 0) {
140
+ return 0;
141
+ }
142
+ let bytesRead = this.readFromPeekBuffer(buffer, offset, length);
143
+ bytesRead += await this.readRemainderFromStream(buffer, offset + bytesRead, length - bytesRead);
144
+ if (bytesRead === 0) {
145
+ throw new EndOfStreamError();
146
+ }
147
+ return bytesRead;
148
+ }
149
+ /**
150
+ * Read chunk from stream
151
+ * @param buffer - Target Uint8Array (or Buffer) to store data read from stream in
152
+ * @param offset - Offset target
153
+ * @param length - Number of bytes to read
154
+ * @returns Number of bytes read
155
+ */
156
+ readFromPeekBuffer(buffer, offset, length) {
157
+ let remaining = length;
158
+ let bytesRead = 0;
159
+ while (this.peekQueue.length > 0 && remaining > 0) {
160
+ const peekData = this.peekQueue.pop();
161
+ if (!peekData)
162
+ throw new Error("peekData should be defined");
163
+ const lenCopy = Math.min(peekData.length, remaining);
164
+ buffer.set(peekData.subarray(0, lenCopy), offset + bytesRead);
165
+ bytesRead += lenCopy;
166
+ remaining -= lenCopy;
167
+ if (lenCopy < peekData.length) {
168
+ this.peekQueue.push(peekData.subarray(lenCopy));
169
+ }
170
+ }
171
+ return bytesRead;
172
+ }
173
+ async readRemainderFromStream(buffer, offset, initialRemaining) {
174
+ let remaining = initialRemaining;
175
+ let bytesRead = 0;
176
+ while (remaining > 0 && !this.endOfStream) {
177
+ const reqLen = Math.min(remaining, this.maxStreamReadSize);
178
+ const chunkLen = await this.readFromStream(buffer, offset + bytesRead, reqLen);
179
+ if (chunkLen === 0)
180
+ break;
181
+ bytesRead += chunkLen;
182
+ remaining -= chunkLen;
183
+ }
184
+ return bytesRead;
185
+ }
186
+ };
187
+
188
+ // ../../../node_modules/.pnpm/peek-readable@5.4.2/node_modules/peek-readable/lib/WebStreamReader.js
189
+ var WebStreamReader = class extends AbstractStreamReader {
190
+ constructor(stream) {
191
+ super();
192
+ this.reader = stream.getReader({ mode: "byob" });
193
+ }
194
+ async readFromStream(buffer, offset, length) {
195
+ if (this.endOfStream) {
196
+ throw new EndOfStreamError();
197
+ }
198
+ const result = await this.reader.read(new Uint8Array(length));
199
+ if (result.done) {
200
+ this.endOfStream = result.done;
201
+ }
202
+ if (result.value) {
203
+ buffer.set(result.value, offset);
204
+ return result.value.byteLength;
205
+ }
206
+ return 0;
207
+ }
208
+ abort() {
209
+ return this.reader.cancel();
210
+ }
211
+ async close() {
212
+ await this.abort();
213
+ this.reader.releaseLock();
214
+ }
215
+ };
216
+
217
+ // ../../../node_modules/.pnpm/strtok3@9.1.1/node_modules/strtok3/lib/AbstractTokenizer.js
218
+ var AbstractTokenizer = class {
219
+ /**
220
+ * Constructor
221
+ * @param options Tokenizer options
222
+ * @protected
223
+ */
224
+ constructor(options) {
225
+ this.numBuffer = new Uint8Array(8);
226
+ this.position = 0;
227
+ this.onClose = options?.onClose;
228
+ if (options?.abortSignal) {
229
+ options.abortSignal.addEventListener("abort", () => {
230
+ this.abort();
231
+ });
232
+ }
233
+ }
234
+ /**
235
+ * Read a token from the tokenizer-stream
236
+ * @param token - The token to read
237
+ * @param position - If provided, the desired position in the tokenizer-stream
238
+ * @returns Promise with token data
239
+ */
240
+ async readToken(token, position = this.position) {
241
+ const uint8Array = new Uint8Array(token.len);
242
+ const len = await this.readBuffer(uint8Array, { position });
243
+ if (len < token.len)
244
+ throw new EndOfStreamError();
245
+ return token.get(uint8Array, 0);
246
+ }
247
+ /**
248
+ * Peek a token from the tokenizer-stream.
249
+ * @param token - Token to peek from the tokenizer-stream.
250
+ * @param position - Offset where to begin reading within the file. If position is null, data will be read from the current file position.
251
+ * @returns Promise with token data
252
+ */
253
+ async peekToken(token, position = this.position) {
254
+ const uint8Array = new Uint8Array(token.len);
255
+ const len = await this.peekBuffer(uint8Array, { position });
256
+ if (len < token.len)
257
+ throw new EndOfStreamError();
258
+ return token.get(uint8Array, 0);
259
+ }
260
+ /**
261
+ * Read a numeric token from the stream
262
+ * @param token - Numeric token
263
+ * @returns Promise with number
264
+ */
265
+ async readNumber(token) {
266
+ const len = await this.readBuffer(this.numBuffer, { length: token.len });
267
+ if (len < token.len)
268
+ throw new EndOfStreamError();
269
+ return token.get(this.numBuffer, 0);
270
+ }
271
+ /**
272
+ * Read a numeric token from the stream
273
+ * @param token - Numeric token
274
+ * @returns Promise with number
275
+ */
276
+ async peekNumber(token) {
277
+ const len = await this.peekBuffer(this.numBuffer, { length: token.len });
278
+ if (len < token.len)
279
+ throw new EndOfStreamError();
280
+ return token.get(this.numBuffer, 0);
281
+ }
282
+ /**
283
+ * Ignore number of bytes, advances the pointer in under tokenizer-stream.
284
+ * @param length - Number of bytes to ignore
285
+ * @return resolves the number of bytes ignored, equals length if this available, otherwise the number of bytes available
286
+ */
287
+ async ignore(length) {
288
+ if (this.fileInfo.size !== void 0) {
289
+ const bytesLeft = this.fileInfo.size - this.position;
290
+ if (length > bytesLeft) {
291
+ this.position += bytesLeft;
292
+ return bytesLeft;
293
+ }
294
+ }
295
+ this.position += length;
296
+ return length;
297
+ }
298
+ async close() {
299
+ await this.abort();
300
+ await this.onClose?.();
301
+ }
302
+ normalizeOptions(uint8Array, options) {
303
+ if (options && options.position !== void 0 && options.position < this.position) {
304
+ throw new Error("`options.position` must be equal or greater than `tokenizer.position`");
305
+ }
306
+ if (options) {
307
+ return {
308
+ mayBeLess: options.mayBeLess === true,
309
+ offset: options.offset ? options.offset : 0,
310
+ length: options.length ? options.length : uint8Array.length - (options.offset ? options.offset : 0),
311
+ position: options.position ? options.position : this.position
312
+ };
313
+ }
314
+ return {
315
+ mayBeLess: false,
316
+ offset: 0,
317
+ length: uint8Array.length,
318
+ position: this.position
319
+ };
320
+ }
321
+ abort() {
322
+ return Promise.resolve();
323
+ }
324
+ };
325
+
326
+ // ../../../node_modules/.pnpm/strtok3@9.1.1/node_modules/strtok3/lib/ReadStreamTokenizer.js
327
+ var maxBufferSize = 256e3;
328
+ var ReadStreamTokenizer = class extends AbstractTokenizer {
329
+ /**
330
+ * Constructor
331
+ * @param streamReader stream-reader to read from
332
+ * @param options Tokenizer options
333
+ */
334
+ constructor(streamReader, options) {
335
+ super(options);
336
+ this.streamReader = streamReader;
337
+ this.fileInfo = options?.fileInfo ?? {};
338
+ }
339
+ /**
340
+ * Read buffer from tokenizer
341
+ * @param uint8Array - Target Uint8Array to fill with data read from the tokenizer-stream
342
+ * @param options - Read behaviour options
343
+ * @returns Promise with number of bytes read
344
+ */
345
+ async readBuffer(uint8Array, options) {
346
+ const normOptions = this.normalizeOptions(uint8Array, options);
347
+ const skipBytes = normOptions.position - this.position;
348
+ if (skipBytes > 0) {
349
+ await this.ignore(skipBytes);
350
+ return this.readBuffer(uint8Array, options);
351
+ }
352
+ if (skipBytes < 0) {
353
+ throw new Error("`options.position` must be equal or greater than `tokenizer.position`");
354
+ }
355
+ if (normOptions.length === 0) {
356
+ return 0;
357
+ }
358
+ const bytesRead = await this.streamReader.read(uint8Array, normOptions.offset, normOptions.length);
359
+ this.position += bytesRead;
360
+ if ((!options || !options.mayBeLess) && bytesRead < normOptions.length) {
361
+ throw new EndOfStreamError();
362
+ }
363
+ return bytesRead;
364
+ }
365
+ /**
366
+ * Peek (read ahead) buffer from tokenizer
367
+ * @param uint8Array - Uint8Array (or Buffer) to write data to
368
+ * @param options - Read behaviour options
369
+ * @returns Promise with number of bytes peeked
370
+ */
371
+ async peekBuffer(uint8Array, options) {
372
+ const normOptions = this.normalizeOptions(uint8Array, options);
373
+ let bytesRead = 0;
374
+ if (normOptions.position) {
375
+ const skipBytes = normOptions.position - this.position;
376
+ if (skipBytes > 0) {
377
+ const skipBuffer = new Uint8Array(normOptions.length + skipBytes);
378
+ bytesRead = await this.peekBuffer(skipBuffer, { mayBeLess: normOptions.mayBeLess });
379
+ uint8Array.set(skipBuffer.subarray(skipBytes), normOptions.offset);
380
+ return bytesRead - skipBytes;
381
+ }
382
+ if (skipBytes < 0) {
383
+ throw new Error("Cannot peek from a negative offset in a stream");
384
+ }
385
+ }
386
+ if (normOptions.length > 0) {
387
+ try {
388
+ bytesRead = await this.streamReader.peek(uint8Array, normOptions.offset, normOptions.length);
389
+ } catch (err) {
390
+ if (options?.mayBeLess && err instanceof EndOfStreamError) {
391
+ return 0;
392
+ }
393
+ throw err;
394
+ }
395
+ if (!normOptions.mayBeLess && bytesRead < normOptions.length) {
396
+ throw new EndOfStreamError();
397
+ }
398
+ }
399
+ return bytesRead;
400
+ }
401
+ async ignore(length) {
402
+ const bufSize = Math.min(maxBufferSize, length);
403
+ const buf = new Uint8Array(bufSize);
404
+ let totBytesRead = 0;
405
+ while (totBytesRead < length) {
406
+ const remaining = length - totBytesRead;
407
+ const bytesRead = await this.readBuffer(buf, { length: Math.min(bufSize, remaining) });
408
+ if (bytesRead < 0) {
409
+ return bytesRead;
410
+ }
411
+ totBytesRead += bytesRead;
412
+ }
413
+ return totBytesRead;
414
+ }
415
+ abort() {
416
+ return this.streamReader.abort();
417
+ }
418
+ supportsRandomAccess() {
419
+ return false;
420
+ }
421
+ };
422
+
423
+ // ../../../node_modules/.pnpm/strtok3@9.1.1/node_modules/strtok3/lib/BufferTokenizer.js
424
+ var BufferTokenizer = class extends AbstractTokenizer {
425
+ /**
426
+ * Construct BufferTokenizer
427
+ * @param uint8Array - Uint8Array to tokenize
428
+ * @param options Tokenizer options
429
+ */
430
+ constructor(uint8Array, options) {
431
+ super(options);
432
+ this.uint8Array = uint8Array;
433
+ this.fileInfo = { ...options?.fileInfo ?? {}, ...{ size: uint8Array.length } };
434
+ }
435
+ /**
436
+ * Read buffer from tokenizer
437
+ * @param uint8Array - Uint8Array to tokenize
438
+ * @param options - Read behaviour options
439
+ * @returns {Promise<number>}
440
+ */
441
+ async readBuffer(uint8Array, options) {
442
+ if (options?.position) {
443
+ if (options.position < this.position) {
444
+ throw new Error("`options.position` must be equal or greater than `tokenizer.position`");
445
+ }
446
+ this.position = options.position;
447
+ }
448
+ const bytesRead = await this.peekBuffer(uint8Array, options);
449
+ this.position += bytesRead;
450
+ return bytesRead;
451
+ }
452
+ /**
453
+ * Peek (read ahead) buffer from tokenizer
454
+ * @param uint8Array
455
+ * @param options - Read behaviour options
456
+ * @returns {Promise<number>}
457
+ */
458
+ async peekBuffer(uint8Array, options) {
459
+ const normOptions = this.normalizeOptions(uint8Array, options);
460
+ const bytes2read = Math.min(this.uint8Array.length - normOptions.position, normOptions.length);
461
+ if (!normOptions.mayBeLess && bytes2read < normOptions.length) {
462
+ throw new EndOfStreamError();
463
+ }
464
+ uint8Array.set(this.uint8Array.subarray(normOptions.position, normOptions.position + bytes2read), normOptions.offset);
465
+ return bytes2read;
466
+ }
467
+ close() {
468
+ return super.close();
469
+ }
470
+ supportsRandomAccess() {
471
+ return true;
472
+ }
473
+ setPosition(position) {
474
+ this.position = position;
475
+ }
476
+ };
477
+
478
+ // ../../../node_modules/.pnpm/strtok3@9.1.1/node_modules/strtok3/lib/core.js
479
+ function fromWebStream(webStream, options) {
480
+ return new ReadStreamTokenizer(new WebStreamReader(webStream), options);
481
+ }
482
+ function fromBuffer(uint8Array, options) {
483
+ return new BufferTokenizer(uint8Array, options);
484
+ }
485
+
486
+ // ../../../node_modules/.pnpm/token-types@6.1.2/node_modules/token-types/lib/index.js
487
+ __toESM(require_ieee754());
488
+
489
+ // ../../../node_modules/.pnpm/@borewit+text-codec@0.2.2/node_modules/@borewit/text-codec/lib/index.js
490
+ var WINDOWS_1252_EXTRA = {
491
+ 128: "\u20AC",
492
+ 130: "\u201A",
493
+ 131: "\u0192",
494
+ 132: "\u201E",
495
+ 133: "\u2026",
496
+ 134: "\u2020",
497
+ 135: "\u2021",
498
+ 136: "\u02C6",
499
+ 137: "\u2030",
500
+ 138: "\u0160",
501
+ 139: "\u2039",
502
+ 140: "\u0152",
503
+ 142: "\u017D",
504
+ 145: "\u2018",
505
+ 146: "\u2019",
506
+ 147: "\u201C",
507
+ 148: "\u201D",
508
+ 149: "\u2022",
509
+ 150: "\u2013",
510
+ 151: "\u2014",
511
+ 152: "\u02DC",
512
+ 153: "\u2122",
513
+ 154: "\u0161",
514
+ 155: "\u203A",
515
+ 156: "\u0153",
516
+ 158: "\u017E",
517
+ 159: "\u0178"
518
+ };
519
+ for (const [code, char] of Object.entries(WINDOWS_1252_EXTRA)) {
520
+ }
521
+ var _utf8Decoder;
522
+ function utf8Decoder() {
523
+ if (typeof globalThis.TextDecoder === "undefined")
524
+ return void 0;
525
+ return _utf8Decoder !== null && _utf8Decoder !== void 0 ? _utf8Decoder : _utf8Decoder = new globalThis.TextDecoder("utf-8");
526
+ }
527
+ var CHUNK = 32 * 1024;
528
+ var REPLACEMENT = 65533;
529
+ function textDecode(bytes, encoding = "utf-8") {
530
+ switch (encoding.toLowerCase()) {
531
+ case "utf-8":
532
+ case "utf8": {
533
+ const dec = utf8Decoder();
534
+ return dec ? dec.decode(bytes) : decodeUTF8(bytes);
535
+ }
536
+ case "utf-16le":
537
+ return decodeUTF16LE(bytes);
538
+ case "us-ascii":
539
+ case "ascii":
540
+ return decodeASCII(bytes);
541
+ case "latin1":
542
+ case "iso-8859-1":
543
+ return decodeLatin1(bytes);
544
+ case "windows-1252":
545
+ return decodeWindows1252(bytes);
546
+ default:
547
+ throw new RangeError(`Encoding '${encoding}' not supported`);
548
+ }
549
+ }
550
+ function flushChunk(parts, chunk) {
551
+ if (chunk.length === 0)
552
+ return;
553
+ parts.push(String.fromCharCode.apply(null, chunk));
554
+ chunk.length = 0;
555
+ }
556
+ function pushCodeUnit(parts, chunk, codeUnit) {
557
+ chunk.push(codeUnit);
558
+ if (chunk.length >= CHUNK)
559
+ flushChunk(parts, chunk);
560
+ }
561
+ function pushCodePoint(parts, chunk, cp) {
562
+ if (cp <= 65535) {
563
+ pushCodeUnit(parts, chunk, cp);
564
+ return;
565
+ }
566
+ cp -= 65536;
567
+ pushCodeUnit(parts, chunk, 55296 + (cp >> 10));
568
+ pushCodeUnit(parts, chunk, 56320 + (cp & 1023));
569
+ }
570
+ function decodeUTF8(bytes) {
571
+ const parts = [];
572
+ const chunk = [];
573
+ let i = 0;
574
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
575
+ i = 3;
576
+ }
577
+ while (i < bytes.length) {
578
+ const b1 = bytes[i];
579
+ if (b1 <= 127) {
580
+ pushCodeUnit(parts, chunk, b1);
581
+ i++;
582
+ continue;
583
+ }
584
+ if (b1 < 194 || b1 > 244) {
585
+ pushCodeUnit(parts, chunk, REPLACEMENT);
586
+ i++;
587
+ continue;
588
+ }
589
+ if (b1 <= 223) {
590
+ if (i + 1 >= bytes.length) {
591
+ pushCodeUnit(parts, chunk, REPLACEMENT);
592
+ i++;
593
+ continue;
594
+ }
595
+ const b22 = bytes[i + 1];
596
+ if ((b22 & 192) !== 128) {
597
+ pushCodeUnit(parts, chunk, REPLACEMENT);
598
+ i++;
599
+ continue;
600
+ }
601
+ const cp2 = (b1 & 31) << 6 | b22 & 63;
602
+ pushCodeUnit(parts, chunk, cp2);
603
+ i += 2;
604
+ continue;
605
+ }
606
+ if (b1 <= 239) {
607
+ if (i + 2 >= bytes.length) {
608
+ pushCodeUnit(parts, chunk, REPLACEMENT);
609
+ i++;
610
+ continue;
611
+ }
612
+ const b22 = bytes[i + 1];
613
+ const b32 = bytes[i + 2];
614
+ const valid2 = (b22 & 192) === 128 && (b32 & 192) === 128 && !(b1 === 224 && b22 < 160) && // overlong
615
+ !(b1 === 237 && b22 >= 160);
616
+ if (!valid2) {
617
+ pushCodeUnit(parts, chunk, REPLACEMENT);
618
+ i++;
619
+ continue;
620
+ }
621
+ const cp2 = (b1 & 15) << 12 | (b22 & 63) << 6 | b32 & 63;
622
+ pushCodeUnit(parts, chunk, cp2);
623
+ i += 3;
624
+ continue;
625
+ }
626
+ if (i + 3 >= bytes.length) {
627
+ pushCodeUnit(parts, chunk, REPLACEMENT);
628
+ i++;
629
+ continue;
630
+ }
631
+ const b2 = bytes[i + 1];
632
+ const b3 = bytes[i + 2];
633
+ const b4 = bytes[i + 3];
634
+ const valid = (b2 & 192) === 128 && (b3 & 192) === 128 && (b4 & 192) === 128 && !(b1 === 240 && b2 < 144) && // overlong
635
+ !(b1 === 244 && b2 > 143);
636
+ if (!valid) {
637
+ pushCodeUnit(parts, chunk, REPLACEMENT);
638
+ i++;
639
+ continue;
640
+ }
641
+ const cp = (b1 & 7) << 18 | (b2 & 63) << 12 | (b3 & 63) << 6 | b4 & 63;
642
+ pushCodePoint(parts, chunk, cp);
643
+ i += 4;
644
+ }
645
+ flushChunk(parts, chunk);
646
+ return parts.join("");
647
+ }
648
+ function decodeUTF16LE(bytes) {
649
+ const parts = [];
650
+ const chunk = [];
651
+ const len = bytes.length;
652
+ let i = 0;
653
+ while (i + 1 < len) {
654
+ const u1 = bytes[i] | bytes[i + 1] << 8;
655
+ i += 2;
656
+ if (u1 >= 55296 && u1 <= 56319) {
657
+ if (i + 1 < len) {
658
+ const u2 = bytes[i] | bytes[i + 1] << 8;
659
+ if (u2 >= 56320 && u2 <= 57343) {
660
+ pushCodeUnit(parts, chunk, u1);
661
+ pushCodeUnit(parts, chunk, u2);
662
+ i += 2;
663
+ } else {
664
+ pushCodeUnit(parts, chunk, REPLACEMENT);
665
+ }
666
+ } else {
667
+ pushCodeUnit(parts, chunk, REPLACEMENT);
668
+ }
669
+ continue;
670
+ }
671
+ if (u1 >= 56320 && u1 <= 57343) {
672
+ pushCodeUnit(parts, chunk, REPLACEMENT);
673
+ continue;
674
+ }
675
+ pushCodeUnit(parts, chunk, u1);
676
+ }
677
+ if (i < len) {
678
+ pushCodeUnit(parts, chunk, REPLACEMENT);
679
+ }
680
+ flushChunk(parts, chunk);
681
+ return parts.join("");
682
+ }
683
+ function decodeASCII(bytes) {
684
+ const parts = [];
685
+ for (let i = 0; i < bytes.length; i += CHUNK) {
686
+ const end = Math.min(bytes.length, i + CHUNK);
687
+ const codes = new Array(end - i);
688
+ for (let j = i, k = 0; j < end; j++, k++) {
689
+ codes[k] = bytes[j] & 127;
690
+ }
691
+ parts.push(String.fromCharCode.apply(null, codes));
692
+ }
693
+ return parts.join("");
694
+ }
695
+ function decodeLatin1(bytes) {
696
+ const parts = [];
697
+ for (let i = 0; i < bytes.length; i += CHUNK) {
698
+ const end = Math.min(bytes.length, i + CHUNK);
699
+ const codes = new Array(end - i);
700
+ for (let j = i, k = 0; j < end; j++, k++) {
701
+ codes[k] = bytes[j];
702
+ }
703
+ parts.push(String.fromCharCode.apply(null, codes));
704
+ }
705
+ return parts.join("");
706
+ }
707
+ function decodeWindows1252(bytes) {
708
+ const parts = [];
709
+ let out = "";
710
+ for (let i = 0; i < bytes.length; i++) {
711
+ const b = bytes[i];
712
+ const extra = b >= 128 && b <= 159 ? WINDOWS_1252_EXTRA[b] : void 0;
713
+ out += extra !== null && extra !== void 0 ? extra : String.fromCharCode(b);
714
+ if (out.length >= CHUNK) {
715
+ parts.push(out);
716
+ out = "";
717
+ }
718
+ }
719
+ if (out)
720
+ parts.push(out);
721
+ return parts.join("");
722
+ }
723
+
724
+ // ../../../node_modules/.pnpm/token-types@6.1.2/node_modules/token-types/lib/index.js
725
+ function dv(array) {
726
+ return new DataView(array.buffer, array.byteOffset);
727
+ }
728
+ var UINT8 = {
729
+ len: 1,
730
+ get(array, offset) {
731
+ return dv(array).getUint8(offset);
732
+ },
733
+ put(array, offset, value) {
734
+ dv(array).setUint8(offset, value);
735
+ return offset + 1;
736
+ }
737
+ };
738
+ var UINT16_LE = {
739
+ len: 2,
740
+ get(array, offset) {
741
+ return dv(array).getUint16(offset, true);
742
+ },
743
+ put(array, offset, value) {
744
+ dv(array).setUint16(offset, value, true);
745
+ return offset + 2;
746
+ }
747
+ };
748
+ var UINT16_BE = {
749
+ len: 2,
750
+ get(array, offset) {
751
+ return dv(array).getUint16(offset);
752
+ },
753
+ put(array, offset, value) {
754
+ dv(array).setUint16(offset, value);
755
+ return offset + 2;
756
+ }
757
+ };
758
+ var UINT32_LE = {
759
+ len: 4,
760
+ get(array, offset) {
761
+ return dv(array).getUint32(offset, true);
762
+ },
763
+ put(array, offset, value) {
764
+ dv(array).setUint32(offset, value, true);
765
+ return offset + 4;
766
+ }
767
+ };
768
+ var UINT32_BE = {
769
+ len: 4,
770
+ get(array, offset) {
771
+ return dv(array).getUint32(offset);
772
+ },
773
+ put(array, offset, value) {
774
+ dv(array).setUint32(offset, value);
775
+ return offset + 4;
776
+ }
777
+ };
778
+ var INT32_BE = {
779
+ len: 4,
780
+ get(array, offset) {
781
+ return dv(array).getInt32(offset);
782
+ },
783
+ put(array, offset, value) {
784
+ dv(array).setInt32(offset, value);
785
+ return offset + 4;
786
+ }
787
+ };
788
+ var UINT64_LE = {
789
+ len: 8,
790
+ get(array, offset) {
791
+ return dv(array).getBigUint64(offset, true);
792
+ },
793
+ put(array, offset, value) {
794
+ dv(array).setBigUint64(offset, value, true);
795
+ return offset + 8;
796
+ }
797
+ };
798
+ var StringType = class {
799
+ constructor(len, encoding) {
800
+ this.len = len;
801
+ this.encoding = encoding;
802
+ }
803
+ get(data, offset = 0) {
804
+ const bytes = data.subarray(offset, offset + this.len);
805
+ return textDecode(bytes, this.encoding);
806
+ }
807
+ };
808
+
809
+ // ../../../node_modules/.pnpm/uint8array-extras@1.5.0/node_modules/uint8array-extras/index.js
810
+ ({
811
+ utf8: new globalThis.TextDecoder("utf8")
812
+ });
813
+ new globalThis.TextEncoder();
814
+ Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, "0"));
815
+ function getUintBE(view) {
816
+ const { byteLength } = view;
817
+ if (byteLength === 6) {
818
+ return view.getUint16(0) * 2 ** 32 + view.getUint32(2);
819
+ }
820
+ if (byteLength === 5) {
821
+ return view.getUint8(0) * 2 ** 32 + view.getUint32(1);
822
+ }
823
+ if (byteLength === 4) {
824
+ return view.getUint32(0);
825
+ }
826
+ if (byteLength === 3) {
827
+ return view.getUint8(0) * 2 ** 16 + view.getUint16(1);
828
+ }
829
+ if (byteLength === 2) {
830
+ return view.getUint16(0);
831
+ }
832
+ if (byteLength === 1) {
833
+ return view.getUint8(0);
834
+ }
835
+ }
836
+ function indexOf(array, value) {
837
+ const arrayLength = array.length;
838
+ const valueLength = value.length;
839
+ if (valueLength === 0) {
840
+ return -1;
841
+ }
842
+ if (valueLength > arrayLength) {
843
+ return -1;
844
+ }
845
+ const validOffsetLength = arrayLength - valueLength;
846
+ for (let index = 0; index <= validOffsetLength; index++) {
847
+ let isMatch = true;
848
+ for (let index2 = 0; index2 < valueLength; index2++) {
849
+ if (array[index + index2] !== value[index2]) {
850
+ isMatch = false;
851
+ break;
852
+ }
853
+ }
854
+ if (isMatch) {
855
+ return index;
856
+ }
857
+ }
858
+ return -1;
859
+ }
860
+ function includes(array, value) {
861
+ return indexOf(array, value) !== -1;
862
+ }
863
+
864
+ // ../../../node_modules/.pnpm/file-type@19.6.0/node_modules/file-type/util.js
865
+ function stringToBytes(string) {
866
+ return [...string].map((character) => character.charCodeAt(0));
867
+ }
868
+ function tarHeaderChecksumMatches(arrayBuffer, offset = 0) {
869
+ const readSum = Number.parseInt(new StringType(6).get(arrayBuffer, 148).replace(/\0.*$/, "").trim(), 8);
870
+ if (Number.isNaN(readSum)) {
871
+ return false;
872
+ }
873
+ let sum = 8 * 32;
874
+ for (let index = offset; index < offset + 148; index++) {
875
+ sum += arrayBuffer[index];
876
+ }
877
+ for (let index = offset + 156; index < offset + 512; index++) {
878
+ sum += arrayBuffer[index];
879
+ }
880
+ return readSum === sum;
881
+ }
882
+ var uint32SyncSafeToken = {
883
+ get: (buffer, offset) => buffer[offset + 3] & 127 | buffer[offset + 2] << 7 | buffer[offset + 1] << 14 | buffer[offset] << 21,
884
+ len: 4
885
+ };
886
+
887
+ // ../../../node_modules/.pnpm/file-type@19.6.0/node_modules/file-type/supported.js
888
+ var extensions = [
889
+ "jpg",
890
+ "png",
891
+ "apng",
892
+ "gif",
893
+ "webp",
894
+ "flif",
895
+ "xcf",
896
+ "cr2",
897
+ "cr3",
898
+ "orf",
899
+ "arw",
900
+ "dng",
901
+ "nef",
902
+ "rw2",
903
+ "raf",
904
+ "tif",
905
+ "bmp",
906
+ "icns",
907
+ "jxr",
908
+ "psd",
909
+ "indd",
910
+ "zip",
911
+ "tar",
912
+ "rar",
913
+ "gz",
914
+ "bz2",
915
+ "7z",
916
+ "dmg",
917
+ "mp4",
918
+ "mid",
919
+ "mkv",
920
+ "webm",
921
+ "mov",
922
+ "avi",
923
+ "mpg",
924
+ "mp2",
925
+ "mp3",
926
+ "m4a",
927
+ "oga",
928
+ "ogg",
929
+ "ogv",
930
+ "opus",
931
+ "flac",
932
+ "wav",
933
+ "spx",
934
+ "amr",
935
+ "pdf",
936
+ "epub",
937
+ "elf",
938
+ "macho",
939
+ "exe",
940
+ "swf",
941
+ "rtf",
942
+ "wasm",
943
+ "woff",
944
+ "woff2",
945
+ "eot",
946
+ "ttf",
947
+ "otf",
948
+ "ico",
949
+ "flv",
950
+ "ps",
951
+ "xz",
952
+ "sqlite",
953
+ "nes",
954
+ "crx",
955
+ "xpi",
956
+ "cab",
957
+ "deb",
958
+ "ar",
959
+ "rpm",
960
+ "Z",
961
+ "lz",
962
+ "cfb",
963
+ "mxf",
964
+ "mts",
965
+ "blend",
966
+ "bpg",
967
+ "docx",
968
+ "pptx",
969
+ "xlsx",
970
+ "3gp",
971
+ "3g2",
972
+ "j2c",
973
+ "jp2",
974
+ "jpm",
975
+ "jpx",
976
+ "mj2",
977
+ "aif",
978
+ "qcp",
979
+ "odt",
980
+ "ods",
981
+ "odp",
982
+ "xml",
983
+ "mobi",
984
+ "heic",
985
+ "cur",
986
+ "ktx",
987
+ "ape",
988
+ "wv",
989
+ "dcm",
990
+ "ics",
991
+ "glb",
992
+ "pcap",
993
+ "dsf",
994
+ "lnk",
995
+ "alias",
996
+ "voc",
997
+ "ac3",
998
+ "m4v",
999
+ "m4p",
1000
+ "m4b",
1001
+ "f4v",
1002
+ "f4p",
1003
+ "f4b",
1004
+ "f4a",
1005
+ "mie",
1006
+ "asf",
1007
+ "ogm",
1008
+ "ogx",
1009
+ "mpc",
1010
+ "arrow",
1011
+ "shp",
1012
+ "aac",
1013
+ "mp1",
1014
+ "it",
1015
+ "s3m",
1016
+ "xm",
1017
+ "ai",
1018
+ "skp",
1019
+ "avif",
1020
+ "eps",
1021
+ "lzh",
1022
+ "pgp",
1023
+ "asar",
1024
+ "stl",
1025
+ "chm",
1026
+ "3mf",
1027
+ "zst",
1028
+ "jxl",
1029
+ "vcf",
1030
+ "jls",
1031
+ "pst",
1032
+ "dwg",
1033
+ "parquet",
1034
+ "class",
1035
+ "arj",
1036
+ "cpio",
1037
+ "ace",
1038
+ "avro",
1039
+ "icc",
1040
+ "fbx",
1041
+ "vsdx",
1042
+ "vtt",
1043
+ "apk"
1044
+ ];
1045
+ var mimeTypes = [
1046
+ "image/jpeg",
1047
+ "image/png",
1048
+ "image/gif",
1049
+ "image/webp",
1050
+ "image/flif",
1051
+ "image/x-xcf",
1052
+ "image/x-canon-cr2",
1053
+ "image/x-canon-cr3",
1054
+ "image/tiff",
1055
+ "image/bmp",
1056
+ "image/vnd.ms-photo",
1057
+ "image/vnd.adobe.photoshop",
1058
+ "application/x-indesign",
1059
+ "application/epub+zip",
1060
+ "application/x-xpinstall",
1061
+ "application/vnd.oasis.opendocument.text",
1062
+ "application/vnd.oasis.opendocument.spreadsheet",
1063
+ "application/vnd.oasis.opendocument.presentation",
1064
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1065
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1066
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1067
+ "application/zip",
1068
+ "application/x-tar",
1069
+ "application/x-rar-compressed",
1070
+ "application/gzip",
1071
+ "application/x-bzip2",
1072
+ "application/x-7z-compressed",
1073
+ "application/x-apple-diskimage",
1074
+ "application/x-apache-arrow",
1075
+ "video/mp4",
1076
+ "audio/midi",
1077
+ "video/x-matroska",
1078
+ "video/webm",
1079
+ "video/quicktime",
1080
+ "video/vnd.avi",
1081
+ "audio/wav",
1082
+ "audio/qcelp",
1083
+ "audio/x-ms-asf",
1084
+ "video/x-ms-asf",
1085
+ "application/vnd.ms-asf",
1086
+ "video/mpeg",
1087
+ "video/3gpp",
1088
+ "audio/mpeg",
1089
+ "audio/mp4",
1090
+ // RFC 4337
1091
+ "video/ogg",
1092
+ "audio/ogg",
1093
+ "audio/ogg; codecs=opus",
1094
+ "application/ogg",
1095
+ "audio/x-flac",
1096
+ "audio/ape",
1097
+ "audio/wavpack",
1098
+ "audio/amr",
1099
+ "application/pdf",
1100
+ "application/x-elf",
1101
+ "application/x-mach-binary",
1102
+ "application/x-msdownload",
1103
+ "application/x-shockwave-flash",
1104
+ "application/rtf",
1105
+ "application/wasm",
1106
+ "font/woff",
1107
+ "font/woff2",
1108
+ "application/vnd.ms-fontobject",
1109
+ "font/ttf",
1110
+ "font/otf",
1111
+ "image/x-icon",
1112
+ "video/x-flv",
1113
+ "application/postscript",
1114
+ "application/eps",
1115
+ "application/x-xz",
1116
+ "application/x-sqlite3",
1117
+ "application/x-nintendo-nes-rom",
1118
+ "application/x-google-chrome-extension",
1119
+ "application/vnd.ms-cab-compressed",
1120
+ "application/x-deb",
1121
+ "application/x-unix-archive",
1122
+ "application/x-rpm",
1123
+ "application/x-compress",
1124
+ "application/x-lzip",
1125
+ "application/x-cfb",
1126
+ "application/x-mie",
1127
+ "application/mxf",
1128
+ "video/mp2t",
1129
+ "application/x-blender",
1130
+ "image/bpg",
1131
+ "image/j2c",
1132
+ "image/jp2",
1133
+ "image/jpx",
1134
+ "image/jpm",
1135
+ "image/mj2",
1136
+ "audio/aiff",
1137
+ "application/xml",
1138
+ "application/x-mobipocket-ebook",
1139
+ "image/heif",
1140
+ "image/heif-sequence",
1141
+ "image/heic",
1142
+ "image/heic-sequence",
1143
+ "image/icns",
1144
+ "image/ktx",
1145
+ "application/dicom",
1146
+ "audio/x-musepack",
1147
+ "text/calendar",
1148
+ "text/vcard",
1149
+ "text/vtt",
1150
+ "model/gltf-binary",
1151
+ "application/vnd.tcpdump.pcap",
1152
+ "audio/x-dsf",
1153
+ // Non-standard
1154
+ "application/x.ms.shortcut",
1155
+ // Invented by us
1156
+ "application/x.apple.alias",
1157
+ // Invented by us
1158
+ "audio/x-voc",
1159
+ "audio/vnd.dolby.dd-raw",
1160
+ "audio/x-m4a",
1161
+ "image/apng",
1162
+ "image/x-olympus-orf",
1163
+ "image/x-sony-arw",
1164
+ "image/x-adobe-dng",
1165
+ "image/x-nikon-nef",
1166
+ "image/x-panasonic-rw2",
1167
+ "image/x-fujifilm-raf",
1168
+ "video/x-m4v",
1169
+ "video/3gpp2",
1170
+ "application/x-esri-shape",
1171
+ "audio/aac",
1172
+ "audio/x-it",
1173
+ "audio/x-s3m",
1174
+ "audio/x-xm",
1175
+ "video/MP1S",
1176
+ "video/MP2P",
1177
+ "application/vnd.sketchup.skp",
1178
+ "image/avif",
1179
+ "application/x-lzh-compressed",
1180
+ "application/pgp-encrypted",
1181
+ "application/x-asar",
1182
+ "model/stl",
1183
+ "application/vnd.ms-htmlhelp",
1184
+ "model/3mf",
1185
+ "image/jxl",
1186
+ "application/zstd",
1187
+ "image/jls",
1188
+ "application/vnd.ms-outlook",
1189
+ "image/vnd.dwg",
1190
+ "application/x-parquet",
1191
+ "application/java-vm",
1192
+ "application/x-arj",
1193
+ "application/x-cpio",
1194
+ "application/x-ace-compressed",
1195
+ "application/avro",
1196
+ "application/vnd.iccprofile",
1197
+ "application/x.autodesk.fbx",
1198
+ // Invented by us
1199
+ "application/vnd.visio",
1200
+ "application/vnd.android.package-archive"
1201
+ ];
1202
+
1203
+ // ../../../node_modules/.pnpm/file-type@19.6.0/node_modules/file-type/core.js
1204
+ var reasonableDetectionSizeInBytes = 4100;
1205
+ async function fileTypeFromBuffer(input) {
1206
+ return new FileTypeParser().fromBuffer(input);
1207
+ }
1208
+ function _check(buffer, headers, options) {
1209
+ options = {
1210
+ offset: 0,
1211
+ ...options
1212
+ };
1213
+ for (const [index, header] of headers.entries()) {
1214
+ if (options.mask) {
1215
+ if (header !== (options.mask[index] & buffer[index + options.offset])) {
1216
+ return false;
1217
+ }
1218
+ } else if (header !== buffer[index + options.offset]) {
1219
+ return false;
1220
+ }
1221
+ }
1222
+ return true;
1223
+ }
1224
+ var FileTypeParser = class {
1225
+ constructor(options) {
1226
+ this.detectors = options?.customDetectors;
1227
+ this.tokenizerOptions = {
1228
+ abortSignal: options?.signal
1229
+ };
1230
+ this.fromTokenizer = this.fromTokenizer.bind(this);
1231
+ this.fromBuffer = this.fromBuffer.bind(this);
1232
+ this.parse = this.parse.bind(this);
1233
+ }
1234
+ async fromTokenizer(tokenizer) {
1235
+ const initialPosition = tokenizer.position;
1236
+ for (const detector of this.detectors || []) {
1237
+ const fileType = await detector(tokenizer);
1238
+ if (fileType) {
1239
+ return fileType;
1240
+ }
1241
+ if (initialPosition !== tokenizer.position) {
1242
+ return void 0;
1243
+ }
1244
+ }
1245
+ return this.parse(tokenizer);
1246
+ }
1247
+ async fromBuffer(input) {
1248
+ if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) {
1249
+ throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof input}\``);
1250
+ }
1251
+ const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
1252
+ if (!(buffer?.length > 1)) {
1253
+ return;
1254
+ }
1255
+ return this.fromTokenizer(fromBuffer(buffer, this.tokenizerOptions));
1256
+ }
1257
+ async fromBlob(blob) {
1258
+ return this.fromStream(blob.stream());
1259
+ }
1260
+ async fromStream(stream) {
1261
+ const tokenizer = await fromWebStream(stream, this.tokenizerOptions);
1262
+ try {
1263
+ return await this.fromTokenizer(tokenizer);
1264
+ } finally {
1265
+ await tokenizer.close();
1266
+ }
1267
+ }
1268
+ async toDetectionStream(stream, options) {
1269
+ const { sampleSize = reasonableDetectionSizeInBytes } = options;
1270
+ let detectedFileType;
1271
+ let firstChunk;
1272
+ const reader = stream.getReader({ mode: "byob" });
1273
+ try {
1274
+ const { value: chunk, done } = await reader.read(new Uint8Array(sampleSize));
1275
+ firstChunk = chunk;
1276
+ if (!done && chunk) {
1277
+ try {
1278
+ detectedFileType = await this.fromBuffer(chunk.slice(0, sampleSize));
1279
+ } catch (error) {
1280
+ if (!(error instanceof EndOfStreamError)) {
1281
+ throw error;
1282
+ }
1283
+ detectedFileType = void 0;
1284
+ }
1285
+ }
1286
+ firstChunk = chunk;
1287
+ } finally {
1288
+ reader.releaseLock();
1289
+ }
1290
+ const transformStream = new TransformStream({
1291
+ async start(controller) {
1292
+ controller.enqueue(firstChunk);
1293
+ },
1294
+ transform(chunk, controller) {
1295
+ controller.enqueue(chunk);
1296
+ }
1297
+ });
1298
+ const newStream = stream.pipeThrough(transformStream);
1299
+ newStream.fileType = detectedFileType;
1300
+ return newStream;
1301
+ }
1302
+ check(header, options) {
1303
+ return _check(this.buffer, header, options);
1304
+ }
1305
+ checkString(header, options) {
1306
+ return this.check(stringToBytes(header), options);
1307
+ }
1308
+ async parse(tokenizer) {
1309
+ this.buffer = new Uint8Array(reasonableDetectionSizeInBytes);
1310
+ if (tokenizer.fileInfo.size === void 0) {
1311
+ tokenizer.fileInfo.size = Number.MAX_SAFE_INTEGER;
1312
+ }
1313
+ this.tokenizer = tokenizer;
1314
+ await tokenizer.peekBuffer(this.buffer, { length: 12, mayBeLess: true });
1315
+ if (this.check([66, 77])) {
1316
+ return {
1317
+ ext: "bmp",
1318
+ mime: "image/bmp"
1319
+ };
1320
+ }
1321
+ if (this.check([11, 119])) {
1322
+ return {
1323
+ ext: "ac3",
1324
+ mime: "audio/vnd.dolby.dd-raw"
1325
+ };
1326
+ }
1327
+ if (this.check([120, 1])) {
1328
+ return {
1329
+ ext: "dmg",
1330
+ mime: "application/x-apple-diskimage"
1331
+ };
1332
+ }
1333
+ if (this.check([77, 90])) {
1334
+ return {
1335
+ ext: "exe",
1336
+ mime: "application/x-msdownload"
1337
+ };
1338
+ }
1339
+ if (this.check([37, 33])) {
1340
+ await tokenizer.peekBuffer(this.buffer, { length: 24, mayBeLess: true });
1341
+ if (this.checkString("PS-Adobe-", { offset: 2 }) && this.checkString(" EPSF-", { offset: 14 })) {
1342
+ return {
1343
+ ext: "eps",
1344
+ mime: "application/eps"
1345
+ };
1346
+ }
1347
+ return {
1348
+ ext: "ps",
1349
+ mime: "application/postscript"
1350
+ };
1351
+ }
1352
+ if (this.check([31, 160]) || this.check([31, 157])) {
1353
+ return {
1354
+ ext: "Z",
1355
+ mime: "application/x-compress"
1356
+ };
1357
+ }
1358
+ if (this.check([199, 113])) {
1359
+ return {
1360
+ ext: "cpio",
1361
+ mime: "application/x-cpio"
1362
+ };
1363
+ }
1364
+ if (this.check([96, 234])) {
1365
+ return {
1366
+ ext: "arj",
1367
+ mime: "application/x-arj"
1368
+ };
1369
+ }
1370
+ if (this.check([239, 187, 191])) {
1371
+ this.tokenizer.ignore(3);
1372
+ return this.parse(tokenizer);
1373
+ }
1374
+ if (this.check([71, 73, 70])) {
1375
+ return {
1376
+ ext: "gif",
1377
+ mime: "image/gif"
1378
+ };
1379
+ }
1380
+ if (this.check([73, 73, 188])) {
1381
+ return {
1382
+ ext: "jxr",
1383
+ mime: "image/vnd.ms-photo"
1384
+ };
1385
+ }
1386
+ if (this.check([31, 139, 8])) {
1387
+ return {
1388
+ ext: "gz",
1389
+ mime: "application/gzip"
1390
+ };
1391
+ }
1392
+ if (this.check([66, 90, 104])) {
1393
+ return {
1394
+ ext: "bz2",
1395
+ mime: "application/x-bzip2"
1396
+ };
1397
+ }
1398
+ if (this.checkString("ID3")) {
1399
+ await tokenizer.ignore(6);
1400
+ const id3HeaderLength = await tokenizer.readToken(uint32SyncSafeToken);
1401
+ if (tokenizer.position + id3HeaderLength > tokenizer.fileInfo.size) {
1402
+ return {
1403
+ ext: "mp3",
1404
+ mime: "audio/mpeg"
1405
+ };
1406
+ }
1407
+ await tokenizer.ignore(id3HeaderLength);
1408
+ return this.fromTokenizer(tokenizer);
1409
+ }
1410
+ if (this.checkString("MP+")) {
1411
+ return {
1412
+ ext: "mpc",
1413
+ mime: "audio/x-musepack"
1414
+ };
1415
+ }
1416
+ if ((this.buffer[0] === 67 || this.buffer[0] === 70) && this.check([87, 83], { offset: 1 })) {
1417
+ return {
1418
+ ext: "swf",
1419
+ mime: "application/x-shockwave-flash"
1420
+ };
1421
+ }
1422
+ if (this.check([255, 216, 255])) {
1423
+ if (this.check([247], { offset: 3 })) {
1424
+ return {
1425
+ ext: "jls",
1426
+ mime: "image/jls"
1427
+ };
1428
+ }
1429
+ return {
1430
+ ext: "jpg",
1431
+ mime: "image/jpeg"
1432
+ };
1433
+ }
1434
+ if (this.check([79, 98, 106, 1])) {
1435
+ return {
1436
+ ext: "avro",
1437
+ mime: "application/avro"
1438
+ };
1439
+ }
1440
+ if (this.checkString("FLIF")) {
1441
+ return {
1442
+ ext: "flif",
1443
+ mime: "image/flif"
1444
+ };
1445
+ }
1446
+ if (this.checkString("8BPS")) {
1447
+ return {
1448
+ ext: "psd",
1449
+ mime: "image/vnd.adobe.photoshop"
1450
+ };
1451
+ }
1452
+ if (this.checkString("WEBP", { offset: 8 })) {
1453
+ return {
1454
+ ext: "webp",
1455
+ mime: "image/webp"
1456
+ };
1457
+ }
1458
+ if (this.checkString("MPCK")) {
1459
+ return {
1460
+ ext: "mpc",
1461
+ mime: "audio/x-musepack"
1462
+ };
1463
+ }
1464
+ if (this.checkString("FORM")) {
1465
+ return {
1466
+ ext: "aif",
1467
+ mime: "audio/aiff"
1468
+ };
1469
+ }
1470
+ if (this.checkString("icns", { offset: 0 })) {
1471
+ return {
1472
+ ext: "icns",
1473
+ mime: "image/icns"
1474
+ };
1475
+ }
1476
+ if (this.check([80, 75, 3, 4])) {
1477
+ try {
1478
+ while (tokenizer.position + 30 < tokenizer.fileInfo.size) {
1479
+ await tokenizer.readBuffer(this.buffer, { length: 30 });
1480
+ const view = new DataView(this.buffer.buffer);
1481
+ const zipHeader = {
1482
+ compressedSize: view.getUint32(18, true),
1483
+ uncompressedSize: view.getUint32(22, true),
1484
+ filenameLength: view.getUint16(26, true),
1485
+ extraFieldLength: view.getUint16(28, true)
1486
+ };
1487
+ zipHeader.filename = await tokenizer.readToken(new StringType(zipHeader.filenameLength, "utf-8"));
1488
+ await tokenizer.ignore(zipHeader.extraFieldLength);
1489
+ if (/classes\d*\.dex/.test(zipHeader.filename)) {
1490
+ return {
1491
+ ext: "apk",
1492
+ mime: "application/vnd.android.package-archive"
1493
+ };
1494
+ }
1495
+ if (zipHeader.filename === "META-INF/mozilla.rsa") {
1496
+ return {
1497
+ ext: "xpi",
1498
+ mime: "application/x-xpinstall"
1499
+ };
1500
+ }
1501
+ if (zipHeader.filename.endsWith(".rels") || zipHeader.filename.endsWith(".xml")) {
1502
+ const type = zipHeader.filename.split("/")[0];
1503
+ switch (type) {
1504
+ case "_rels":
1505
+ break;
1506
+ case "word":
1507
+ return {
1508
+ ext: "docx",
1509
+ mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
1510
+ };
1511
+ case "ppt":
1512
+ return {
1513
+ ext: "pptx",
1514
+ mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
1515
+ };
1516
+ case "xl":
1517
+ return {
1518
+ ext: "xlsx",
1519
+ mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1520
+ };
1521
+ case "visio":
1522
+ return {
1523
+ ext: "vsdx",
1524
+ mime: "application/vnd.visio"
1525
+ };
1526
+ default:
1527
+ break;
1528
+ }
1529
+ }
1530
+ if (zipHeader.filename.startsWith("xl/")) {
1531
+ return {
1532
+ ext: "xlsx",
1533
+ mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1534
+ };
1535
+ }
1536
+ if (zipHeader.filename.startsWith("3D/") && zipHeader.filename.endsWith(".model")) {
1537
+ return {
1538
+ ext: "3mf",
1539
+ mime: "model/3mf"
1540
+ };
1541
+ }
1542
+ if (zipHeader.filename === "mimetype" && zipHeader.compressedSize === zipHeader.uncompressedSize) {
1543
+ let mimeType = await tokenizer.readToken(new StringType(zipHeader.compressedSize, "utf-8"));
1544
+ mimeType = mimeType.trim();
1545
+ switch (mimeType) {
1546
+ case "application/epub+zip":
1547
+ return {
1548
+ ext: "epub",
1549
+ mime: "application/epub+zip"
1550
+ };
1551
+ case "application/vnd.oasis.opendocument.text":
1552
+ return {
1553
+ ext: "odt",
1554
+ mime: "application/vnd.oasis.opendocument.text"
1555
+ };
1556
+ case "application/vnd.oasis.opendocument.spreadsheet":
1557
+ return {
1558
+ ext: "ods",
1559
+ mime: "application/vnd.oasis.opendocument.spreadsheet"
1560
+ };
1561
+ case "application/vnd.oasis.opendocument.presentation":
1562
+ return {
1563
+ ext: "odp",
1564
+ mime: "application/vnd.oasis.opendocument.presentation"
1565
+ };
1566
+ default:
1567
+ }
1568
+ }
1569
+ if (zipHeader.compressedSize === 0) {
1570
+ let nextHeaderIndex = -1;
1571
+ while (nextHeaderIndex < 0 && tokenizer.position < tokenizer.fileInfo.size) {
1572
+ await tokenizer.peekBuffer(this.buffer, { mayBeLess: true });
1573
+ nextHeaderIndex = indexOf(this.buffer, new Uint8Array([80, 75, 3, 4]));
1574
+ await tokenizer.ignore(nextHeaderIndex >= 0 ? nextHeaderIndex : this.buffer.length);
1575
+ }
1576
+ } else {
1577
+ await tokenizer.ignore(zipHeader.compressedSize);
1578
+ }
1579
+ }
1580
+ } catch (error) {
1581
+ if (!(error instanceof EndOfStreamError)) {
1582
+ throw error;
1583
+ }
1584
+ }
1585
+ return {
1586
+ ext: "zip",
1587
+ mime: "application/zip"
1588
+ };
1589
+ }
1590
+ if (this.checkString("OggS")) {
1591
+ await tokenizer.ignore(28);
1592
+ const type = new Uint8Array(8);
1593
+ await tokenizer.readBuffer(type);
1594
+ if (_check(type, [79, 112, 117, 115, 72, 101, 97, 100])) {
1595
+ return {
1596
+ ext: "opus",
1597
+ mime: "audio/ogg; codecs=opus"
1598
+ };
1599
+ }
1600
+ if (_check(type, [128, 116, 104, 101, 111, 114, 97])) {
1601
+ return {
1602
+ ext: "ogv",
1603
+ mime: "video/ogg"
1604
+ };
1605
+ }
1606
+ if (_check(type, [1, 118, 105, 100, 101, 111, 0])) {
1607
+ return {
1608
+ ext: "ogm",
1609
+ mime: "video/ogg"
1610
+ };
1611
+ }
1612
+ if (_check(type, [127, 70, 76, 65, 67])) {
1613
+ return {
1614
+ ext: "oga",
1615
+ mime: "audio/ogg"
1616
+ };
1617
+ }
1618
+ if (_check(type, [83, 112, 101, 101, 120, 32, 32])) {
1619
+ return {
1620
+ ext: "spx",
1621
+ mime: "audio/ogg"
1622
+ };
1623
+ }
1624
+ if (_check(type, [1, 118, 111, 114, 98, 105, 115])) {
1625
+ return {
1626
+ ext: "ogg",
1627
+ mime: "audio/ogg"
1628
+ };
1629
+ }
1630
+ return {
1631
+ ext: "ogx",
1632
+ mime: "application/ogg"
1633
+ };
1634
+ }
1635
+ if (this.check([80, 75]) && (this.buffer[2] === 3 || this.buffer[2] === 5 || this.buffer[2] === 7) && (this.buffer[3] === 4 || this.buffer[3] === 6 || this.buffer[3] === 8)) {
1636
+ return {
1637
+ ext: "zip",
1638
+ mime: "application/zip"
1639
+ };
1640
+ }
1641
+ if (this.checkString("ftyp", { offset: 4 }) && (this.buffer[8] & 96) !== 0) {
1642
+ const brandMajor = new StringType(4, "latin1").get(this.buffer, 8).replace("\0", " ").trim();
1643
+ switch (brandMajor) {
1644
+ case "avif":
1645
+ case "avis":
1646
+ return { ext: "avif", mime: "image/avif" };
1647
+ case "mif1":
1648
+ return { ext: "heic", mime: "image/heif" };
1649
+ case "msf1":
1650
+ return { ext: "heic", mime: "image/heif-sequence" };
1651
+ case "heic":
1652
+ case "heix":
1653
+ return { ext: "heic", mime: "image/heic" };
1654
+ case "hevc":
1655
+ case "hevx":
1656
+ return { ext: "heic", mime: "image/heic-sequence" };
1657
+ case "qt":
1658
+ return { ext: "mov", mime: "video/quicktime" };
1659
+ case "M4V":
1660
+ case "M4VH":
1661
+ case "M4VP":
1662
+ return { ext: "m4v", mime: "video/x-m4v" };
1663
+ case "M4P":
1664
+ return { ext: "m4p", mime: "video/mp4" };
1665
+ case "M4B":
1666
+ return { ext: "m4b", mime: "audio/mp4" };
1667
+ case "M4A":
1668
+ return { ext: "m4a", mime: "audio/x-m4a" };
1669
+ case "F4V":
1670
+ return { ext: "f4v", mime: "video/mp4" };
1671
+ case "F4P":
1672
+ return { ext: "f4p", mime: "video/mp4" };
1673
+ case "F4A":
1674
+ return { ext: "f4a", mime: "audio/mp4" };
1675
+ case "F4B":
1676
+ return { ext: "f4b", mime: "audio/mp4" };
1677
+ case "crx":
1678
+ return { ext: "cr3", mime: "image/x-canon-cr3" };
1679
+ default:
1680
+ if (brandMajor.startsWith("3g")) {
1681
+ if (brandMajor.startsWith("3g2")) {
1682
+ return { ext: "3g2", mime: "video/3gpp2" };
1683
+ }
1684
+ return { ext: "3gp", mime: "video/3gpp" };
1685
+ }
1686
+ return { ext: "mp4", mime: "video/mp4" };
1687
+ }
1688
+ }
1689
+ if (this.checkString("MThd")) {
1690
+ return {
1691
+ ext: "mid",
1692
+ mime: "audio/midi"
1693
+ };
1694
+ }
1695
+ if (this.checkString("wOFF") && (this.check([0, 1, 0, 0], { offset: 4 }) || this.checkString("OTTO", { offset: 4 }))) {
1696
+ return {
1697
+ ext: "woff",
1698
+ mime: "font/woff"
1699
+ };
1700
+ }
1701
+ if (this.checkString("wOF2") && (this.check([0, 1, 0, 0], { offset: 4 }) || this.checkString("OTTO", { offset: 4 }))) {
1702
+ return {
1703
+ ext: "woff2",
1704
+ mime: "font/woff2"
1705
+ };
1706
+ }
1707
+ if (this.check([212, 195, 178, 161]) || this.check([161, 178, 195, 212])) {
1708
+ return {
1709
+ ext: "pcap",
1710
+ mime: "application/vnd.tcpdump.pcap"
1711
+ };
1712
+ }
1713
+ if (this.checkString("DSD ")) {
1714
+ return {
1715
+ ext: "dsf",
1716
+ mime: "audio/x-dsf"
1717
+ // Non-standard
1718
+ };
1719
+ }
1720
+ if (this.checkString("LZIP")) {
1721
+ return {
1722
+ ext: "lz",
1723
+ mime: "application/x-lzip"
1724
+ };
1725
+ }
1726
+ if (this.checkString("fLaC")) {
1727
+ return {
1728
+ ext: "flac",
1729
+ mime: "audio/x-flac"
1730
+ };
1731
+ }
1732
+ if (this.check([66, 80, 71, 251])) {
1733
+ return {
1734
+ ext: "bpg",
1735
+ mime: "image/bpg"
1736
+ };
1737
+ }
1738
+ if (this.checkString("wvpk")) {
1739
+ return {
1740
+ ext: "wv",
1741
+ mime: "audio/wavpack"
1742
+ };
1743
+ }
1744
+ if (this.checkString("%PDF")) {
1745
+ try {
1746
+ await tokenizer.ignore(1350);
1747
+ const maxBufferSize2 = 10 * 1024 * 1024;
1748
+ const buffer = new Uint8Array(Math.min(maxBufferSize2, tokenizer.fileInfo.size));
1749
+ await tokenizer.readBuffer(buffer, { mayBeLess: true });
1750
+ if (includes(buffer, new TextEncoder().encode("AIPrivateData"))) {
1751
+ return {
1752
+ ext: "ai",
1753
+ mime: "application/postscript"
1754
+ };
1755
+ }
1756
+ } catch (error) {
1757
+ if (!(error instanceof EndOfStreamError)) {
1758
+ throw error;
1759
+ }
1760
+ }
1761
+ return {
1762
+ ext: "pdf",
1763
+ mime: "application/pdf"
1764
+ };
1765
+ }
1766
+ if (this.check([0, 97, 115, 109])) {
1767
+ return {
1768
+ ext: "wasm",
1769
+ mime: "application/wasm"
1770
+ };
1771
+ }
1772
+ if (this.check([73, 73])) {
1773
+ const fileType = await this.readTiffHeader(false);
1774
+ if (fileType) {
1775
+ return fileType;
1776
+ }
1777
+ }
1778
+ if (this.check([77, 77])) {
1779
+ const fileType = await this.readTiffHeader(true);
1780
+ if (fileType) {
1781
+ return fileType;
1782
+ }
1783
+ }
1784
+ if (this.checkString("MAC ")) {
1785
+ return {
1786
+ ext: "ape",
1787
+ mime: "audio/ape"
1788
+ };
1789
+ }
1790
+ if (this.check([26, 69, 223, 163])) {
1791
+ async function readField() {
1792
+ const msb = await tokenizer.peekNumber(UINT8);
1793
+ let mask = 128;
1794
+ let ic = 0;
1795
+ while ((msb & mask) === 0 && mask !== 0) {
1796
+ ++ic;
1797
+ mask >>= 1;
1798
+ }
1799
+ const id = new Uint8Array(ic + 1);
1800
+ await tokenizer.readBuffer(id);
1801
+ return id;
1802
+ }
1803
+ async function readElement() {
1804
+ const idField = await readField();
1805
+ const lengthField = await readField();
1806
+ lengthField[0] ^= 128 >> lengthField.length - 1;
1807
+ const nrLength = Math.min(6, lengthField.length);
1808
+ const idView = new DataView(idField.buffer);
1809
+ const lengthView = new DataView(lengthField.buffer, lengthField.length - nrLength, nrLength);
1810
+ return {
1811
+ id: getUintBE(idView),
1812
+ len: getUintBE(lengthView)
1813
+ };
1814
+ }
1815
+ async function readChildren(children) {
1816
+ while (children > 0) {
1817
+ const element = await readElement();
1818
+ if (element.id === 17026) {
1819
+ const rawValue = await tokenizer.readToken(new StringType(element.len));
1820
+ return rawValue.replaceAll(/\00.*$/g, "");
1821
+ }
1822
+ await tokenizer.ignore(element.len);
1823
+ --children;
1824
+ }
1825
+ }
1826
+ const re = await readElement();
1827
+ const docType = await readChildren(re.len);
1828
+ switch (docType) {
1829
+ case "webm":
1830
+ return {
1831
+ ext: "webm",
1832
+ mime: "video/webm"
1833
+ };
1834
+ case "matroska":
1835
+ return {
1836
+ ext: "mkv",
1837
+ mime: "video/x-matroska"
1838
+ };
1839
+ default:
1840
+ return;
1841
+ }
1842
+ }
1843
+ if (this.check([82, 73, 70, 70])) {
1844
+ if (this.check([65, 86, 73], { offset: 8 })) {
1845
+ return {
1846
+ ext: "avi",
1847
+ mime: "video/vnd.avi"
1848
+ };
1849
+ }
1850
+ if (this.check([87, 65, 86, 69], { offset: 8 })) {
1851
+ return {
1852
+ ext: "wav",
1853
+ mime: "audio/wav"
1854
+ };
1855
+ }
1856
+ if (this.check([81, 76, 67, 77], { offset: 8 })) {
1857
+ return {
1858
+ ext: "qcp",
1859
+ mime: "audio/qcelp"
1860
+ };
1861
+ }
1862
+ }
1863
+ if (this.checkString("SQLi")) {
1864
+ return {
1865
+ ext: "sqlite",
1866
+ mime: "application/x-sqlite3"
1867
+ };
1868
+ }
1869
+ if (this.check([78, 69, 83, 26])) {
1870
+ return {
1871
+ ext: "nes",
1872
+ mime: "application/x-nintendo-nes-rom"
1873
+ };
1874
+ }
1875
+ if (this.checkString("Cr24")) {
1876
+ return {
1877
+ ext: "crx",
1878
+ mime: "application/x-google-chrome-extension"
1879
+ };
1880
+ }
1881
+ if (this.checkString("MSCF") || this.checkString("ISc(")) {
1882
+ return {
1883
+ ext: "cab",
1884
+ mime: "application/vnd.ms-cab-compressed"
1885
+ };
1886
+ }
1887
+ if (this.check([237, 171, 238, 219])) {
1888
+ return {
1889
+ ext: "rpm",
1890
+ mime: "application/x-rpm"
1891
+ };
1892
+ }
1893
+ if (this.check([197, 208, 211, 198])) {
1894
+ return {
1895
+ ext: "eps",
1896
+ mime: "application/eps"
1897
+ };
1898
+ }
1899
+ if (this.check([40, 181, 47, 253])) {
1900
+ return {
1901
+ ext: "zst",
1902
+ mime: "application/zstd"
1903
+ };
1904
+ }
1905
+ if (this.check([127, 69, 76, 70])) {
1906
+ return {
1907
+ ext: "elf",
1908
+ mime: "application/x-elf"
1909
+ };
1910
+ }
1911
+ if (this.check([33, 66, 68, 78])) {
1912
+ return {
1913
+ ext: "pst",
1914
+ mime: "application/vnd.ms-outlook"
1915
+ };
1916
+ }
1917
+ if (this.checkString("PAR1")) {
1918
+ return {
1919
+ ext: "parquet",
1920
+ mime: "application/x-parquet"
1921
+ };
1922
+ }
1923
+ if (this.check([207, 250, 237, 254])) {
1924
+ return {
1925
+ ext: "macho",
1926
+ mime: "application/x-mach-binary"
1927
+ };
1928
+ }
1929
+ if (this.check([79, 84, 84, 79, 0])) {
1930
+ return {
1931
+ ext: "otf",
1932
+ mime: "font/otf"
1933
+ };
1934
+ }
1935
+ if (this.checkString("#!AMR")) {
1936
+ return {
1937
+ ext: "amr",
1938
+ mime: "audio/amr"
1939
+ };
1940
+ }
1941
+ if (this.checkString("{\\rtf")) {
1942
+ return {
1943
+ ext: "rtf",
1944
+ mime: "application/rtf"
1945
+ };
1946
+ }
1947
+ if (this.check([70, 76, 86, 1])) {
1948
+ return {
1949
+ ext: "flv",
1950
+ mime: "video/x-flv"
1951
+ };
1952
+ }
1953
+ if (this.checkString("IMPM")) {
1954
+ return {
1955
+ ext: "it",
1956
+ mime: "audio/x-it"
1957
+ };
1958
+ }
1959
+ if (this.checkString("-lh0-", { offset: 2 }) || this.checkString("-lh1-", { offset: 2 }) || this.checkString("-lh2-", { offset: 2 }) || this.checkString("-lh3-", { offset: 2 }) || this.checkString("-lh4-", { offset: 2 }) || this.checkString("-lh5-", { offset: 2 }) || this.checkString("-lh6-", { offset: 2 }) || this.checkString("-lh7-", { offset: 2 }) || this.checkString("-lzs-", { offset: 2 }) || this.checkString("-lz4-", { offset: 2 }) || this.checkString("-lz5-", { offset: 2 }) || this.checkString("-lhd-", { offset: 2 })) {
1960
+ return {
1961
+ ext: "lzh",
1962
+ mime: "application/x-lzh-compressed"
1963
+ };
1964
+ }
1965
+ if (this.check([0, 0, 1, 186])) {
1966
+ if (this.check([33], { offset: 4, mask: [241] })) {
1967
+ return {
1968
+ ext: "mpg",
1969
+ // May also be .ps, .mpeg
1970
+ mime: "video/MP1S"
1971
+ };
1972
+ }
1973
+ if (this.check([68], { offset: 4, mask: [196] })) {
1974
+ return {
1975
+ ext: "mpg",
1976
+ // May also be .mpg, .m2p, .vob or .sub
1977
+ mime: "video/MP2P"
1978
+ };
1979
+ }
1980
+ }
1981
+ if (this.checkString("ITSF")) {
1982
+ return {
1983
+ ext: "chm",
1984
+ mime: "application/vnd.ms-htmlhelp"
1985
+ };
1986
+ }
1987
+ if (this.check([202, 254, 186, 190])) {
1988
+ return {
1989
+ ext: "class",
1990
+ mime: "application/java-vm"
1991
+ };
1992
+ }
1993
+ if (this.check([253, 55, 122, 88, 90, 0])) {
1994
+ return {
1995
+ ext: "xz",
1996
+ mime: "application/x-xz"
1997
+ };
1998
+ }
1999
+ if (this.checkString("<?xml ")) {
2000
+ return {
2001
+ ext: "xml",
2002
+ mime: "application/xml"
2003
+ };
2004
+ }
2005
+ if (this.check([55, 122, 188, 175, 39, 28])) {
2006
+ return {
2007
+ ext: "7z",
2008
+ mime: "application/x-7z-compressed"
2009
+ };
2010
+ }
2011
+ if (this.check([82, 97, 114, 33, 26, 7]) && (this.buffer[6] === 0 || this.buffer[6] === 1)) {
2012
+ return {
2013
+ ext: "rar",
2014
+ mime: "application/x-rar-compressed"
2015
+ };
2016
+ }
2017
+ if (this.checkString("solid ")) {
2018
+ return {
2019
+ ext: "stl",
2020
+ mime: "model/stl"
2021
+ };
2022
+ }
2023
+ if (this.checkString("AC")) {
2024
+ const version = new StringType(4, "latin1").get(this.buffer, 2);
2025
+ if (version.match("^d*") && version >= 1e3 && version <= 1050) {
2026
+ return {
2027
+ ext: "dwg",
2028
+ mime: "image/vnd.dwg"
2029
+ };
2030
+ }
2031
+ }
2032
+ if (this.checkString("070707")) {
2033
+ return {
2034
+ ext: "cpio",
2035
+ mime: "application/x-cpio"
2036
+ };
2037
+ }
2038
+ if (this.checkString("BLENDER")) {
2039
+ return {
2040
+ ext: "blend",
2041
+ mime: "application/x-blender"
2042
+ };
2043
+ }
2044
+ if (this.checkString("!<arch>")) {
2045
+ await tokenizer.ignore(8);
2046
+ const string = await tokenizer.readToken(new StringType(13, "ascii"));
2047
+ if (string === "debian-binary") {
2048
+ return {
2049
+ ext: "deb",
2050
+ mime: "application/x-deb"
2051
+ };
2052
+ }
2053
+ return {
2054
+ ext: "ar",
2055
+ mime: "application/x-unix-archive"
2056
+ };
2057
+ }
2058
+ if (this.checkString("WEBVTT") && // One of LF, CR, tab, space, or end of file must follow "WEBVTT" per the spec (see `fixture/fixture-vtt-*.vtt` for examples). Note that `\0` is technically the null character (there is no such thing as an EOF character). However, checking for `\0` gives us the same result as checking for the end of the stream.
2059
+ ["\n", "\r", " ", " ", "\0"].some((char7) => this.checkString(char7, { offset: 6 }))) {
2060
+ return {
2061
+ ext: "vtt",
2062
+ mime: "text/vtt"
2063
+ };
2064
+ }
2065
+ if (this.check([137, 80, 78, 71, 13, 10, 26, 10])) {
2066
+ await tokenizer.ignore(8);
2067
+ async function readChunkHeader() {
2068
+ return {
2069
+ length: await tokenizer.readToken(INT32_BE),
2070
+ type: await tokenizer.readToken(new StringType(4, "latin1"))
2071
+ };
2072
+ }
2073
+ do {
2074
+ const chunk = await readChunkHeader();
2075
+ if (chunk.length < 0) {
2076
+ return;
2077
+ }
2078
+ switch (chunk.type) {
2079
+ case "IDAT":
2080
+ return {
2081
+ ext: "png",
2082
+ mime: "image/png"
2083
+ };
2084
+ case "acTL":
2085
+ return {
2086
+ ext: "apng",
2087
+ mime: "image/apng"
2088
+ };
2089
+ default:
2090
+ await tokenizer.ignore(chunk.length + 4);
2091
+ }
2092
+ } while (tokenizer.position + 8 < tokenizer.fileInfo.size);
2093
+ return {
2094
+ ext: "png",
2095
+ mime: "image/png"
2096
+ };
2097
+ }
2098
+ if (this.check([65, 82, 82, 79, 87, 49, 0, 0])) {
2099
+ return {
2100
+ ext: "arrow",
2101
+ mime: "application/x-apache-arrow"
2102
+ };
2103
+ }
2104
+ if (this.check([103, 108, 84, 70, 2, 0, 0, 0])) {
2105
+ return {
2106
+ ext: "glb",
2107
+ mime: "model/gltf-binary"
2108
+ };
2109
+ }
2110
+ if (this.check([102, 114, 101, 101], { offset: 4 }) || this.check([109, 100, 97, 116], { offset: 4 }) || this.check([109, 111, 111, 118], { offset: 4 }) || this.check([119, 105, 100, 101], { offset: 4 })) {
2111
+ return {
2112
+ ext: "mov",
2113
+ mime: "video/quicktime"
2114
+ };
2115
+ }
2116
+ if (this.check([73, 73, 82, 79, 8, 0, 0, 0, 24])) {
2117
+ return {
2118
+ ext: "orf",
2119
+ mime: "image/x-olympus-orf"
2120
+ };
2121
+ }
2122
+ if (this.checkString("gimp xcf ")) {
2123
+ return {
2124
+ ext: "xcf",
2125
+ mime: "image/x-xcf"
2126
+ };
2127
+ }
2128
+ if (this.check([73, 73, 85, 0, 24, 0, 0, 0, 136, 231, 116, 216])) {
2129
+ return {
2130
+ ext: "rw2",
2131
+ mime: "image/x-panasonic-rw2"
2132
+ };
2133
+ }
2134
+ if (this.check([48, 38, 178, 117, 142, 102, 207, 17, 166, 217])) {
2135
+ async function readHeader() {
2136
+ const guid = new Uint8Array(16);
2137
+ await tokenizer.readBuffer(guid);
2138
+ return {
2139
+ id: guid,
2140
+ size: Number(await tokenizer.readToken(UINT64_LE))
2141
+ };
2142
+ }
2143
+ await tokenizer.ignore(30);
2144
+ while (tokenizer.position + 24 < tokenizer.fileInfo.size) {
2145
+ const header = await readHeader();
2146
+ let payload = header.size - 24;
2147
+ if (_check(header.id, [145, 7, 220, 183, 183, 169, 207, 17, 142, 230, 0, 192, 12, 32, 83, 101])) {
2148
+ const typeId = new Uint8Array(16);
2149
+ payload -= await tokenizer.readBuffer(typeId);
2150
+ if (_check(typeId, [64, 158, 105, 248, 77, 91, 207, 17, 168, 253, 0, 128, 95, 92, 68, 43])) {
2151
+ return {
2152
+ ext: "asf",
2153
+ mime: "audio/x-ms-asf"
2154
+ };
2155
+ }
2156
+ if (_check(typeId, [192, 239, 25, 188, 77, 91, 207, 17, 168, 253, 0, 128, 95, 92, 68, 43])) {
2157
+ return {
2158
+ ext: "asf",
2159
+ mime: "video/x-ms-asf"
2160
+ };
2161
+ }
2162
+ break;
2163
+ }
2164
+ await tokenizer.ignore(payload);
2165
+ }
2166
+ return {
2167
+ ext: "asf",
2168
+ mime: "application/vnd.ms-asf"
2169
+ };
2170
+ }
2171
+ if (this.check([171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10])) {
2172
+ return {
2173
+ ext: "ktx",
2174
+ mime: "image/ktx"
2175
+ };
2176
+ }
2177
+ if ((this.check([126, 16, 4]) || this.check([126, 24, 4])) && this.check([48, 77, 73, 69], { offset: 4 })) {
2178
+ return {
2179
+ ext: "mie",
2180
+ mime: "application/x-mie"
2181
+ };
2182
+ }
2183
+ if (this.check([39, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], { offset: 2 })) {
2184
+ return {
2185
+ ext: "shp",
2186
+ mime: "application/x-esri-shape"
2187
+ };
2188
+ }
2189
+ if (this.check([255, 79, 255, 81])) {
2190
+ return {
2191
+ ext: "j2c",
2192
+ mime: "image/j2c"
2193
+ };
2194
+ }
2195
+ if (this.check([0, 0, 0, 12, 106, 80, 32, 32, 13, 10, 135, 10])) {
2196
+ await tokenizer.ignore(20);
2197
+ const type = await tokenizer.readToken(new StringType(4, "ascii"));
2198
+ switch (type) {
2199
+ case "jp2 ":
2200
+ return {
2201
+ ext: "jp2",
2202
+ mime: "image/jp2"
2203
+ };
2204
+ case "jpx ":
2205
+ return {
2206
+ ext: "jpx",
2207
+ mime: "image/jpx"
2208
+ };
2209
+ case "jpm ":
2210
+ return {
2211
+ ext: "jpm",
2212
+ mime: "image/jpm"
2213
+ };
2214
+ case "mjp2":
2215
+ return {
2216
+ ext: "mj2",
2217
+ mime: "image/mj2"
2218
+ };
2219
+ default:
2220
+ return;
2221
+ }
2222
+ }
2223
+ if (this.check([255, 10]) || this.check([0, 0, 0, 12, 74, 88, 76, 32, 13, 10, 135, 10])) {
2224
+ return {
2225
+ ext: "jxl",
2226
+ mime: "image/jxl"
2227
+ };
2228
+ }
2229
+ if (this.check([254, 255])) {
2230
+ if (this.check([0, 60, 0, 63, 0, 120, 0, 109, 0, 108], { offset: 2 })) {
2231
+ return {
2232
+ ext: "xml",
2233
+ mime: "application/xml"
2234
+ };
2235
+ }
2236
+ return void 0;
2237
+ }
2238
+ if (this.check([0, 0, 1, 186]) || this.check([0, 0, 1, 179])) {
2239
+ return {
2240
+ ext: "mpg",
2241
+ mime: "video/mpeg"
2242
+ };
2243
+ }
2244
+ if (this.check([0, 1, 0, 0, 0])) {
2245
+ return {
2246
+ ext: "ttf",
2247
+ mime: "font/ttf"
2248
+ };
2249
+ }
2250
+ if (this.check([0, 0, 1, 0])) {
2251
+ return {
2252
+ ext: "ico",
2253
+ mime: "image/x-icon"
2254
+ };
2255
+ }
2256
+ if (this.check([0, 0, 2, 0])) {
2257
+ return {
2258
+ ext: "cur",
2259
+ mime: "image/x-icon"
2260
+ };
2261
+ }
2262
+ if (this.check([208, 207, 17, 224, 161, 177, 26, 225])) {
2263
+ return {
2264
+ ext: "cfb",
2265
+ mime: "application/x-cfb"
2266
+ };
2267
+ }
2268
+ await tokenizer.peekBuffer(this.buffer, { length: Math.min(256, tokenizer.fileInfo.size), mayBeLess: true });
2269
+ if (this.check([97, 99, 115, 112], { offset: 36 })) {
2270
+ return {
2271
+ ext: "icc",
2272
+ mime: "application/vnd.iccprofile"
2273
+ };
2274
+ }
2275
+ if (this.checkString("**ACE", { offset: 7 }) && this.checkString("**", { offset: 12 })) {
2276
+ return {
2277
+ ext: "ace",
2278
+ mime: "application/x-ace-compressed"
2279
+ };
2280
+ }
2281
+ if (this.checkString("BEGIN:")) {
2282
+ if (this.checkString("VCARD", { offset: 6 })) {
2283
+ return {
2284
+ ext: "vcf",
2285
+ mime: "text/vcard"
2286
+ };
2287
+ }
2288
+ if (this.checkString("VCALENDAR", { offset: 6 })) {
2289
+ return {
2290
+ ext: "ics",
2291
+ mime: "text/calendar"
2292
+ };
2293
+ }
2294
+ }
2295
+ if (this.checkString("FUJIFILMCCD-RAW")) {
2296
+ return {
2297
+ ext: "raf",
2298
+ mime: "image/x-fujifilm-raf"
2299
+ };
2300
+ }
2301
+ if (this.checkString("Extended Module:")) {
2302
+ return {
2303
+ ext: "xm",
2304
+ mime: "audio/x-xm"
2305
+ };
2306
+ }
2307
+ if (this.checkString("Creative Voice File")) {
2308
+ return {
2309
+ ext: "voc",
2310
+ mime: "audio/x-voc"
2311
+ };
2312
+ }
2313
+ if (this.check([4, 0, 0, 0]) && this.buffer.length >= 16) {
2314
+ const jsonSize = new DataView(this.buffer.buffer).getUint32(12, true);
2315
+ if (jsonSize > 12 && this.buffer.length >= jsonSize + 16) {
2316
+ try {
2317
+ const header = new TextDecoder().decode(this.buffer.slice(16, jsonSize + 16));
2318
+ const json = JSON.parse(header);
2319
+ if (json.files) {
2320
+ return {
2321
+ ext: "asar",
2322
+ mime: "application/x-asar"
2323
+ };
2324
+ }
2325
+ } catch {
2326
+ }
2327
+ }
2328
+ }
2329
+ if (this.check([6, 14, 43, 52, 2, 5, 1, 1, 13, 1, 2, 1, 1, 2])) {
2330
+ return {
2331
+ ext: "mxf",
2332
+ mime: "application/mxf"
2333
+ };
2334
+ }
2335
+ if (this.checkString("SCRM", { offset: 44 })) {
2336
+ return {
2337
+ ext: "s3m",
2338
+ mime: "audio/x-s3m"
2339
+ };
2340
+ }
2341
+ if (this.check([71]) && this.check([71], { offset: 188 })) {
2342
+ return {
2343
+ ext: "mts",
2344
+ mime: "video/mp2t"
2345
+ };
2346
+ }
2347
+ if (this.check([71], { offset: 4 }) && this.check([71], { offset: 196 })) {
2348
+ return {
2349
+ ext: "mts",
2350
+ mime: "video/mp2t"
2351
+ };
2352
+ }
2353
+ if (this.check([66, 79, 79, 75, 77, 79, 66, 73], { offset: 60 })) {
2354
+ return {
2355
+ ext: "mobi",
2356
+ mime: "application/x-mobipocket-ebook"
2357
+ };
2358
+ }
2359
+ if (this.check([68, 73, 67, 77], { offset: 128 })) {
2360
+ return {
2361
+ ext: "dcm",
2362
+ mime: "application/dicom"
2363
+ };
2364
+ }
2365
+ if (this.check([76, 0, 0, 0, 1, 20, 2, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 70])) {
2366
+ return {
2367
+ ext: "lnk",
2368
+ mime: "application/x.ms.shortcut"
2369
+ // Invented by us
2370
+ };
2371
+ }
2372
+ if (this.check([98, 111, 111, 107, 0, 0, 0, 0, 109, 97, 114, 107, 0, 0, 0, 0])) {
2373
+ return {
2374
+ ext: "alias",
2375
+ mime: "application/x.apple.alias"
2376
+ // Invented by us
2377
+ };
2378
+ }
2379
+ if (this.checkString("Kaydara FBX Binary \0")) {
2380
+ return {
2381
+ ext: "fbx",
2382
+ mime: "application/x.autodesk.fbx"
2383
+ // Invented by us
2384
+ };
2385
+ }
2386
+ if (this.check([76, 80], { offset: 34 }) && (this.check([0, 0, 1], { offset: 8 }) || this.check([1, 0, 2], { offset: 8 }) || this.check([2, 0, 2], { offset: 8 }))) {
2387
+ return {
2388
+ ext: "eot",
2389
+ mime: "application/vnd.ms-fontobject"
2390
+ };
2391
+ }
2392
+ if (this.check([6, 6, 237, 245, 216, 29, 70, 229, 189, 49, 239, 231, 254, 116, 183, 29])) {
2393
+ return {
2394
+ ext: "indd",
2395
+ mime: "application/x-indesign"
2396
+ };
2397
+ }
2398
+ await tokenizer.peekBuffer(this.buffer, { length: Math.min(512, tokenizer.fileInfo.size), mayBeLess: true });
2399
+ if (tarHeaderChecksumMatches(this.buffer)) {
2400
+ return {
2401
+ ext: "tar",
2402
+ mime: "application/x-tar"
2403
+ };
2404
+ }
2405
+ if (this.check([255, 254])) {
2406
+ if (this.check([60, 0, 63, 0, 120, 0, 109, 0, 108, 0], { offset: 2 })) {
2407
+ return {
2408
+ ext: "xml",
2409
+ mime: "application/xml"
2410
+ };
2411
+ }
2412
+ if (this.check([255, 14, 83, 0, 107, 0, 101, 0, 116, 0, 99, 0, 104, 0, 85, 0, 112, 0, 32, 0, 77, 0, 111, 0, 100, 0, 101, 0, 108, 0], { offset: 2 })) {
2413
+ return {
2414
+ ext: "skp",
2415
+ mime: "application/vnd.sketchup.skp"
2416
+ };
2417
+ }
2418
+ return void 0;
2419
+ }
2420
+ if (this.checkString("-----BEGIN PGP MESSAGE-----")) {
2421
+ return {
2422
+ ext: "pgp",
2423
+ mime: "application/pgp-encrypted"
2424
+ };
2425
+ }
2426
+ if (this.buffer.length >= 2 && this.check([255, 224], { offset: 0, mask: [255, 224] })) {
2427
+ if (this.check([16], { offset: 1, mask: [22] })) {
2428
+ if (this.check([8], { offset: 1, mask: [8] })) {
2429
+ return {
2430
+ ext: "aac",
2431
+ mime: "audio/aac"
2432
+ };
2433
+ }
2434
+ return {
2435
+ ext: "aac",
2436
+ mime: "audio/aac"
2437
+ };
2438
+ }
2439
+ if (this.check([2], { offset: 1, mask: [6] })) {
2440
+ return {
2441
+ ext: "mp3",
2442
+ mime: "audio/mpeg"
2443
+ };
2444
+ }
2445
+ if (this.check([4], { offset: 1, mask: [6] })) {
2446
+ return {
2447
+ ext: "mp2",
2448
+ mime: "audio/mpeg"
2449
+ };
2450
+ }
2451
+ if (this.check([6], { offset: 1, mask: [6] })) {
2452
+ return {
2453
+ ext: "mp1",
2454
+ mime: "audio/mpeg"
2455
+ };
2456
+ }
2457
+ }
2458
+ }
2459
+ async readTiffTag(bigEndian) {
2460
+ const tagId = await this.tokenizer.readToken(bigEndian ? UINT16_BE : UINT16_LE);
2461
+ this.tokenizer.ignore(10);
2462
+ switch (tagId) {
2463
+ case 50341:
2464
+ return {
2465
+ ext: "arw",
2466
+ mime: "image/x-sony-arw"
2467
+ };
2468
+ case 50706:
2469
+ return {
2470
+ ext: "dng",
2471
+ mime: "image/x-adobe-dng"
2472
+ };
2473
+ }
2474
+ }
2475
+ async readTiffIFD(bigEndian) {
2476
+ const numberOfTags = await this.tokenizer.readToken(bigEndian ? UINT16_BE : UINT16_LE);
2477
+ for (let n = 0; n < numberOfTags; ++n) {
2478
+ const fileType = await this.readTiffTag(bigEndian);
2479
+ if (fileType) {
2480
+ return fileType;
2481
+ }
2482
+ }
2483
+ }
2484
+ async readTiffHeader(bigEndian) {
2485
+ const version = (bigEndian ? UINT16_BE : UINT16_LE).get(this.buffer, 2);
2486
+ const ifdOffset = (bigEndian ? UINT32_BE : UINT32_LE).get(this.buffer, 4);
2487
+ if (version === 42) {
2488
+ if (ifdOffset >= 6) {
2489
+ if (this.checkString("CR", { offset: 8 })) {
2490
+ return {
2491
+ ext: "cr2",
2492
+ mime: "image/x-canon-cr2"
2493
+ };
2494
+ }
2495
+ if (ifdOffset >= 8 && (this.check([28, 0, 254, 0], { offset: 8 }) || this.check([31, 0, 11, 0], { offset: 8 }))) {
2496
+ return {
2497
+ ext: "nef",
2498
+ mime: "image/x-nikon-nef"
2499
+ };
2500
+ }
2501
+ }
2502
+ await this.tokenizer.ignore(ifdOffset);
2503
+ const fileType = await this.readTiffIFD(bigEndian);
2504
+ return fileType ?? {
2505
+ ext: "tif",
2506
+ mime: "image/tiff"
2507
+ };
2508
+ }
2509
+ if (version === 43) {
2510
+ return {
2511
+ ext: "tif",
2512
+ mime: "image/tiff"
2513
+ };
2514
+ }
2515
+ }
2516
+ };
2517
+ new Set(extensions);
2518
+ new Set(mimeTypes);
2519
+
2520
+ // src/extract-metadata.ts
2521
+ async function extractMetadataFromBuffer(buffer, context, opts) {
2522
+ const extract = opts.extractMetadata !== false;
2523
+ if (!extract) {
2524
+ return {};
2525
+ }
2526
+ const patch = {
2527
+ file: {},
2528
+ checksums: {}
2529
+ };
2530
+ const { trusted } = context;
2531
+ if (trusted.file?.size == null) {
2532
+ patch.file.size = buffer.length;
2533
+ }
2534
+ if (trusted.file?.mimeType == null || trusted.file?.extension == null) {
2535
+ const ft = await fileTypeFromBuffer(buffer);
2536
+ if (ft) {
2537
+ if (trusted.file?.mimeType == null) patch.file.mimeType = ft.mime;
2538
+ if (trusted.file?.extension == null) {
2539
+ patch.file.extension = ft.ext.startsWith(".") ? ft.ext : `.${ft.ext}`;
2540
+ }
2541
+ }
2542
+ }
2543
+ const algorithms = opts.extractChecksums ?? ["sha256"];
2544
+ for (const algo of algorithms) {
2545
+ if (algo === "sha256" && trusted.checksums?.sha256 == null) {
2546
+ patch.checksums.sha256 = crypto.createHash("sha256").update(buffer).digest("hex");
2547
+ } else if (algo === "md5" && trusted.checksums?.md5 == null) {
2548
+ patch.checksums.md5 = crypto.createHash("md5").update(buffer).digest("hex");
2549
+ }
2550
+ }
2551
+ return patch;
2552
+ }
2553
+ async function validateFileType(buffer, file, metadata, opts) {
2554
+ const errors = [];
2555
+ const ext = path.extname(file.key).toLowerCase();
2556
+ if (opts.allowedExtensions && opts.allowedExtensions.length > 0) {
2557
+ const allowed = opts.allowedExtensions.map((e) => e.toLowerCase().replace(/^\.?/, "."));
2558
+ if (!allowed.includes(ext)) {
2559
+ errors.push({
2560
+ rule: "extension",
2561
+ message: `Extension ${ext} not allowed. Allowed: ${allowed.join(", ")}`,
2562
+ details: { extension: ext, allowedExtensions: opts.allowedExtensions }
2563
+ });
2564
+ }
2565
+ }
2566
+ let detectedMime;
2567
+ let magicMime;
2568
+ const useMagicBytes = opts.useMagicBytes !== false;
2569
+ if (useMagicBytes) {
2570
+ const ft = await fileTypeFromBuffer(buffer);
2571
+ magicMime = ft?.mime;
2572
+ if (!magicMime && opts.allowedMimeTypes && opts.allowedMimeTypes.length > 0) {
2573
+ errors.push({
2574
+ rule: "magic-bytes",
2575
+ message: "Could not detect MIME type from file content (magic bytes)",
2576
+ details: { fileKey: file.key }
2577
+ });
2578
+ }
2579
+ if (ext) {
2580
+ const allowedMimes = EXTENSION_TO_MIME_MAP[ext];
2581
+ if (allowedMimes) {
2582
+ if (!magicMime) {
2583
+ const isGeneric = allowedMimes.some(
2584
+ (m) => m === "application/octet-stream" || m === "text/plain"
2585
+ );
2586
+ if (!isGeneric) {
2587
+ errors.push({
2588
+ rule: "mime-spoof",
2589
+ message: `MIME spoofing detected: file content does not match expected ${ext} format`,
2590
+ details: { extension: ext, magicMime: "none" }
2591
+ });
2592
+ }
2593
+ } else if (!allowedMimes.includes(magicMime)) {
2594
+ errors.push({
2595
+ rule: "mime-spoof",
2596
+ message: `MIME spoofing detected: content is ${magicMime} but extension is ${ext}`,
2597
+ details: { extension: ext, magicMime }
2598
+ });
2599
+ }
2600
+ }
2601
+ }
2602
+ detectedMime = magicMime;
2603
+ }
2604
+ if (!detectedMime) {
2605
+ const metaMime = file.mimeType ?? metadata.contentType ?? metadata.mimeType ?? metadata["content-type"];
2606
+ detectedMime = typeof metaMime === "string" ? metaMime : void 0;
2607
+ }
2608
+ if (opts.allowedMimeTypes && opts.allowedMimeTypes.length > 0) {
2609
+ if (detectedMime && !opts.allowedMimeTypes.includes(detectedMime)) {
2610
+ errors.push({
2611
+ rule: "mime-type",
2612
+ message: `MIME type ${detectedMime} not allowed. Allowed: ${opts.allowedMimeTypes.join(", ")}`,
2613
+ details: { mime: detectedMime, allowedMimeTypes: opts.allowedMimeTypes }
2614
+ });
2615
+ } else if (!detectedMime) {
2616
+ errors.push({
2617
+ rule: "mime-type-missing",
2618
+ message: "MIME type could not be determined for validation",
2619
+ details: { fileKey: file.key }
2620
+ });
2621
+ }
2622
+ }
2623
+ return errors;
2624
+ }
2625
+
2626
+ // src/validators/file-size.ts
2627
+ function validateFileSize(buffer, opts) {
2628
+ const errors = [];
2629
+ const size = buffer.length;
2630
+ if (opts.minBytes != null && size < opts.minBytes) {
2631
+ errors.push({
2632
+ rule: "min-size",
2633
+ message: `File size ${size} bytes is below minimum ${opts.minBytes} bytes`,
2634
+ details: { size, minBytes: opts.minBytes }
2635
+ });
2636
+ }
2637
+ if (opts.maxBytes != null && size > opts.maxBytes) {
2638
+ errors.push({
2639
+ rule: "max-size",
2640
+ message: `File size ${size} bytes exceeds maximum ${opts.maxBytes} bytes`,
2641
+ details: { size, maxBytes: opts.maxBytes }
2642
+ });
2643
+ }
2644
+ return errors;
2645
+ }
2646
+ function validateDimensions(buffer, opts) {
2647
+ const errors = [];
2648
+ const dims = imageSize(buffer);
2649
+ if (!dims || !dims.width || !dims.height) {
2650
+ return errors;
2651
+ }
2652
+ const { width, height } = dims;
2653
+ if (opts.minWidth != null && width < opts.minWidth) {
2654
+ errors.push({
2655
+ rule: "min-width",
2656
+ message: `Image width ${width}px is below minimum ${opts.minWidth}px`,
2657
+ details: { width, minWidth: opts.minWidth }
2658
+ });
2659
+ }
2660
+ if (opts.maxWidth != null && width > opts.maxWidth) {
2661
+ errors.push({
2662
+ rule: "max-width",
2663
+ message: `Image width ${width}px exceeds maximum ${opts.maxWidth}px`,
2664
+ details: { width, maxWidth: opts.maxWidth }
2665
+ });
2666
+ }
2667
+ if (opts.minHeight != null && height < opts.minHeight) {
2668
+ errors.push({
2669
+ rule: "min-height",
2670
+ message: `Image height ${height}px is below minimum ${opts.minHeight}px`,
2671
+ details: { height, minHeight: opts.minHeight }
2672
+ });
2673
+ }
2674
+ if (opts.maxHeight != null && height > opts.maxHeight) {
2675
+ errors.push({
2676
+ rule: "max-height",
2677
+ message: `Image height ${height}px exceeds maximum ${opts.maxHeight}px`,
2678
+ details: { height, maxHeight: opts.maxHeight }
2679
+ });
2680
+ }
2681
+ return errors;
2682
+ }
2683
+ function validateChecksum(buffer, metadata, opts) {
2684
+ const errors = [];
2685
+ const cfg = opts.checksum;
2686
+ if (!cfg) return errors;
2687
+ const algo = cfg.algorithm;
2688
+ const expected = cfg.metadataKey != null ? metadata[cfg.metadataKey] : void 0;
2689
+ if (expected == null || typeof expected !== "string") {
2690
+ errors.push({
2691
+ rule: "checksum",
2692
+ message: `Expected checksum not found in metadata (key: ${cfg.metadataKey ?? "default"})`,
2693
+ details: { metadataKey: cfg.metadataKey }
2694
+ });
2695
+ return errors;
2696
+ }
2697
+ const hash = crypto.createHash(algo).update(buffer).digest("hex");
2698
+ const expectedNorm = expected.toLowerCase().trim();
2699
+ if (hash.toLowerCase() !== expectedNorm) {
2700
+ errors.push({
2701
+ rule: "checksum",
2702
+ message: `${algo} hash mismatch`,
2703
+ details: { expected: expectedNorm, computed: hash }
2704
+ });
2705
+ }
2706
+ return errors;
2707
+ }
2708
+ async function validateChecksumUniqueness(buffer, fileKey, database, opts) {
2709
+ const errors = [];
2710
+ const cfg = opts.preventDuplicates;
2711
+ if (!cfg) return errors;
2712
+ const algo = "sha256";
2713
+ const hash = crypto.createHash(algo).update(buffer).digest("hex");
2714
+ const existing = await database.findOne({
2715
+ model: "media",
2716
+ where: [{ field: "checksum", value: hash }]
2717
+ });
2718
+ if (existing && existing.id !== fileKey) {
2719
+ errors.push({
2720
+ rule: "duplicate-content",
2721
+ message: `File with same ${algo} checksum already exists: ${existing.id}`,
2722
+ details: { duplicateId: existing.id, checksum: hash, algorithm: algo }
2723
+ });
2724
+ }
2725
+ return errors;
2726
+ }
2727
+
2728
+ // src/validators/security-scanner.ts
2729
+ var INJECTION_PATTERNS = [
2730
+ /(%27)|(')|(--)|(%23)|(#)/i,
2731
+ // SQL basics
2732
+ /(<script)/i,
2733
+ // XSS basics
2734
+ /(\$where)/i,
2735
+ // NoSQL basics
2736
+ /(%|\\x)[0-9a-f]{2}/i
2737
+ // Malicious encoding
2738
+ ];
2739
+ function runSecurityScan(value, path2 = "root") {
2740
+ const errors = [];
2741
+ if (typeof value === "string") {
2742
+ if (INJECTION_PATTERNS.some((p) => p.test(value))) {
2743
+ errors.push({
2744
+ rule: "security-threat",
2745
+ message: `Suspicious activity detected in ${path2}`,
2746
+ details: { path: path2, value: value.length > 50 ? `${value.substring(0, 50)}...` : value }
2747
+ });
2748
+ }
2749
+ } else if (Array.isArray(value)) {
2750
+ value.forEach((item, index) => {
2751
+ errors.push(...runSecurityScan(item, `${path2}[${index}]`));
2752
+ });
2753
+ } else if (value !== null && typeof value === "object") {
2754
+ Object.entries(value).forEach(([key, val]) => {
2755
+ errors.push(...runSecurityScan(val, `${path2}.${key}`));
2756
+ });
2757
+ }
2758
+ return errors;
2759
+ }
2760
+
2761
+ // src/validators/index.ts
2762
+ async function runValidators(buffer, file, metadata, database, opts) {
2763
+ const allErrors = [];
2764
+ allErrors.push(...runSecurityScan(file.key, "fileKey"));
2765
+ if (file.originalName) {
2766
+ allErrors.push(...runSecurityScan(file.originalName, "filename"));
2767
+ }
2768
+ allErrors.push(...runSecurityScan(metadata, "metadata"));
2769
+ if (opts.allowedExtensions || opts.allowedMimeTypes || opts.useMagicBytes !== false) {
2770
+ allErrors.push(...await validateFileType(buffer, file, metadata, opts));
2771
+ }
2772
+ if (opts.minBytes || opts.maxBytes) {
2773
+ allErrors.push(...validateFileSize(buffer, opts));
2774
+ }
2775
+ if (opts.minWidth || opts.maxWidth || opts.minHeight || opts.maxHeight) {
2776
+ allErrors.push(...await validateDimensions(buffer, opts));
2777
+ }
2778
+ if (opts.checksum) {
2779
+ allErrors.push(...validateChecksum(buffer, metadata, opts));
2780
+ }
2781
+ if (opts.preventDuplicates) {
2782
+ allErrors.push(...await validateChecksumUniqueness(buffer, file.key, database, opts));
2783
+ }
2784
+ if (opts.customValidators) {
2785
+ for (const v of opts.customValidators) {
2786
+ const e = await v(buffer, metadata, file.key);
2787
+ allErrors.push(...e);
2788
+ }
2789
+ }
2790
+ return allErrors;
2791
+ }
2792
+
2793
+ // src/runtime/runner.ts
2794
+ var SecurityLogger = {
2795
+ logSuspiciousActivity: (fileKey, errors) => {
2796
+ const threats = errors.filter((e) => e.rule === "security-threat" || e.rule === "magic-bytes");
2797
+ if (threats.length > 0) {
2798
+ console.warn(`[SECURITY ALERT] Suspicious activity on ${fileKey}:`, threats);
2799
+ }
2800
+ }
2801
+ };
2802
+ async function sleep(ms) {
2803
+ return new Promise((resolve) => setTimeout(resolve, ms));
2804
+ }
2805
+ async function fetchWithRetry(storage, fileKey, opts) {
2806
+ const behavior = opts.fileNotFoundBehavior ?? "fail";
2807
+ const retryOpts = opts.retryOptions ?? { maxAttempts: 3, delayMs: 1e3, backoff: "exponential" };
2808
+ const maxAttempts = retryOpts.maxAttempts ?? 3;
2809
+ const delayMs = retryOpts.delayMs ?? 1e3;
2810
+ const backoff = retryOpts.backoff ?? "exponential";
2811
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2812
+ const buffer = await storage.get(fileKey);
2813
+ if (buffer != null) return buffer;
2814
+ if (behavior === "skip") return null;
2815
+ if (behavior === "fail" || attempt === maxAttempts) return null;
2816
+ const wait = backoff === "exponential" ? delayMs * Math.pow(2, attempt - 1) : delayMs * attempt;
2817
+ await sleep(wait);
2818
+ }
2819
+ return null;
2820
+ }
2821
+ async function readBufferForValidation(context) {
2822
+ const fileContent = context.utilities?.fileContent;
2823
+ if (fileContent?.buffer) return fileContent.buffer;
2824
+ if (fileContent?.tempPath) return fs.readFile(fileContent.tempPath);
2825
+ return null;
2826
+ }
2827
+ async function recordValidationResult(database, recordId, valid, errors) {
2828
+ const model = "media_validation_results";
2829
+ const pluginId = "better-media-validation";
2830
+ const data = {
2831
+ mediaId: recordId,
2832
+ valid,
2833
+ pluginId,
2834
+ errors,
2835
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2836
+ };
2837
+ const existing = await database.findOne({
2838
+ model,
2839
+ where: [
2840
+ { field: "mediaId", value: recordId },
2841
+ { field: "pluginId", value: pluginId }
2842
+ ]
2843
+ });
2844
+ if (existing) {
2845
+ await database.update({
2846
+ model,
2847
+ where: [{ field: "id", value: existing.id }],
2848
+ update: data
2849
+ });
2850
+ } else {
2851
+ await database.create({
2852
+ model,
2853
+ data: { id: randomUUID(), ...data }
2854
+ });
2855
+ }
2856
+ }
2857
+ async function runValidation(context, api, opts) {
2858
+ const { file, metadata, storage, database } = context;
2859
+ const fileKey = file.key;
2860
+ let buffer = await readBufferForValidation(context);
2861
+ if (buffer == null) buffer = await fetchWithRetry(storage, fileKey, opts);
2862
+ if (buffer == null) {
2863
+ const notFoundError = {
2864
+ rule: "file-not-found",
2865
+ message: opts.fileNotFoundBehavior === "retry" ? "File not found in storage after retries (e.g. presigned URL upload not complete)" : "File not found in storage",
2866
+ details: { fileKey }
2867
+ };
2868
+ if (opts.fileNotFoundBehavior === "skip") {
2869
+ return;
2870
+ }
2871
+ await recordValidationResult(database, context.recordId, false, [notFoundError]);
2872
+ if (opts.onFailure === "continue" || opts.onFailure === "custom") {
2873
+ if (opts.onFailure === "custom" && opts.onFailureCallback) {
2874
+ const result2 = await opts.onFailureCallback(fileKey, [notFoundError]);
2875
+ if (result2 && result2.valid === false) return result2;
2876
+ }
2877
+ return;
2878
+ }
2879
+ return {
2880
+ valid: false,
2881
+ message: notFoundError.message
2882
+ };
2883
+ }
2884
+ markFileContentVerified(context);
2885
+ const patch = await extractMetadataFromBuffer(buffer, context, opts);
2886
+ const hasTrustedPatch = patch.file && Object.keys(patch.file).length > 0 || patch.checksums && Object.keys(patch.checksums).length > 0 || patch.media && Object.keys(patch.media).length > 0;
2887
+ if (hasTrustedPatch) {
2888
+ api.proposeTrusted(patch);
2889
+ }
2890
+ const errors = await runValidators(buffer, context.file, metadata, database, opts);
2891
+ if (errors.length === 0) {
2892
+ await recordValidationResult(database, context.recordId, true, []);
2893
+ return;
2894
+ }
2895
+ SecurityLogger.logSuspiciousActivity(fileKey, errors);
2896
+ await recordValidationResult(database, context.recordId, false, errors);
2897
+ const message = errors.map((e) => e.message).join("; ");
2898
+ const result = { valid: false, message };
2899
+ switch (opts.onFailure ?? "abort") {
2900
+ case "continue":
2901
+ return;
2902
+ case "custom":
2903
+ if (opts.onFailureCallback) {
2904
+ const customResult = await opts.onFailureCallback(fileKey, errors);
2905
+ if (customResult && customResult.valid === false) return customResult;
2906
+ }
2907
+ return result;
2908
+ case "abort":
2909
+ default:
2910
+ return result;
2911
+ }
2912
+ }
2913
+
2914
+ // src/index.ts
2915
+ function validationPlugin(opts = {}) {
2916
+ const executionMode = opts.executionMode ?? "background";
2917
+ const isBackground = executionMode === "background";
2918
+ return {
2919
+ name: "validation",
2920
+ runtimeManifest: {
2921
+ id: "better-media-validation",
2922
+ version: "1.0.0",
2923
+ trustLevel: "trusted",
2924
+ // Authorized for core metadata (size, mime, checksums)
2925
+ capabilities: ["file.read", "metadata.write.own", "processing.write.own", "trusted.propose"],
2926
+ namespace: "validation"
2927
+ },
2928
+ executionMode,
2929
+ intensive: isBackground,
2930
+ apply(runtime) {
2931
+ runtime.hooks["validation:run"].tap(
2932
+ "validation",
2933
+ async (context, api) => {
2934
+ return runValidation(context, api, opts);
2935
+ },
2936
+ { mode: executionMode }
2937
+ );
2938
+ }
2939
+ };
2940
+ }
2941
+ /*! Bundled license information:
2942
+
2943
+ ieee754/index.js:
2944
+ (*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> *)
2945
+ */
2946
+
2947
+ export { validationPlugin };
2948
+ //# sourceMappingURL=index.mjs.map
2949
+ //# sourceMappingURL=index.mjs.map