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