Skip to content
Source
Sign inSign up

zipDirectory.ts

75 lines3.4 KB

1export function checkZipDirectory(bytes: ArrayBuffer, entryLimit = 2048) {
2 // Reject oversized directories before JSZip allocates objects for their entries.
3 const view = new DataView(bytes);
4 let directoryEnd = -1;
5 let directorySize = 0;
6 for (
7 let offset = bytes.byteLength - 22;
8 offset >= Math.max(0, bytes.byteLength - 65557);
9 offset--
10 ) {
11 if (view.getUint32(offset, true) !== 0x06054b50) continue;
12 if (offset + 22 + view.getUint16(offset + 20, true) !== bytes.byteLength)
13 throw new Error("This is not a readable ZIP archive.");
14 let count = view.getUint16(offset + 10, true);
15 directorySize = view.getUint32(offset + 12, true);
16 let directoryOffset = view.getUint32(offset + 16, true);
17 directoryEnd = offset;
18 if (
19 [4, 6, 8, 10].some((field) => view.getUint16(offset + field, true) === 0xffff) ||
20 directorySize === 0xffffffff ||
21 directoryOffset === 0xffffffff
22 ) {
23 const locator = offset - 20;
24 const record = locator - 56;
25 // JSZip uses the last locator, including signatures inside the archive comment.
26 for (let candidate = bytes.byteLength - 4; candidate > locator; candidate--)
27 if (view.getUint32(candidate, true) === 0x07064b50)
28 throw new Error("Unsupported ZIP64 directory.");
29 if (
30 record < 0 ||
31 view.getUint32(locator, true) !== 0x07064b50 ||
32 view.getUint32(record, true) !== 0x06064b50 ||
33 view.getBigUint64(record + 4, true) !== 44n ||
34 view.getUint32(record + 16, true) !== 0 ||
35 view.getUint32(record + 20, true) !== 0 ||
36 view.getUint32(locator + 4, true) !== 0 ||
37 view.getUint32(locator + 16, true) !== 1 ||
38 view.getBigUint64(locator + 8, true) !== BigInt(record)
39 )
40 throw new Error("Unsupported ZIP64 directory.");
41 const records = view.getBigUint64(record + 32, true);
42 const size = view.getBigUint64(record + 40, true);
43 const start = view.getBigUint64(record + 48, true);
44 if (
45 records > BigInt(entryLimit) ||
46 size > BigInt(record) ||
47 start > BigInt(record) ||
48 view.getBigUint64(locator + 8, true) !== start + size ||
49 view.getBigUint64(record + 24, true) !== records
50 )
51 throw new Error("This ZIP64 directory is too large or damaged.");
52 count = Number(records);
53 directorySize = Number(size);
54 directoryOffset = Number(start);
55 directoryEnd = record;
56 }
57 if (count > entryLimit) throw new Error("This archive contains more than 2,048 entries.");
58 if (directoryOffset > directoryEnd - directorySize)
59 throw new Error("This ZIP directory is damaged.");
60 break;
61 }
62 if (directoryEnd < 0 || directorySize > directoryEnd)
63 throw new Error("This is not a readable ZIP archive.");
64 let records = 0;
65 for (let offset = directoryEnd - directorySize; offset < directoryEnd;) {
66 if (offset + 46 > directoryEnd || view.getUint32(offset, true) !== 0x02014b50)
67 throw new Error("This ZIP directory is damaged.");
68 if (++records > entryLimit) throw new Error("This archive contains more than 2,048 entries.");
69 const nameLength = view.getUint16(offset + 28, true);
70 if (nameLength > 1024) throw new Error("An archive path is too long to preview.");
71 offset +=
72 46 + nameLength + view.getUint16(offset + 30, true) + view.getUint16(offset + 32, true);
73 if (offset > directoryEnd) throw new Error("This ZIP directory is damaged.");
74 }