single-file-core 1.5.91 → 1.5.93
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/core/helper.js +3 -1
- package/core/lib/sha.js +242 -0
- package/package.json +1 -1
- package/processors/compression/compression.js +122 -16
package/core/helper.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import * as cssUnescape from "./../vendor/css-unescape.js";
|
|
25
|
+
import * as sha from "./lib/sha.js";
|
|
25
26
|
import * as hooksFrames from "./../processors/hooks/content/content-hooks-frames.js";
|
|
26
27
|
import * as infobar from "./infobar.js";
|
|
27
28
|
import {
|
|
@@ -793,7 +794,8 @@ function getContentSize(content) {
|
|
|
793
794
|
|
|
794
795
|
async function digest(algo, text) {
|
|
795
796
|
try {
|
|
796
|
-
const
|
|
797
|
+
const data = new TextEncoder("utf-8").encode(text);
|
|
798
|
+
const hash = globalThis.crypto && crypto.subtle ? await crypto.subtle.digest(algo, data) : sha.digest(algo, data);
|
|
797
799
|
return hex(hash);
|
|
798
800
|
// eslint-disable-next-line no-unused-vars
|
|
799
801
|
} catch (error) {
|
package/core/lib/sha.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2026 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const H1 = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];
|
|
25
|
+
const H256 = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
|
26
|
+
const K256 = [
|
|
27
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
28
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
29
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
30
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
31
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
32
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
33
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
34
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
35
|
+
];
|
|
36
|
+
const H384 = [
|
|
37
|
+
0xcbbb9d5dc1059ed8n, 0x629a292a367cd507n, 0x9159015a3070dd17n, 0x152fecd8f70e5939n,
|
|
38
|
+
0x67332667ffc00b31n, 0x8eb44a8768581511n, 0xdb0c2e0d64f98fa7n, 0x47b5481dbefa4fa4n
|
|
39
|
+
];
|
|
40
|
+
const H512 = [
|
|
41
|
+
0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n,
|
|
42
|
+
0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n
|
|
43
|
+
];
|
|
44
|
+
const K512 = [
|
|
45
|
+
0x428a2f98d728ae22n, 0x7137449123ef65cdn, 0xb5c0fbcfec4d3b2fn, 0xe9b5dba58189dbbcn,
|
|
46
|
+
0x3956c25bf348b538n, 0x59f111f1b605d019n, 0x923f82a4af194f9bn, 0xab1c5ed5da6d8118n,
|
|
47
|
+
0xd807aa98a3030242n, 0x12835b0145706fben, 0x243185be4ee4b28cn, 0x550c7dc3d5ffb4e2n,
|
|
48
|
+
0x72be5d74f27b896fn, 0x80deb1fe3b1696b1n, 0x9bdc06a725c71235n, 0xc19bf174cf692694n,
|
|
49
|
+
0xe49b69c19ef14ad2n, 0xefbe4786384f25e3n, 0x0fc19dc68b8cd5b5n, 0x240ca1cc77ac9c65n,
|
|
50
|
+
0x2de92c6f592b0275n, 0x4a7484aa6ea6e483n, 0x5cb0a9dcbd41fbd4n, 0x76f988da831153b5n,
|
|
51
|
+
0x983e5152ee66dfabn, 0xa831c66d2db43210n, 0xb00327c898fb213fn, 0xbf597fc7beef0ee4n,
|
|
52
|
+
0xc6e00bf33da88fc2n, 0xd5a79147930aa725n, 0x06ca6351e003826fn, 0x142929670a0e6e70n,
|
|
53
|
+
0x27b70a8546d22ffcn, 0x2e1b21385c26c926n, 0x4d2c6dfc5ac42aedn, 0x53380d139d95b3dfn,
|
|
54
|
+
0x650a73548baf63den, 0x766a0abb3c77b2a8n, 0x81c2c92e47edaee6n, 0x92722c851482353bn,
|
|
55
|
+
0xa2bfe8a14cf10364n, 0xa81a664bbc423001n, 0xc24b8b70d0f89791n, 0xc76c51a30654be30n,
|
|
56
|
+
0xd192e819d6ef5218n, 0xd69906245565a910n, 0xf40e35855771202an, 0x106aa07032bbd1b8n,
|
|
57
|
+
0x19a4c116b8d2d0c8n, 0x1e376c085141ab53n, 0x2748774cdf8eeb99n, 0x34b0bcb5e19b48a8n,
|
|
58
|
+
0x391c0cb3c5c95a63n, 0x4ed8aa4ae3418acbn, 0x5b9cca4f7763e373n, 0x682e6ff3d6b2b8a3n,
|
|
59
|
+
0x748f82ee5defb2fcn, 0x78a5636f43172f60n, 0x84c87814a1f0ab72n, 0x8cc702081a6439ecn,
|
|
60
|
+
0x90befffa23631e28n, 0xa4506cebde82bde9n, 0xbef9a3f7b2c67915n, 0xc67178f2e372532bn,
|
|
61
|
+
0xca273eceea26619cn, 0xd186b8c721c0c207n, 0xeada7dd6cde0eb1en, 0xf57d4f7fee6ed178n,
|
|
62
|
+
0x06f067aa72176fban, 0x0a637dc5a2c898a6n, 0x113f9804bef90daen, 0x1b710b35131c471bn,
|
|
63
|
+
0x28db77f523047d84n, 0x32caab7b40c72493n, 0x3c9ebe0a15c9bebcn, 0x431d67c49c100d4cn,
|
|
64
|
+
0x4cc5d4becb3e42b6n, 0x597f299cfc657e2an, 0x5fcb6fab3ad6faecn, 0x6c44198c4a475817n
|
|
65
|
+
];
|
|
66
|
+
const MASK_64 = 0xffffffffffffffffn;
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
digest
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function digest(algorithm, data) {
|
|
73
|
+
if (algorithm == "SHA-1") {
|
|
74
|
+
return digestSha1(data);
|
|
75
|
+
} else if (algorithm == "SHA-256") {
|
|
76
|
+
return digestSha256(data);
|
|
77
|
+
} else if (algorithm == "SHA-384") {
|
|
78
|
+
return digestSha512(data, H384, 6);
|
|
79
|
+
} else if (algorithm == "SHA-512") {
|
|
80
|
+
return digestSha512(data, H512, 8);
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error("Unsupported algorithm: " + algorithm);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function digestSha1(data) {
|
|
87
|
+
const state = Int32Array.from(H1);
|
|
88
|
+
const schedule = new Int32Array(80);
|
|
89
|
+
const view = padMessage(data, 64);
|
|
90
|
+
for (let blockOffset = 0; blockOffset < view.byteLength; blockOffset += 64) {
|
|
91
|
+
for (let index = 0; index < 16; index++) {
|
|
92
|
+
schedule[index] = view.getUint32(blockOffset + index * 4);
|
|
93
|
+
}
|
|
94
|
+
for (let index = 16; index < 80; index++) {
|
|
95
|
+
const value = schedule[index - 3] ^ schedule[index - 8] ^ schedule[index - 14] ^ schedule[index - 16];
|
|
96
|
+
schedule[index] = (value << 1) | (value >>> 31);
|
|
97
|
+
}
|
|
98
|
+
let [a, b, c, d, e] = state;
|
|
99
|
+
for (let index = 0; index < 80; index++) {
|
|
100
|
+
let mix, constant;
|
|
101
|
+
if (index < 20) {
|
|
102
|
+
mix = (b & c) | (~b & d);
|
|
103
|
+
constant = 0x5a827999;
|
|
104
|
+
} else if (index < 40) {
|
|
105
|
+
mix = b ^ c ^ d;
|
|
106
|
+
constant = 0x6ed9eba1;
|
|
107
|
+
} else if (index < 60) {
|
|
108
|
+
mix = (b & c) | (b & d) | (c & d);
|
|
109
|
+
constant = 0x8f1bbcdc;
|
|
110
|
+
} else {
|
|
111
|
+
mix = b ^ c ^ d;
|
|
112
|
+
constant = 0xca62c1d6;
|
|
113
|
+
}
|
|
114
|
+
const value = (((a << 5) | (a >>> 27)) + mix + e + constant + schedule[index]) | 0;
|
|
115
|
+
e = d;
|
|
116
|
+
d = c;
|
|
117
|
+
c = (b << 30) | (b >>> 2);
|
|
118
|
+
b = a;
|
|
119
|
+
a = value;
|
|
120
|
+
}
|
|
121
|
+
state[0] += a;
|
|
122
|
+
state[1] += b;
|
|
123
|
+
state[2] += c;
|
|
124
|
+
state[3] += d;
|
|
125
|
+
state[4] += e;
|
|
126
|
+
}
|
|
127
|
+
return serializeState32(state);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function digestSha256(data) {
|
|
131
|
+
const state = Int32Array.from(H256);
|
|
132
|
+
const schedule = new Int32Array(64);
|
|
133
|
+
const view = padMessage(data, 64);
|
|
134
|
+
for (let blockOffset = 0; blockOffset < view.byteLength; blockOffset += 64) {
|
|
135
|
+
for (let index = 0; index < 16; index++) {
|
|
136
|
+
schedule[index] = view.getUint32(blockOffset + index * 4);
|
|
137
|
+
}
|
|
138
|
+
for (let index = 16; index < 64; index++) {
|
|
139
|
+
const value0 = schedule[index - 15];
|
|
140
|
+
const value1 = schedule[index - 2];
|
|
141
|
+
const sigma0 = rotateRight32(value0, 7) ^ rotateRight32(value0, 18) ^ (value0 >>> 3);
|
|
142
|
+
const sigma1 = rotateRight32(value1, 17) ^ rotateRight32(value1, 19) ^ (value1 >>> 10);
|
|
143
|
+
schedule[index] = schedule[index - 16] + sigma0 + schedule[index - 7] + sigma1;
|
|
144
|
+
}
|
|
145
|
+
let [a, b, c, d, e, f, g, h] = state;
|
|
146
|
+
for (let index = 0; index < 64; index++) {
|
|
147
|
+
const sum1 = rotateRight32(e, 6) ^ rotateRight32(e, 11) ^ rotateRight32(e, 25);
|
|
148
|
+
const choice = (e & f) ^ (~e & g);
|
|
149
|
+
const value1 = (h + sum1 + choice + K256[index] + schedule[index]) | 0;
|
|
150
|
+
const sum0 = rotateRight32(a, 2) ^ rotateRight32(a, 13) ^ rotateRight32(a, 22);
|
|
151
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
152
|
+
const value2 = (sum0 + majority) | 0;
|
|
153
|
+
h = g;
|
|
154
|
+
g = f;
|
|
155
|
+
f = e;
|
|
156
|
+
e = (d + value1) | 0;
|
|
157
|
+
d = c;
|
|
158
|
+
c = b;
|
|
159
|
+
b = a;
|
|
160
|
+
a = (value1 + value2) | 0;
|
|
161
|
+
}
|
|
162
|
+
state[0] += a;
|
|
163
|
+
state[1] += b;
|
|
164
|
+
state[2] += c;
|
|
165
|
+
state[3] += d;
|
|
166
|
+
state[4] += e;
|
|
167
|
+
state[5] += f;
|
|
168
|
+
state[6] += g;
|
|
169
|
+
state[7] += h;
|
|
170
|
+
}
|
|
171
|
+
return serializeState32(state);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function digestSha512(data, initialState, stateLength) {
|
|
175
|
+
const state = Array.from(initialState);
|
|
176
|
+
const schedule = new Array(80);
|
|
177
|
+
const view = padMessage(data, 128);
|
|
178
|
+
for (let blockOffset = 0; blockOffset < view.byteLength; blockOffset += 128) {
|
|
179
|
+
for (let index = 0; index < 16; index++) {
|
|
180
|
+
schedule[index] = (BigInt(view.getUint32(blockOffset + index * 8)) << 32n) | BigInt(view.getUint32(blockOffset + index * 8 + 4));
|
|
181
|
+
}
|
|
182
|
+
for (let index = 16; index < 80; index++) {
|
|
183
|
+
const value0 = schedule[index - 15];
|
|
184
|
+
const value1 = schedule[index - 2];
|
|
185
|
+
const sigma0 = rotateRight64(value0, 1n) ^ rotateRight64(value0, 8n) ^ (value0 >> 7n);
|
|
186
|
+
const sigma1 = rotateRight64(value1, 19n) ^ rotateRight64(value1, 61n) ^ (value1 >> 6n);
|
|
187
|
+
schedule[index] = (schedule[index - 16] + sigma0 + schedule[index - 7] + sigma1) & MASK_64;
|
|
188
|
+
}
|
|
189
|
+
let [a, b, c, d, e, f, g, h] = state;
|
|
190
|
+
for (let index = 0; index < 80; index++) {
|
|
191
|
+
const sum1 = rotateRight64(e, 14n) ^ rotateRight64(e, 18n) ^ rotateRight64(e, 41n);
|
|
192
|
+
const choice = (e & f) ^ (~e & g);
|
|
193
|
+
const value1 = (h + sum1 + choice + K512[index] + schedule[index]) & MASK_64;
|
|
194
|
+
const sum0 = rotateRight64(a, 28n) ^ rotateRight64(a, 34n) ^ rotateRight64(a, 39n);
|
|
195
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
196
|
+
const value2 = (sum0 + majority) & MASK_64;
|
|
197
|
+
h = g;
|
|
198
|
+
g = f;
|
|
199
|
+
f = e;
|
|
200
|
+
e = (d + value1) & MASK_64;
|
|
201
|
+
d = c;
|
|
202
|
+
c = b;
|
|
203
|
+
b = a;
|
|
204
|
+
a = (value1 + value2) & MASK_64;
|
|
205
|
+
}
|
|
206
|
+
const values = [a, b, c, d, e, f, g, h];
|
|
207
|
+
values.forEach((value, index) => state[index] = (state[index] + value) & MASK_64);
|
|
208
|
+
}
|
|
209
|
+
const buffer = new ArrayBuffer(stateLength * 8);
|
|
210
|
+
const outputView = new DataView(buffer);
|
|
211
|
+
for (let index = 0; index < stateLength; index++) {
|
|
212
|
+
outputView.setUint32(index * 8, Number(state[index] >> 32n));
|
|
213
|
+
outputView.setUint32(index * 8 + 4, Number(state[index] & 0xffffffffn));
|
|
214
|
+
}
|
|
215
|
+
return buffer;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function padMessage(data, blockLength) {
|
|
219
|
+
const paddedLength = Math.ceil((data.length + 1 + blockLength / 8) / blockLength) * blockLength;
|
|
220
|
+
const padded = new Uint8Array(paddedLength);
|
|
221
|
+
padded.set(data);
|
|
222
|
+
padded[data.length] = 0x80;
|
|
223
|
+
const view = new DataView(padded.buffer);
|
|
224
|
+
view.setUint32(paddedLength - 8, Math.floor(data.length / 0x20000000));
|
|
225
|
+
view.setUint32(paddedLength - 4, data.length << 3);
|
|
226
|
+
return view;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function rotateRight32(value, count) {
|
|
230
|
+
return (value >>> count) | (value << (32 - count));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function rotateRight64(value, count) {
|
|
234
|
+
return ((value >> count) | (value << (64n - count))) & MASK_64;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function serializeState32(state) {
|
|
238
|
+
const buffer = new ArrayBuffer(state.length * 4);
|
|
239
|
+
const view = new DataView(buffer);
|
|
240
|
+
state.forEach((value, index) => view.setUint32(index * 4, value));
|
|
241
|
+
return buffer;
|
|
242
|
+
}
|
package/package.json
CHANGED
|
@@ -86,6 +86,12 @@ const CRC32_TABLE = new Uint32Array(256).map((_, indexTable) => {
|
|
|
86
86
|
const PNG_IEND_LENGTH = 12;
|
|
87
87
|
const PNG_SIGNATURE_LENGTH = 8;
|
|
88
88
|
const PNG_IHDR_LENGTH = 25;
|
|
89
|
+
const PDF_ENTRY_FILENAME = "page.pdf";
|
|
90
|
+
const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
91
|
+
const CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
|
|
92
|
+
const END_OF_CENTRAL_DIR_SIGNATURE = 0x06054b50;
|
|
93
|
+
const ZIP64_END_OF_CENTRAL_DIR_SIGNATURE = 0x06064b50;
|
|
94
|
+
const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE = 0x07064b50;
|
|
89
95
|
|
|
90
96
|
const browser = globalThis.browser;
|
|
91
97
|
|
|
@@ -112,7 +118,7 @@ async function process(pageData, options, lastModDate = new Date()) {
|
|
|
112
118
|
async function createArchive(pageData, options, script, writeEntries, lastModDate = new Date()) {
|
|
113
119
|
const zipDataWriter = new Uint8ArrayWriter();
|
|
114
120
|
zipDataWriter.init();
|
|
115
|
-
let extraDataOffset, extraData, embeddedImageDataOffset, endTag;
|
|
121
|
+
let extraDataOffset, extraData, embeddedImageDataOffset, endTag, pdfEntry;
|
|
116
122
|
if (options.embeddedImage) {
|
|
117
123
|
options.embeddedImage = new Uint8Array(options.embeddedImage);
|
|
118
124
|
const embeddedImageData = options.embeddedImage.slice(PNG_SIGNATURE_LENGTH + PNG_IHDR_LENGTH, options.embeddedImage.length - PNG_IEND_LENGTH);
|
|
@@ -122,7 +128,13 @@ async function createArchive(pageData, options, script, writeEntries, lastModDat
|
|
|
122
128
|
const tagIndex = EMBEDDED_DATA_REGEXPS.slice(0, -1).findIndex(tests => !embeddedImageText.match(tests[1]));
|
|
123
129
|
let startTag;
|
|
124
130
|
[startTag, endTag] = tagIndex == -1 ? ["", ""] : EMBEDDED_DATA_TAGS[tagIndex];
|
|
125
|
-
const
|
|
131
|
+
const startHTMLData = getStartHTMLArray(pageData, options, lastModDate, startTag);
|
|
132
|
+
const htmlArray = startHTMLData.htmlArray;
|
|
133
|
+
if (startHTMLData.pdfEntry) {
|
|
134
|
+
pdfEntry = startHTMLData.pdfEntry;
|
|
135
|
+
// the htmlArray starts after the 4-byte length and the 8-byte type of the tEXt chunk
|
|
136
|
+
pdfEntry.offset += zipDataWriter.offset + 12;
|
|
137
|
+
}
|
|
126
138
|
const hmtlData = new Uint8Array([...getLength(htmlArray.length + 4), ...[0x74, 0x45, 0x58, 0x74, 0x50, 0x4e, 0x47, 0], ...htmlArray]);
|
|
127
139
|
await writeData(zipDataWriter.writable, hmtlData);
|
|
128
140
|
await writeData(zipDataWriter.writable, getCRC32(hmtlData, 4));
|
|
@@ -140,7 +152,9 @@ async function createArchive(pageData, options, script, writeEntries, lastModDat
|
|
|
140
152
|
}
|
|
141
153
|
}
|
|
142
154
|
if (options.selfExtractingArchive) {
|
|
143
|
-
|
|
155
|
+
const prependedData = await prependHTMLData(pageData, zipDataWriter, script, options, lastModDate);
|
|
156
|
+
extraDataOffset = prependedData.extraDataOffset;
|
|
157
|
+
pdfEntry = pdfEntry || prependedData.pdfEntry;
|
|
144
158
|
} else if (!options.embeddedImage && options.embeddedPdf) {
|
|
145
159
|
await writeData(zipDataWriter.writable, new Uint8Array(options.embeddedPdf));
|
|
146
160
|
}
|
|
@@ -151,7 +165,17 @@ async function createArchive(pageData, options, script, writeEntries, lastModDat
|
|
|
151
165
|
const startOffset = zipDataWriter.offset;
|
|
152
166
|
const zipWriter = new ZipWriter({ writable: zipDataWriter.writable, size: startOffset }, { bufferedWrite: true, keepOrder: true, lastModDate, useCompressionStream: true });
|
|
153
167
|
await writeEntries(zipWriter);
|
|
168
|
+
if (pdfEntry) {
|
|
169
|
+
// the record is written where the central directory will start so that the PDF is listed
|
|
170
|
+
// first; the ZipWriter is unaware of these bytes, so the central directory offset it
|
|
171
|
+
// stores in the end of central directory record points here
|
|
172
|
+
new DataView(pdfEntry.centralRecord.buffer).setUint32(42, pdfEntry.offset, true);
|
|
173
|
+
await writeData(zipDataWriter.writable, pdfEntry.centralRecord);
|
|
174
|
+
}
|
|
154
175
|
await zipWriter.close(undefined, { preventClose: true });
|
|
176
|
+
if (pdfEntry) {
|
|
177
|
+
patchEndOfCentralDirectory(zipDataWriter, pdfEntry.centralRecord.length);
|
|
178
|
+
}
|
|
155
179
|
const data = zipDataWriter.getData();
|
|
156
180
|
if (options.selfExtractingArchive) {
|
|
157
181
|
const lfCodes = [];
|
|
@@ -248,13 +272,85 @@ async function createArchive(pageData, options, script, writeEntries, lastModDat
|
|
|
248
272
|
|
|
249
273
|
function getCRC32(data, indexData = 0) {
|
|
250
274
|
const crcArray = new Uint8Array(4);
|
|
275
|
+
setUint32(crcArray, getCRC32Value(data, indexData));
|
|
276
|
+
return crcArray;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getCRC32Value(data, indexData = 0) {
|
|
251
280
|
let crc = -1;
|
|
252
281
|
for (; indexData < data.length; indexData++) {
|
|
253
282
|
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ data[indexData]) & 0xff];
|
|
254
283
|
}
|
|
255
|
-
crc
|
|
256
|
-
|
|
257
|
-
|
|
284
|
+
return (crc ^ -1) >>> 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getPDFEntry(embeddedPdf, lastModDate = new Date()) {
|
|
288
|
+
const filename = new TextEncoder().encode(PDF_ENTRY_FILENAME);
|
|
289
|
+
const crc32 = getCRC32Value(embeddedPdf);
|
|
290
|
+
const dosTime = (lastModDate.getHours() << 11) | (lastModDate.getMinutes() << 5) | (lastModDate.getSeconds() >> 1);
|
|
291
|
+
const dosDate = (Math.max(0, lastModDate.getFullYear() - 1980) << 9) | ((lastModDate.getMonth() + 1) << 5) | lastModDate.getDate();
|
|
292
|
+
const localHeader = new Uint8Array(30 + filename.length);
|
|
293
|
+
const localHeaderView = new DataView(localHeader.buffer);
|
|
294
|
+
localHeaderView.setUint32(0, LOCAL_FILE_HEADER_SIGNATURE, true);
|
|
295
|
+
localHeaderView.setUint16(4, 20, true);
|
|
296
|
+
localHeaderView.setUint16(10, dosTime, true);
|
|
297
|
+
localHeaderView.setUint16(12, dosDate, true);
|
|
298
|
+
localHeaderView.setUint32(14, crc32, true);
|
|
299
|
+
localHeaderView.setUint32(18, embeddedPdf.length, true);
|
|
300
|
+
localHeaderView.setUint32(22, embeddedPdf.length, true);
|
|
301
|
+
localHeaderView.setUint16(26, filename.length, true);
|
|
302
|
+
localHeader.set(filename, 30);
|
|
303
|
+
const centralRecord = new Uint8Array(46 + filename.length);
|
|
304
|
+
const centralRecordView = new DataView(centralRecord.buffer);
|
|
305
|
+
centralRecordView.setUint32(0, CENTRAL_FILE_HEADER_SIGNATURE, true);
|
|
306
|
+
centralRecordView.setUint16(4, 0x0300, true);
|
|
307
|
+
centralRecordView.setUint16(6, 20, true);
|
|
308
|
+
centralRecordView.setUint16(12, dosTime, true);
|
|
309
|
+
centralRecordView.setUint16(14, dosDate, true);
|
|
310
|
+
centralRecordView.setUint32(16, crc32, true);
|
|
311
|
+
centralRecordView.setUint32(20, embeddedPdf.length, true);
|
|
312
|
+
centralRecordView.setUint32(24, embeddedPdf.length, true);
|
|
313
|
+
centralRecordView.setUint16(28, filename.length, true);
|
|
314
|
+
centralRecordView.setUint32(38, 0o100644 << 16, true);
|
|
315
|
+
centralRecord.set(filename, 46);
|
|
316
|
+
return { localHeader, centralRecord };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function patchEndOfCentralDirectory(zipDataWriter, centralRecordLength) {
|
|
320
|
+
const view = new DataView(zipDataWriter.array.buffer);
|
|
321
|
+
const offsetEOCD = zipDataWriter.offset - 22;
|
|
322
|
+
if (view.getUint32(offsetEOCD, true) != END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const entriesOnDisk = view.getUint16(offsetEOCD + 8, true);
|
|
326
|
+
const totalEntries = view.getUint16(offsetEOCD + 10, true);
|
|
327
|
+
const centralDirectorySize = view.getUint32(offsetEOCD + 12, true);
|
|
328
|
+
const offsetLocator = offsetEOCD - 20;
|
|
329
|
+
let offsetZip64EOCD;
|
|
330
|
+
if (offsetLocator >= 0 && view.getUint32(offsetLocator, true) == ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
|
|
331
|
+
// the offset stored in the locator does not account for the PDF record either
|
|
332
|
+
offsetZip64EOCD = Number(view.getBigUint64(offsetLocator + 8, true)) + centralRecordLength;
|
|
333
|
+
if (view.getUint32(offsetZip64EOCD, true) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
} else if (entriesOnDisk + 1 >= 0xFFFF || totalEntries + 1 >= 0xFFFF || centralDirectorySize + centralRecordLength >= 0xFFFFFFFF) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (entriesOnDisk != 0xFFFF) {
|
|
340
|
+
view.setUint16(offsetEOCD + 8, entriesOnDisk + 1, true);
|
|
341
|
+
}
|
|
342
|
+
if (totalEntries != 0xFFFF) {
|
|
343
|
+
view.setUint16(offsetEOCD + 10, totalEntries + 1, true);
|
|
344
|
+
}
|
|
345
|
+
if (centralDirectorySize != 0xFFFFFFFF) {
|
|
346
|
+
view.setUint32(offsetEOCD + 12, centralDirectorySize + centralRecordLength, true);
|
|
347
|
+
}
|
|
348
|
+
if (offsetZip64EOCD !== undefined) {
|
|
349
|
+
view.setBigUint64(offsetLocator + 8, BigInt(offsetZip64EOCD), true);
|
|
350
|
+
view.setBigUint64(offsetZip64EOCD + 24, view.getBigUint64(offsetZip64EOCD + 24, true) + 1n, true);
|
|
351
|
+
view.setBigUint64(offsetZip64EOCD + 32, view.getBigUint64(offsetZip64EOCD + 32, true) + 1n, true);
|
|
352
|
+
view.setBigUint64(offsetZip64EOCD + 40, view.getBigUint64(offsetZip64EOCD + 40, true) + BigInt(centralRecordLength), true);
|
|
353
|
+
}
|
|
258
354
|
}
|
|
259
355
|
|
|
260
356
|
function getLength(length) {
|
|
@@ -270,10 +366,16 @@ function setUint32(data, value) {
|
|
|
270
366
|
data[3] = value;
|
|
271
367
|
}
|
|
272
368
|
|
|
273
|
-
async function prependHTMLData(pageData, zipDataWriter, script, options) {
|
|
369
|
+
async function prependHTMLData(pageData, zipDataWriter, script, options, lastModDate) {
|
|
274
370
|
let pageContent = "";
|
|
371
|
+
let pdfEntry;
|
|
275
372
|
if (!options.embeddedImage) {
|
|
276
|
-
|
|
373
|
+
const startHTMLData = getStartHTMLArray(pageData, options, lastModDate);
|
|
374
|
+
if (startHTMLData.pdfEntry) {
|
|
375
|
+
pdfEntry = startHTMLData.pdfEntry;
|
|
376
|
+
pdfEntry.offset += zipDataWriter.offset;
|
|
377
|
+
}
|
|
378
|
+
await writeData(zipDataWriter.writable, startHTMLData.htmlArray);
|
|
277
379
|
}
|
|
278
380
|
pageContent += "<div id=sfz-wait-message>Please wait...</div>";
|
|
279
381
|
if (options.extractDataFromPage) {
|
|
@@ -341,10 +443,10 @@ async function prependHTMLData(pageData, zipDataWriter, script, options) {
|
|
|
341
443
|
pageContent += (extraData ? " " : "") + startTag;
|
|
342
444
|
const extraDataOffset = startTag.length + extraData.length + (extraData ? 1 : 0);
|
|
343
445
|
await writeData(zipDataWriter.writable, (new TextEncoder()).encode(pageContent));
|
|
344
|
-
return extraDataOffset;
|
|
446
|
+
return { extraDataOffset, pdfEntry };
|
|
345
447
|
}
|
|
346
448
|
|
|
347
|
-
function getStartHTMLArray(pageData, options, startTag = "") {
|
|
449
|
+
function getStartHTMLArray(pageData, options, lastModDate, startTag = "") {
|
|
348
450
|
let html = "";
|
|
349
451
|
if (options.includeBOM && !options.extractDataFromPage && !options.embeddedImage) {
|
|
350
452
|
html += "\ufeff";
|
|
@@ -355,21 +457,25 @@ function getStartHTMLArray(pageData, options, startTag = "") {
|
|
|
355
457
|
const charset = options.extractDataFromPage ? "windows-1252" : "utf-8";
|
|
356
458
|
html += "<meta charset=" + charset + ">";
|
|
357
459
|
const htmlHeadData = getHTMLHeadData(pageData, options);
|
|
358
|
-
let htmlArray;
|
|
460
|
+
let htmlArray, pdfEntry;
|
|
359
461
|
if (options.embeddedPdf) {
|
|
360
|
-
const
|
|
462
|
+
const embeddedPdf = new Uint8Array(options.embeddedPdf);
|
|
463
|
+
pdfEntry = getPDFEntry(embeddedPdf, lastModDate);
|
|
464
|
+
const embeddedPdfText = TEXT_DECODER.decode(pdfEntry.localHeader) + TEXT_DECODER.decode(embeddedPdf);
|
|
361
465
|
const pdfTagIndex = EMBEDDED_DATA_REGEXPS.slice(0, -1).findIndex(tests => !embeddedPdfText.match(tests[1]));
|
|
362
466
|
const [pdfStartTag, pdfEndTag] = pdfTagIndex == -1 ? ["", ""] : EMBEDDED_DATA_TAGS[pdfTagIndex];
|
|
363
467
|
const htmlArray1 = new TextEncoder().encode(html + pdfStartTag);
|
|
364
468
|
const htmlArray2 = new TextEncoder().encode(pdfEndTag + htmlHeadData + startTag);
|
|
365
|
-
htmlArray = new Uint8Array(htmlArray1.length +
|
|
469
|
+
htmlArray = new Uint8Array(htmlArray1.length + pdfEntry.localHeader.length + embeddedPdf.length + htmlArray2.length);
|
|
366
470
|
htmlArray.set(htmlArray1);
|
|
367
|
-
htmlArray.set(
|
|
368
|
-
htmlArray.set(
|
|
471
|
+
htmlArray.set(pdfEntry.localHeader, htmlArray1.length);
|
|
472
|
+
htmlArray.set(embeddedPdf, htmlArray1.length + pdfEntry.localHeader.length);
|
|
473
|
+
htmlArray.set(htmlArray2, htmlArray1.length + pdfEntry.localHeader.length + embeddedPdf.length);
|
|
474
|
+
pdfEntry.offset = htmlArray1.length;
|
|
369
475
|
} else {
|
|
370
476
|
htmlArray = new TextEncoder().encode(html + htmlHeadData + startTag);
|
|
371
477
|
}
|
|
372
|
-
return htmlArray;
|
|
478
|
+
return { htmlArray, pdfEntry };
|
|
373
479
|
}
|
|
374
480
|
|
|
375
481
|
function getHTMLHeadData(pageData, options) {
|