neozipkit 1.0.2 → 1.5.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/NEOZIP_APPNOTE.md +625 -0
- package/README.md +44 -11
- package/WHATS_NEW.md +153 -10
- package/dist/browser/ZipkitBrowser.d.ts.map +1 -1
- package/dist/browser/ZipkitBrowser.js +4 -6
- package/dist/browser/ZipkitBrowser.js.map +1 -1
- package/dist/browser/zstd-browser-shim.d.ts +17 -9
- package/dist/browser/zstd-browser-shim.d.ts.map +1 -1
- package/dist/browser/zstd-browser-shim.js +99 -21
- package/dist/browser/zstd-browser-shim.js.map +1 -1
- package/dist/browser-esm/index.js +6534 -1275
- package/dist/browser-esm/index.js.map +4 -4
- package/dist/browser-umd/index.js +6534 -1275
- package/dist/browser-umd/index.js.map +4 -4
- package/dist/browser-umd/index.min.js +6 -3
- package/dist/core/ZipCompress.d.ts +6 -0
- package/dist/core/ZipCompress.d.ts.map +1 -1
- package/dist/core/ZipCompress.js +12 -11
- package/dist/core/ZipCompress.js.map +1 -1
- package/dist/core/ZipCopy.d.ts.map +1 -1
- package/dist/core/ZipCopy.js +33 -25
- package/dist/core/ZipCopy.js.map +1 -1
- package/dist/core/ZipDecompress.d.ts.map +1 -1
- package/dist/core/ZipDecompress.js +13 -8
- package/dist/core/ZipDecompress.js.map +1 -1
- package/dist/core/ZipEntry.d.ts +24 -0
- package/dist/core/ZipEntry.d.ts.map +1 -1
- package/dist/core/ZipEntry.js +146 -14
- package/dist/core/ZipEntry.js.map +1 -1
- package/dist/core/Zipkit.d.ts +43 -11
- package/dist/core/Zipkit.d.ts.map +1 -1
- package/dist/core/Zipkit.js +132 -82
- package/dist/core/Zipkit.js.map +1 -1
- package/dist/core/components/HashCalculator.d.ts +22 -1
- package/dist/core/components/HashCalculator.d.ts.map +1 -1
- package/dist/core/components/HashCalculator.js +49 -6
- package/dist/core/components/HashCalculator.js.map +1 -1
- package/dist/core/components/Util.d.ts +3 -4
- package/dist/core/components/Util.d.ts.map +1 -1
- package/dist/core/components/Util.js +10 -8
- package/dist/core/components/Util.js.map +1 -1
- package/dist/core/constants/Headers.d.ts +2 -3
- package/dist/core/constants/Headers.d.ts.map +1 -1
- package/dist/core/constants/Headers.js +15 -5
- package/dist/core/constants/Headers.js.map +1 -1
- package/dist/core/constants/MetaPaths.d.ts +38 -0
- package/dist/core/constants/MetaPaths.d.ts.map +1 -0
- package/dist/core/constants/MetaPaths.js +79 -0
- package/dist/core/constants/MetaPaths.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +18 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/inflateZipPayload.d.ts +6 -0
- package/dist/core/inflateZipPayload.d.ts.map +1 -0
- package/dist/core/inflateZipPayload.js +28 -0
- package/dist/core/inflateZipPayload.js.map +1 -0
- package/dist/core/merkle/MerkleRoot.d.ts +109 -0
- package/dist/core/merkle/MerkleRoot.d.ts.map +1 -0
- package/dist/core/merkle/MerkleRoot.js +275 -0
- package/dist/core/merkle/MerkleRoot.js.map +1 -0
- package/dist/core/origin/OriginExtra.d.ts +43 -0
- package/dist/core/origin/OriginExtra.d.ts.map +1 -0
- package/dist/core/origin/OriginExtra.js +187 -0
- package/dist/core/origin/OriginExtra.js.map +1 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zip64/Zip64.d.ts +138 -0
- package/dist/core/zip64/Zip64.d.ts.map +1 -0
- package/dist/core/zip64/Zip64.js +353 -0
- package/dist/core/zip64/Zip64.js.map +1 -0
- package/dist/node/ZipCompressNode.d.ts.map +1 -1
- package/dist/node/ZipCompressNode.js +40 -49
- package/dist/node/ZipCompressNode.js.map +1 -1
- package/dist/node/ZipCopyNode.d.ts.map +1 -1
- package/dist/node/ZipCopyNode.js +26 -26
- package/dist/node/ZipCopyNode.js.map +1 -1
- package/dist/node/ZipDecompressNode.d.ts.map +1 -1
- package/dist/node/ZipDecompressNode.js +21 -3
- package/dist/node/ZipDecompressNode.js.map +1 -1
- package/dist/node/ZipkitNode.d.ts +23 -1
- package/dist/node/ZipkitNode.d.ts.map +1 -1
- package/dist/node/ZipkitNode.js +158 -57
- package/dist/node/ZipkitNode.js.map +1 -1
- package/dist/node/ZstdNode.d.ts +14 -4
- package/dist/node/ZstdNode.d.ts.map +1 -1
- package/dist/node/ZstdNode.js +28 -4
- package/dist/node/ZstdNode.js.map +1 -1
- package/dist/node/buildZipBuffer.d.ts +49 -0
- package/dist/node/buildZipBuffer.d.ts.map +1 -0
- package/dist/node/buildZipBuffer.js +146 -0
- package/dist/node/buildZipBuffer.js.map +1 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +6 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/inflateZipPayload.d.ts +5 -0
- package/dist/node/inflateZipPayload.d.ts.map +1 -0
- package/dist/node/inflateZipPayload.js +21 -0
- package/dist/node/inflateZipPayload.js.map +1 -0
- package/dist/types/index.js +2 -2
- package/node-esm.mjs +32 -0
- package/package.json +13 -6
- package/src/browser/ZipkitBrowser.ts +4 -6
- package/src/browser/zstd-browser-shim.ts +110 -28
- package/src/core/ZipCompress.ts +20 -13
- package/src/core/ZipCopy.ts +56 -27
- package/src/core/ZipDecompress.ts +14 -8
- package/src/core/ZipEntry.ts +200 -14
- package/src/core/Zipkit.ts +189 -98
- package/src/core/components/HashCalculator.ts +58 -6
- package/src/core/components/Util.ts +16 -7
- package/src/core/constants/Headers.ts +15 -4
- package/src/core/constants/MetaPaths.ts +86 -0
- package/src/core/index.ts +17 -0
- package/src/core/inflateZipPayload.ts +27 -0
- package/src/core/merkle/MerkleRoot.ts +312 -0
- package/src/core/origin/OriginExtra.ts +209 -0
- package/src/core/version.ts +1 -1
- package/src/core/zip64/Zip64.ts +473 -0
- package/src/node/ZipCompressNode.ts +40 -49
- package/src/node/ZipCopyNode.ts +31 -29
- package/src/node/ZipDecompressNode.ts +16 -3
- package/src/node/ZipkitNode.ts +203 -72
- package/src/node/ZstdNode.ts +33 -8
- package/src/node/buildZipBuffer.ts +221 -0
- package/src/node/index.ts +7 -0
- package/src/node/inflateZipPayload.ts +19 -0
- package/src/types/index.ts +2 -2
package/src/node/ZipCopyNode.ts
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import ZipEntry from '../core/ZipEntry';
|
|
12
12
|
import ZipkitNode from './ZipkitNode';
|
|
13
|
-
import { LOCAL_HDR,
|
|
13
|
+
import { LOCAL_HDR, GP_FLAG } from '../core/constants/Headers';
|
|
14
|
+
import {
|
|
15
|
+
buildEndRecords,
|
|
16
|
+
dataDescriptorByteLength,
|
|
17
|
+
} from '../core/zip64/Zip64';
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* Options for copying ZIP files
|
|
@@ -159,9 +163,12 @@ export class ZipCopyNode {
|
|
|
159
163
|
const localHeaderSize = this.calculateLocalHeaderSize(sourceFd, entry);
|
|
160
164
|
|
|
161
165
|
// Determine total entry size
|
|
162
|
-
// For data descriptor entries, add
|
|
166
|
+
// For data descriptor entries, add Zip64 (24) or classic (16) descriptor bytes
|
|
163
167
|
const hasDataDescriptor = (entry.bitFlags & GP_FLAG.DATA_DESC) !== 0;
|
|
164
|
-
const
|
|
168
|
+
const descLen = hasDataDescriptor
|
|
169
|
+
? dataDescriptorByteLength(entry.usesZip64Extra)
|
|
170
|
+
: 0;
|
|
171
|
+
const totalEntrySize = localHeaderSize + entry.compressedSize + descLen;
|
|
165
172
|
|
|
166
173
|
// Read the entire entry (local header + compressed data + data descriptor if present) from source
|
|
167
174
|
const entryBuffer = Buffer.alloc(totalEntrySize);
|
|
@@ -248,6 +255,10 @@ export class ZipCopyNode {
|
|
|
248
255
|
cloned.uid = entry.uid;
|
|
249
256
|
cloned.gid = entry.gid;
|
|
250
257
|
cloned.sha256 = entry.sha256;
|
|
258
|
+
cloned.emitUnicodePath = entry.emitUnicodePath;
|
|
259
|
+
if (entry.additionalExtra) {
|
|
260
|
+
cloned.additionalExtra = Buffer.from(entry.additionalExtra);
|
|
261
|
+
}
|
|
251
262
|
|
|
252
263
|
// Copy symlink data
|
|
253
264
|
cloned.isSymlink = entry.isSymlink;
|
|
@@ -258,6 +269,14 @@ export class ZipCopyNode {
|
|
|
258
269
|
cloned.originalEntry = entry.originalEntry;
|
|
259
270
|
cloned.inode = entry.inode;
|
|
260
271
|
|
|
272
|
+
cloned.usesZip64Extra = entry.usesZip64Extra;
|
|
273
|
+
cloned.aesVersion = entry.aesVersion;
|
|
274
|
+
cloned.aesStrength = entry.aesStrength;
|
|
275
|
+
cloned.realCmpMethod = entry.realCmpMethod;
|
|
276
|
+
cloned.neoCryptoPayloadVersion = entry.neoCryptoPayloadVersion;
|
|
277
|
+
cloned.neoCryptoAlgorithm = entry.neoCryptoAlgorithm;
|
|
278
|
+
cloned.neoCryptoFlags = entry.neoCryptoFlags;
|
|
279
|
+
|
|
261
280
|
return cloned;
|
|
262
281
|
}
|
|
263
282
|
|
|
@@ -280,33 +299,16 @@ export class ZipCopyNode {
|
|
|
280
299
|
const centralDirEndOffset = fs.fstatSync(destFd).size;
|
|
281
300
|
const centralDirSize = centralDirEndOffset - centralDirStartOffset;
|
|
282
301
|
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
eocdBuffer.writeUInt16LE(0, pos);
|
|
292
|
-
pos += 2;
|
|
293
|
-
eocdBuffer.writeUInt16LE(0, pos);
|
|
294
|
-
pos += 2;
|
|
295
|
-
eocdBuffer.writeUInt16LE(entries.length, pos);
|
|
296
|
-
pos += 2;
|
|
297
|
-
eocdBuffer.writeUInt16LE(entries.length, pos);
|
|
298
|
-
pos += 2;
|
|
299
|
-
eocdBuffer.writeUInt32LE(centralDirSize, pos);
|
|
300
|
-
pos += 4;
|
|
301
|
-
eocdBuffer.writeUInt32LE(centralDirStartOffset, pos);
|
|
302
|
-
pos += 4;
|
|
303
|
-
eocdBuffer.writeUInt16LE(commentLength, pos);
|
|
304
|
-
pos += 2;
|
|
305
|
-
if (commentLength > 0) {
|
|
306
|
-
commentBytes.copy(eocdBuffer, pos, 0, commentLength);
|
|
307
|
-
}
|
|
302
|
+
const endRecords = buildEndRecords({
|
|
303
|
+
totalEntries: entries.length,
|
|
304
|
+
centralDirSize,
|
|
305
|
+
centralDirOffset: centralDirStartOffset,
|
|
306
|
+
zip64EocdOffset: centralDirStartOffset + centralDirSize,
|
|
307
|
+
archiveComment: zipComment,
|
|
308
|
+
allowSizeOffsetZip64: true,
|
|
309
|
+
});
|
|
308
310
|
|
|
309
|
-
fs.writeSync(destFd,
|
|
311
|
+
fs.writeSync(destFd, endRecords, 0, endRecords.length);
|
|
310
312
|
|
|
311
313
|
return { centralDirOffset: centralDirStartOffset, centralDirSize };
|
|
312
314
|
}
|
|
@@ -451,6 +451,8 @@ export class ZipDecompressNode {
|
|
|
451
451
|
if (!options?.skipHashCheck) {
|
|
452
452
|
if (entry.sha256) {
|
|
453
453
|
const calculatedHash = hashCalc.finalizeSHA256();
|
|
454
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
455
|
+
if (leaf) entry.merkleLeafV1 = leaf;
|
|
454
456
|
this.log(`SHA-256 comparison: calculated=${calculatedHash}, stored=${entry.sha256}`);
|
|
455
457
|
if (calculatedHash !== entry.sha256) {
|
|
456
458
|
if (options?.outputPath && fs) {
|
|
@@ -540,6 +542,8 @@ export class ZipDecompressNode {
|
|
|
540
542
|
if (!options?.skipHashCheck) {
|
|
541
543
|
if (entry.sha256) {
|
|
542
544
|
const calculatedHash = hashCalc.finalizeSHA256();
|
|
545
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
546
|
+
if (leaf) entry.merkleLeafV1 = leaf;
|
|
543
547
|
this.log(`SHA-256 comparison: calculated=${calculatedHash}, stored=${entry.sha256}`);
|
|
544
548
|
if (calculatedHash !== entry.sha256) {
|
|
545
549
|
throw new Error(Errors.INVALID_SHA256);
|
|
@@ -588,7 +592,8 @@ export class ZipDecompressNode {
|
|
|
588
592
|
const decompressedStream = this.decompressStream(compressedStream, entry.cmpMethod, undefined, entry);
|
|
589
593
|
|
|
590
594
|
// Process and validate chunks - one block at a time
|
|
591
|
-
|
|
595
|
+
// Always hash the v1 Merkle leaf so getMerkleRootAsync works for CRC-only archives.
|
|
596
|
+
const hashCalc = new HashCalculator({ useSHA256: true });
|
|
592
597
|
let totalBytes = 0;
|
|
593
598
|
|
|
594
599
|
try {
|
|
@@ -607,6 +612,8 @@ export class ZipDecompressNode {
|
|
|
607
612
|
if (!options?.skipHashCheck) {
|
|
608
613
|
if (entry.sha256) {
|
|
609
614
|
const calculatedHash = hashCalc.finalizeSHA256();
|
|
615
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
616
|
+
if (leaf) entry.merkleLeafV1 = leaf;
|
|
610
617
|
this.log(`SHA-256 comparison: calculated=${calculatedHash}, stored=${entry.sha256}`);
|
|
611
618
|
if (calculatedHash !== entry.sha256) {
|
|
612
619
|
throw new Error(Errors.INVALID_SHA256);
|
|
@@ -616,16 +623,22 @@ export class ZipDecompressNode {
|
|
|
616
623
|
return { verifiedHash: calculatedHash };
|
|
617
624
|
} else {
|
|
618
625
|
const calculatedCRC = hashCalc.finalizeCRC32();
|
|
626
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
627
|
+
if (leaf) entry.merkleLeafV1 = leaf;
|
|
619
628
|
this.log(`CRC-32 comparison: calculated=${calculatedCRC}, stored=${entry.crc}`);
|
|
620
629
|
if (calculatedCRC !== entry.crc) {
|
|
621
630
|
throw new Error(Errors.INVALID_CRC);
|
|
622
631
|
}
|
|
623
632
|
this.log(`CRC-32 comparison: calculated=${calculatedCRC}, stored=${entry.crc}`);
|
|
624
|
-
// No hash to return for CRC-32 only entries
|
|
625
633
|
return { verifiedHash: undefined };
|
|
626
634
|
}
|
|
627
635
|
} else {
|
|
628
|
-
|
|
636
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
637
|
+
if (leaf) entry.merkleLeafV1 = leaf;
|
|
638
|
+
if (entry.sha256 && hashCalc) {
|
|
639
|
+
const calculatedHash = hashCalc.finalizeSHA256();
|
|
640
|
+
return { verifiedHash: calculatedHash || undefined };
|
|
641
|
+
}
|
|
629
642
|
return { verifiedHash: undefined };
|
|
630
643
|
}
|
|
631
644
|
} catch (error) {
|
package/src/node/ZipkitNode.ts
CHANGED
|
@@ -11,22 +11,40 @@ import { ZipDecompressNode } from './ZipDecompressNode';
|
|
|
11
11
|
import {
|
|
12
12
|
CENTRAL_END,
|
|
13
13
|
CENTRAL_DIR,
|
|
14
|
-
ZIP64_CENTRAL_END,
|
|
15
|
-
ZIP64_CENTRAL_DIR,
|
|
16
14
|
LOCAL_HDR,
|
|
17
15
|
GP_FLAG,
|
|
18
16
|
CMP_METHOD,
|
|
19
|
-
ENCRYPT_HDR_SIZE
|
|
17
|
+
ENCRYPT_HDR_SIZE,
|
|
18
|
+
HDR_ID,
|
|
20
19
|
} from '../core/constants/Headers';
|
|
20
|
+
import {
|
|
21
|
+
buildEndRecords,
|
|
22
|
+
classicEocdNeedsZip64,
|
|
23
|
+
exceedsU32,
|
|
24
|
+
parseZip64Eocd,
|
|
25
|
+
parseZip64Locator,
|
|
26
|
+
writeU64,
|
|
27
|
+
ZIP64_EOCD_RECORD_SIZE,
|
|
28
|
+
ZIP64_LOCATOR_SIZE,
|
|
29
|
+
ZIP64_U32,
|
|
30
|
+
} from '../core/zip64/Zip64';
|
|
21
31
|
import { NEO_CRYPTO_ALGORITHM_AES256_V1 } from '../core/encryption/NeoCrypto';
|
|
22
32
|
import { AesCrypto } from '../core/encryption/AesCrypto';
|
|
23
33
|
import {
|
|
24
34
|
detectLegacyWasmZstd,
|
|
25
35
|
type LegacyZstdDetectResult,
|
|
26
36
|
} from './LegacyZstd';
|
|
37
|
+
import { writeZipFileSync, type ZipBufferMember, type BuildZipBufferOptions } from './buildZipBuffer';
|
|
38
|
+
export type { ZipBufferMember };
|
|
27
39
|
import * as fs from 'fs';
|
|
28
40
|
import * as path from 'path';
|
|
29
41
|
import { minimatch } from 'minimatch';
|
|
42
|
+
import {
|
|
43
|
+
computeMerkleRootV0FromDigests,
|
|
44
|
+
computeMerkleRootV1FromLeaves,
|
|
45
|
+
type MerkleAlgorithm,
|
|
46
|
+
} from '../core/merkle/MerkleRoot';
|
|
47
|
+
import { isMetaInfPath } from '../core/constants/MetaPaths';
|
|
30
48
|
|
|
31
49
|
// Re-export everything from core Zipkit
|
|
32
50
|
export * from '../core';
|
|
@@ -111,6 +129,79 @@ export default class ZipkitNode extends Zipkit {
|
|
|
111
129
|
return this._zipkitDeCmpNode;
|
|
112
130
|
}
|
|
113
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Streaming Merkle root for file-based archives.
|
|
134
|
+
* V1 leaves come from the same decompress→hash pass as integrity (testEntry / extract),
|
|
135
|
+
* chunked — never re-reads uncompressed sources or materializes whole entries for the root alone.
|
|
136
|
+
*/
|
|
137
|
+
async getMerkleRootAsync(options?: { algorithm?: MerkleAlgorithm }): Promise<string | null> {
|
|
138
|
+
const algorithm: MerkleAlgorithm = options?.algorithm ?? 'v1';
|
|
139
|
+
let zipEntries: ZipEntry[];
|
|
140
|
+
try {
|
|
141
|
+
zipEntries = this.getDirectory();
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (!zipEntries?.length) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const contentEntries = zipEntries.filter((e) => !isMetaInfPath(e.filename || ''));
|
|
150
|
+
if (contentEntries.length === 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (algorithm === 'v0') {
|
|
155
|
+
const digests = contentEntries
|
|
156
|
+
.filter((e) => e.sha256)
|
|
157
|
+
.map((e) => ({ path: e.filename || '', sha256: e.sha256 as string }));
|
|
158
|
+
return computeMerkleRootV0FromDigests(digests);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (contentEntries.every((e) => !!e.merkleLeafV1)) {
|
|
162
|
+
return computeMerkleRootV1FromLeaves(
|
|
163
|
+
contentEntries.map((e) => ({
|
|
164
|
+
path: e.filename || '',
|
|
165
|
+
merkleLeafV1: e.merkleLeafV1 as string,
|
|
166
|
+
}))
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Buffer-loaded: base class extract path
|
|
171
|
+
if ((this as any).inBuffer) {
|
|
172
|
+
return super.getMerkleRootAsync(options);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// File-based: stream hash each content entry once via testEntry (no write, dual hash)
|
|
176
|
+
if (!this.fileHandle && !this.filePath) {
|
|
177
|
+
// Create-session only: may only have live ZipEntry[] with leaves from write
|
|
178
|
+
return super.getMerkleRootAsync(options);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
for (const entry of contentEntries) {
|
|
183
|
+
if (entry.merkleLeafV1) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
await this.getZipDecompressNode().testEntry(entry, { skipHashCheck: false });
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error('[ZipkitNode.getMerkleRootAsync] stream hash failed:', err);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!contentEntries.every((e) => !!e.merkleLeafV1)) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return computeMerkleRootV1FromLeaves(
|
|
198
|
+
contentEntries.map((e) => ({
|
|
199
|
+
path: e.filename || '',
|
|
200
|
+
merkleLeafV1: e.merkleLeafV1 as string,
|
|
201
|
+
}))
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
114
205
|
// ============================================================================
|
|
115
206
|
// File Loading Methods
|
|
116
207
|
// ============================================================================
|
|
@@ -638,6 +729,10 @@ export default class ZipkitNode extends Zipkit {
|
|
|
638
729
|
onHashCalculated?: (entry: ZipEntry, hash: Buffer) => void;
|
|
639
730
|
}
|
|
640
731
|
): Promise<void> {
|
|
732
|
+
if (options?.forceZip64) {
|
|
733
|
+
entry.forceZip64 = true;
|
|
734
|
+
}
|
|
735
|
+
|
|
641
736
|
// Set compression method based on options
|
|
642
737
|
const level = options?.level ?? 6;
|
|
643
738
|
let realCmpMethod: number;
|
|
@@ -650,11 +745,10 @@ export default class ZipkitNode extends Zipkit {
|
|
|
650
745
|
}
|
|
651
746
|
|
|
652
747
|
// For AES encryption, pre-configure the entry so the initial local header
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
// Default password encryption is WinZip AES-256 (matches compressData).
|
|
748
|
+
// matches the chosen scheme (WinZip method 99 + 0x9901, or NeoEncrypt + 0x024E).
|
|
749
|
+
// Default password encryption is NeoEncrypt (matches NeoZip product AES-256).
|
|
656
750
|
const encMethod = options?.password
|
|
657
|
-
? (options.encryptionMethod || 'aes256')
|
|
751
|
+
? (options.encryptionMethod || 'neo-aes256')
|
|
658
752
|
: undefined;
|
|
659
753
|
const isAesRequested = encMethod === 'aes256';
|
|
660
754
|
const isNeoRequested = encMethod === 'neo-aes256';
|
|
@@ -781,9 +875,55 @@ export default class ZipkitNode extends Zipkit {
|
|
|
781
875
|
throw new Error(`Compressed size not set for entry: ${entry.filename}`);
|
|
782
876
|
}
|
|
783
877
|
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
878
|
+
const classicCmp = entry.localHdrOffset + LOCAL_HDR.CMP_SIZE;
|
|
879
|
+
const classicUncmp = entry.localHdrOffset + LOCAL_HDR.UNCMP_SIZE;
|
|
880
|
+
const useZip64Sizes =
|
|
881
|
+
entry.forceZip64 ||
|
|
882
|
+
exceedsU32(entry.uncompressedSize) ||
|
|
883
|
+
exceedsU32(entry.compressedSize);
|
|
884
|
+
|
|
885
|
+
if (useZip64Sizes) {
|
|
886
|
+
// Classic fields stay at 0xFFFFFFFF; update u64 sizes inside local extra 0x0001
|
|
887
|
+
const sentinel = Buffer.alloc(4);
|
|
888
|
+
sentinel.writeUInt32LE(ZIP64_U32, 0);
|
|
889
|
+
fs.writeSync(writer.outputFd, sentinel, 0, 4, classicCmp);
|
|
890
|
+
fs.writeSync(writer.outputFd, sentinel, 0, 4, classicUncmp);
|
|
891
|
+
|
|
892
|
+
const fnameLenBuf = Buffer.alloc(2);
|
|
893
|
+
fs.readSync(writer.outputFd, fnameLenBuf, 0, 2, entry.localHdrOffset + LOCAL_HDR.FNAME_LEN);
|
|
894
|
+
const fnameLen = fnameLenBuf.readUInt16LE(0);
|
|
895
|
+
const extraLenBuf = Buffer.alloc(2);
|
|
896
|
+
fs.readSync(writer.outputFd, extraLenBuf, 0, 2, entry.localHdrOffset + LOCAL_HDR.EXTRA_LEN);
|
|
897
|
+
const extraLen = extraLenBuf.readUInt16LE(0);
|
|
898
|
+
const extraStart = entry.localHdrOffset + LOCAL_HDR.SIZE + fnameLen;
|
|
899
|
+
const extraBuf = Buffer.alloc(extraLen);
|
|
900
|
+
if (extraLen > 0) {
|
|
901
|
+
fs.readSync(writer.outputFd, extraBuf, 0, extraLen, extraStart);
|
|
902
|
+
}
|
|
903
|
+
let patched = false;
|
|
904
|
+
for (let i = 0; i + 4 <= extraBuf.length; ) {
|
|
905
|
+
const id = extraBuf.readUInt16LE(i);
|
|
906
|
+
const len = extraBuf.readUInt16LE(i + 2);
|
|
907
|
+
if (id === HDR_ID.ZIP64 && len >= 16) {
|
|
908
|
+
// Local Zip64 always has uncompressed (8) then compressed (8)
|
|
909
|
+
writeU64(extraBuf, i + 4, entry.uncompressedSize);
|
|
910
|
+
writeU64(extraBuf, i + 12, entry.compressedSize);
|
|
911
|
+
fs.writeSync(writer.outputFd, extraBuf, i + 4, 16, extraStart + i + 4);
|
|
912
|
+
patched = true;
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
i += 4 + len;
|
|
916
|
+
}
|
|
917
|
+
if (!patched) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
`Zip64 local extra (0x0001) missing for large entry: ${entry.filename}`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
const sizeBuffer = Buffer.alloc(4);
|
|
924
|
+
sizeBuffer.writeUInt32LE(entry.compressedSize, 0);
|
|
925
|
+
fs.writeSync(writer.outputFd, sizeBuffer, 0, 4, classicCmp);
|
|
926
|
+
}
|
|
787
927
|
|
|
788
928
|
if (entry.crc !== undefined) {
|
|
789
929
|
const crcBuffer = Buffer.alloc(4);
|
|
@@ -860,6 +1000,7 @@ export default class ZipkitNode extends Zipkit {
|
|
|
860
1000
|
* @param centralDirSize - Size of central directory in bytes
|
|
861
1001
|
* @param centralDirOffset - Offset to start of central directory
|
|
862
1002
|
* @param archiveComment - Optional archive comment (max 65535 bytes)
|
|
1003
|
+
* @param options - Optional Zip64 force and related EOCD flags
|
|
863
1004
|
* @returns Promise that resolves when EOCD is written
|
|
864
1005
|
*/
|
|
865
1006
|
async writeEndOfCentralDirectory(
|
|
@@ -867,53 +1008,20 @@ export default class ZipkitNode extends Zipkit {
|
|
|
867
1008
|
totalEntries: number,
|
|
868
1009
|
centralDirSize: number,
|
|
869
1010
|
centralDirOffset: number,
|
|
870
|
-
archiveComment?: string
|
|
1011
|
+
archiveComment?: string,
|
|
1012
|
+
options?: { forceZip64?: boolean }
|
|
871
1013
|
): Promise<void> {
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
offset += 4;
|
|
882
|
-
|
|
883
|
-
// Number of this disk (2 bytes)
|
|
884
|
-
buffer.writeUInt16LE(0, offset);
|
|
885
|
-
offset += 2;
|
|
886
|
-
|
|
887
|
-
// Number of the disk with the start of the central directory (2 bytes)
|
|
888
|
-
buffer.writeUInt16LE(0, offset);
|
|
889
|
-
offset += 2;
|
|
890
|
-
|
|
891
|
-
// Total number of entries in the central directory on this disk (2 bytes)
|
|
892
|
-
buffer.writeUInt16LE(totalEntries, offset);
|
|
893
|
-
offset += 2;
|
|
894
|
-
|
|
895
|
-
// Total number of entries in the central directory (2 bytes)
|
|
896
|
-
buffer.writeUInt16LE(totalEntries, offset);
|
|
897
|
-
offset += 2;
|
|
898
|
-
|
|
899
|
-
// Size of the central directory (4 bytes)
|
|
900
|
-
buffer.writeUInt32LE(centralDirSize, offset);
|
|
901
|
-
offset += 4;
|
|
902
|
-
|
|
903
|
-
// Offset of start of central directory with respect to the starting disk number (4 bytes)
|
|
904
|
-
buffer.writeUInt32LE(centralDirOffset, offset);
|
|
905
|
-
offset += 4;
|
|
906
|
-
|
|
907
|
-
// ZIP file comment length (2 bytes)
|
|
908
|
-
buffer.writeUInt16LE(commentLength, offset);
|
|
909
|
-
offset += 2;
|
|
910
|
-
|
|
911
|
-
// ZIP file comment (variable length)
|
|
912
|
-
if (commentLength > 0) {
|
|
913
|
-
commentBytes.copy(buffer, offset, 0, commentLength);
|
|
914
|
-
}
|
|
1014
|
+
const buffer = buildEndRecords({
|
|
1015
|
+
totalEntries,
|
|
1016
|
+
centralDirSize,
|
|
1017
|
+
centralDirOffset,
|
|
1018
|
+
zip64EocdOffset: centralDirOffset + centralDirSize,
|
|
1019
|
+
archiveComment,
|
|
1020
|
+
allowSizeOffsetZip64: true,
|
|
1021
|
+
forceZip64: options?.forceZip64 === true,
|
|
1022
|
+
});
|
|
915
1023
|
|
|
916
|
-
// Write EOCD to file
|
|
1024
|
+
// Write EOCD (and Zip64 EOCD + locator when needed) to file
|
|
917
1025
|
await new Promise<void>((resolve, reject) => {
|
|
918
1026
|
writer.outputStream.write(buffer, (error) => {
|
|
919
1027
|
if (error) {
|
|
@@ -989,7 +1097,9 @@ export default class ZipkitNode extends Zipkit {
|
|
|
989
1097
|
writer,
|
|
990
1098
|
entries.length,
|
|
991
1099
|
centralDirSize,
|
|
992
|
-
centralDirOffset
|
|
1100
|
+
centralDirOffset,
|
|
1101
|
+
undefined,
|
|
1102
|
+
{ forceZip64: options?.forceZip64 === true }
|
|
993
1103
|
);
|
|
994
1104
|
} finally {
|
|
995
1105
|
await this.finalizeZipFile(writer);
|
|
@@ -1132,6 +1242,21 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1132
1242
|
throw new Error('updateZipFile() - Full implementation pending. Use neozip CLI for now.');
|
|
1133
1243
|
}
|
|
1134
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* Stream members to `filePath` (local headers, payloads, central directory, EOCD).
|
|
1247
|
+
* Pass `useSHA256` for Extra Field 0x014E and `additionalExtra` for caller blocks
|
|
1248
|
+
* such as ZipWiki 0x014F. `method` 93 uses zlib zstd at `level` (default 7).
|
|
1249
|
+
* NeoEncrypt is not enabled. Precompressed members are copied without recompression.
|
|
1250
|
+
* Pass `{ forceZip64: true }` to emit Zip64 even for small members (testing).
|
|
1251
|
+
*/
|
|
1252
|
+
writeMembersSync(
|
|
1253
|
+
filePath: string,
|
|
1254
|
+
members: ZipBufferMember[],
|
|
1255
|
+
options?: BuildZipBufferOptions
|
|
1256
|
+
): void {
|
|
1257
|
+
writeZipFileSync(filePath, members, options);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1135
1260
|
// ============================================================================
|
|
1136
1261
|
// File-based ZIP Loading Methods (merged from ZipLoadEntriesServer)
|
|
1137
1262
|
// ============================================================================
|
|
@@ -1197,9 +1322,17 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1197
1322
|
const zipkit = this as any;
|
|
1198
1323
|
zipkit.centralDirSize = eocdBuffer.readUInt32LE(CENTRAL_END.CENTRAL_DIR_SIZE);
|
|
1199
1324
|
zipkit.centralDirOffset = eocdBuffer.readUInt32LE(CENTRAL_END.CENTRAL_DIR_OFFSET);
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1325
|
+
const volEntries = eocdBuffer.readUInt16LE(CENTRAL_END.VOL_ENTRIES);
|
|
1326
|
+
const totalEntries = eocdBuffer.readUInt16LE(CENTRAL_END.TOTAL_ENTRIES);
|
|
1327
|
+
|
|
1328
|
+
if (
|
|
1329
|
+
classicEocdNeedsZip64({
|
|
1330
|
+
volEntries,
|
|
1331
|
+
totalEntries,
|
|
1332
|
+
centralDirSize: zipkit.centralDirSize,
|
|
1333
|
+
centralDirOffset: zipkit.centralDirOffset,
|
|
1334
|
+
})
|
|
1335
|
+
) {
|
|
1203
1336
|
await this.loadZIP64EOCD(eocdOffset);
|
|
1204
1337
|
}
|
|
1205
1338
|
} else {
|
|
@@ -1229,20 +1362,18 @@ export default class ZipkitNode extends Zipkit {
|
|
|
1229
1362
|
}
|
|
1230
1363
|
|
|
1231
1364
|
// Look for ZIP64 locator
|
|
1232
|
-
const locatorOffset = eocdOffset -
|
|
1233
|
-
const locatorBuffer = Buffer.alloc(
|
|
1234
|
-
await this.fileHandle.read(locatorBuffer, 0,
|
|
1365
|
+
const locatorOffset = eocdOffset - ZIP64_LOCATOR_SIZE;
|
|
1366
|
+
const locatorBuffer = Buffer.alloc(ZIP64_LOCATOR_SIZE);
|
|
1367
|
+
await this.fileHandle.read(locatorBuffer, 0, ZIP64_LOCATOR_SIZE, locatorOffset);
|
|
1235
1368
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
zipkit.centralDirOffset = Number(zip64Buffer.readBigUInt64LE(ZIP64_CENTRAL_DIR.CENTRAL_DIR_OFFSET));
|
|
1245
|
-
}
|
|
1369
|
+
const zip64Offset = parseZip64Locator(locatorBuffer);
|
|
1370
|
+
const zip64Buffer = Buffer.alloc(ZIP64_EOCD_RECORD_SIZE);
|
|
1371
|
+
await this.fileHandle.read(zip64Buffer, 0, ZIP64_EOCD_RECORD_SIZE, zip64Offset);
|
|
1372
|
+
|
|
1373
|
+
const z64 = parseZip64Eocd(zip64Buffer);
|
|
1374
|
+
const zipkit = this as any;
|
|
1375
|
+
zipkit.centralDirSize = z64.centralDirSize;
|
|
1376
|
+
zipkit.centralDirOffset = z64.centralDirOffset;
|
|
1246
1377
|
}
|
|
1247
1378
|
|
|
1248
1379
|
/**
|
package/src/node/ZstdNode.ts
CHANGED
|
@@ -54,26 +54,49 @@ export function createDecompressTransform(): zlib.ZstdDecompress {
|
|
|
54
54
|
return zlib.createZstdDecompress();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/** Bytes accepted by the buffer zstd codec (Node Buffer or browser ArrayBuffer). */
|
|
58
|
+
export type ZstdInput = Buffer | Uint8Array | ArrayBuffer;
|
|
59
|
+
|
|
60
|
+
export function asZstdBuffer(data: ZstdInput): Buffer {
|
|
61
|
+
if (Buffer.isBuffer(data)) return data;
|
|
62
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data);
|
|
63
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function compress(data: ZstdInput, level: number = 6): Promise<Buffer> {
|
|
58
67
|
assertNativeZstd();
|
|
59
|
-
const result = await zstdCompressAsync(data, compressOptions(level));
|
|
68
|
+
const result = await zstdCompressAsync(asZstdBuffer(data), compressOptions(level));
|
|
60
69
|
return Buffer.from(result);
|
|
61
70
|
}
|
|
62
71
|
|
|
63
|
-
export async function decompress(data:
|
|
72
|
+
export async function decompress(data: ZstdInput): Promise<Buffer> {
|
|
64
73
|
assertNativeZstd();
|
|
65
|
-
const result = await zstdDecompressAsync(data);
|
|
74
|
+
const result = await zstdDecompressAsync(asZstdBuffer(data));
|
|
66
75
|
return Buffer.from(result);
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
export function compressSync(data:
|
|
78
|
+
export function compressSync(data: ZstdInput, level: number = 6): Buffer {
|
|
79
|
+
assertNativeZstd();
|
|
80
|
+
return zlib.zstdCompressSync(asZstdBuffer(data), compressOptions(level));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compress with a raw zlib zstd level (1–19), without the 1–9 kit scale.
|
|
85
|
+
* ZipWiki pack uses level 7 on this scale.
|
|
86
|
+
*/
|
|
87
|
+
export function compressSyncAtLevel(data: ZstdInput, zstdLevel: number): Buffer {
|
|
70
88
|
assertNativeZstd();
|
|
71
|
-
|
|
89
|
+
const level = Math.min(19, Math.max(1, Math.trunc(zstdLevel)));
|
|
90
|
+
return zlib.zstdCompressSync(asZstdBuffer(data), {
|
|
91
|
+
params: {
|
|
92
|
+
[zlib.constants.ZSTD_c_compressionLevel]: level,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
72
95
|
}
|
|
73
96
|
|
|
74
|
-
export function decompressSync(data:
|
|
97
|
+
export function decompressSync(data: ZstdInput): Buffer {
|
|
75
98
|
assertNativeZstd();
|
|
76
|
-
return zlib.zstdDecompressSync(data);
|
|
99
|
+
return zlib.zstdDecompressSync(asZstdBuffer(data));
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
/**
|
|
@@ -147,7 +170,9 @@ export const ZstdNode = {
|
|
|
147
170
|
compress,
|
|
148
171
|
decompress,
|
|
149
172
|
compressSync,
|
|
173
|
+
compressSyncAtLevel,
|
|
150
174
|
decompressSync,
|
|
175
|
+
asZstdBuffer,
|
|
151
176
|
compressChunks,
|
|
152
177
|
decompressStream,
|
|
153
178
|
};
|