file-type 21.3.4 → 22.0.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/package.json +36 -53
- package/readme.md +34 -104
- package/source/detectors/asf.js +127 -0
- package/source/detectors/ebml.js +120 -0
- package/source/detectors/png.js +123 -0
- package/source/detectors/zip.js +643 -0
- package/{core.d.ts → source/index.d.ts} +49 -22
- package/{core.js → source/index.js} +151 -1138
- package/source/index.test-d.ts +53 -0
- package/source/parser.js +65 -0
- package/{supported.js → source/supported.js} +14 -6
- package/{util.js → source/tokens.js} +2 -2
- package/index.d.ts +0 -98
- package/index.js +0 -163
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as Token from 'token-types';
|
|
2
|
+
import * as strtok3 from 'strtok3/core';
|
|
3
|
+
import {
|
|
4
|
+
ParserHardLimitError,
|
|
5
|
+
safeIgnore,
|
|
6
|
+
hasUnknownFileSize,
|
|
7
|
+
hasExceededUnknownSizeScanBudget,
|
|
8
|
+
} from '../parser.js';
|
|
9
|
+
|
|
10
|
+
const maximumPngChunkCount = 512;
|
|
11
|
+
const maximumPngStreamScanBudgetInBytes = 16 * 1024 * 1024;
|
|
12
|
+
const maximumPngChunkSizeInBytes = 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
function isPngAncillaryChunk(type) {
|
|
15
|
+
return (type.codePointAt(0) & 0x20) !== 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function detectPng(tokenizer) {
|
|
19
|
+
const pngFileType = {
|
|
20
|
+
ext: 'png',
|
|
21
|
+
mime: 'image/png',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const apngFileType = {
|
|
25
|
+
ext: 'apng',
|
|
26
|
+
mime: 'image/apng',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// APNG format (https://wiki.mozilla.org/APNG_Specification)
|
|
30
|
+
// 1. Find the first IDAT (image data) chunk (49 44 41 54)
|
|
31
|
+
// 2. Check if there is an "acTL" chunk before the IDAT one (61 63 54 4C)
|
|
32
|
+
|
|
33
|
+
// Offset calculated as follows:
|
|
34
|
+
// - 8 bytes: PNG signature
|
|
35
|
+
// - 4 (length) + 4 (chunk type) + 13 (chunk data) + 4 (CRC): IHDR chunk
|
|
36
|
+
|
|
37
|
+
await tokenizer.ignore(8); // ignore PNG signature
|
|
38
|
+
|
|
39
|
+
async function readChunkHeader() {
|
|
40
|
+
return {
|
|
41
|
+
length: await tokenizer.readToken(Token.INT32_BE),
|
|
42
|
+
type: await tokenizer.readToken(new Token.StringType(4, 'latin1')),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isUnknownPngStream = hasUnknownFileSize(tokenizer);
|
|
47
|
+
const pngScanStart = tokenizer.position;
|
|
48
|
+
let pngChunkCount = 0;
|
|
49
|
+
let hasSeenImageHeader = false;
|
|
50
|
+
do {
|
|
51
|
+
pngChunkCount++;
|
|
52
|
+
if (pngChunkCount > maximumPngChunkCount) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (hasExceededUnknownSizeScanBudget(tokenizer, pngScanStart, maximumPngStreamScanBudgetInBytes)) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const previousPosition = tokenizer.position;
|
|
61
|
+
const chunk = await readChunkHeader();
|
|
62
|
+
if (chunk.length < 0) {
|
|
63
|
+
return; // Invalid chunk length
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (chunk.type === 'IHDR') {
|
|
67
|
+
// PNG requires the first real image header to be a 13-byte IHDR chunk.
|
|
68
|
+
if (chunk.length !== 13) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
hasSeenImageHeader = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
switch (chunk.type) {
|
|
76
|
+
case 'IDAT':
|
|
77
|
+
return pngFileType;
|
|
78
|
+
case 'acTL':
|
|
79
|
+
return apngFileType;
|
|
80
|
+
default:
|
|
81
|
+
if (
|
|
82
|
+
!hasSeenImageHeader
|
|
83
|
+
&& chunk.type !== 'CgBI'
|
|
84
|
+
) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
isUnknownPngStream
|
|
90
|
+
&& chunk.length > maximumPngChunkSizeInBytes
|
|
91
|
+
) {
|
|
92
|
+
// Avoid huge attacker-controlled skips when probing unknown-size streams.
|
|
93
|
+
return hasSeenImageHeader && isPngAncillaryChunk(chunk.type) ? pngFileType : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await safeIgnore(tokenizer, chunk.length + 4, {
|
|
98
|
+
maximumLength: isUnknownPngStream ? maximumPngChunkSizeInBytes + 4 : tokenizer.fileInfo.size,
|
|
99
|
+
reason: 'PNG chunk payload',
|
|
100
|
+
}); // Ignore chunk-data + CRC
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (
|
|
103
|
+
!isUnknownPngStream
|
|
104
|
+
&& (
|
|
105
|
+
error instanceof ParserHardLimitError
|
|
106
|
+
|| error instanceof strtok3.EndOfStreamError
|
|
107
|
+
)
|
|
108
|
+
) {
|
|
109
|
+
return pngFileType;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Safeguard against malformed files: bail if the position did not advance.
|
|
117
|
+
if (tokenizer.position <= previousPosition) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
} while (tokenizer.position + 8 < tokenizer.fileInfo.size);
|
|
121
|
+
|
|
122
|
+
return pngFileType;
|
|
123
|
+
}
|