-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathzip.js
More file actions
105 lines (94 loc) · 3.36 KB
/
Copy pathzip.js
File metadata and controls
105 lines (94 loc) · 3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// Minimal store-mode (uncompressed) ZIP writer — no dependency, no network call.
// Good enough for bundling a handful of PDF files client-side.
const CRC_TABLE = (() => {
const table = new Uint32Array(256);
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) {
c = c & 1 ? (0xedb88320 ^ (c >>> 1)) : c >>> 1;
}
table[n] = c >>> 0;
}
return table;
})();
function crc32(bytes) {
let crc = 0xffffffff;
for (let i = 0; i < bytes.length; i++) {
crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
}
return (crc ^ 0xffffffff) >>> 0;
}
function dosDateTime(date) {
const time =
((date.getHours() & 0x1f) << 11) |
((date.getMinutes() & 0x3f) << 5) |
((date.getSeconds() >> 1) & 0x1f);
const dosDate =
(((date.getFullYear() - 1980) & 0x7f) << 9) |
(((date.getMonth() + 1) & 0xf) << 5) |
(date.getDate() & 0x1f);
return { time, dosDate };
}
/**
* @param {{name: string, data: Uint8Array}[]} entries
* @returns {Blob}
*/
export function createZip(entries) {
const { time, dosDate } = dosDateTime(new Date());
const localParts = [];
const centralParts = [];
let offset = 0;
for (const entry of entries) {
const nameBytes = new TextEncoder().encode(entry.name);
const data = entry.data;
const crc = crc32(data);
const local = new DataView(new ArrayBuffer(30));
local.setUint32(0, 0x04034b50, true);
local.setUint16(4, 20, true);
local.setUint16(6, 0x0800, true); // bit 11: filename/comment are UTF-8
local.setUint16(8, 0, true); // stored, no compression
local.setUint16(10, time, true);
local.setUint16(12, dosDate, true);
local.setUint32(14, crc, true);
local.setUint32(18, data.length, true);
local.setUint32(22, data.length, true);
local.setUint16(26, nameBytes.length, true);
local.setUint16(28, 0, true);
localParts.push(new Uint8Array(local.buffer), nameBytes, data);
const central = new DataView(new ArrayBuffer(46));
central.setUint32(0, 0x02014b50, true);
central.setUint16(4, 20, true);
central.setUint16(6, 20, true);
central.setUint16(8, 0x0800, true); // bit 11: filename/comment are UTF-8
central.setUint16(10, 0, true);
central.setUint16(12, time, true);
central.setUint16(14, dosDate, true);
central.setUint32(16, crc, true);
central.setUint32(20, data.length, true);
central.setUint32(24, data.length, true);
central.setUint16(28, nameBytes.length, true);
central.setUint16(30, 0, true);
central.setUint16(32, 0, true);
central.setUint16(34, 0, true);
central.setUint16(36, 0, true);
central.setUint32(38, 0, true);
central.setUint32(42, offset, true);
centralParts.push(new Uint8Array(central.buffer), nameBytes);
offset += local.buffer.byteLength + nameBytes.length + data.length;
}
const centralStart = offset;
let centralSize = 0;
for (const part of centralParts) centralSize += part.length;
const end = new DataView(new ArrayBuffer(22));
end.setUint32(0, 0x06054b50, true);
end.setUint16(4, 0, true);
end.setUint16(6, 0, true);
end.setUint16(8, entries.length, true);
end.setUint16(10, entries.length, true);
end.setUint32(12, centralSize, true);
end.setUint32(16, centralStart, true);
end.setUint16(20, 0, true);
return new Blob([...localParts, ...centralParts, new Uint8Array(end.buffer)], {
type: "application/zip",
});
}