neozipkit 1.0.2 → 1.0.5
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 +527 -0
- package/README.md +8 -5
- package/WHATS_NEW.md +42 -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-esm/index.js +424 -82
- package/dist/browser-esm/index.js.map +4 -4
- package/dist/browser-umd/index.js +424 -82
- package/dist/browser-umd/index.js.map +4 -4
- package/dist/browser-umd/index.min.js +3 -3
- 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/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 +6 -0
- package/dist/core/ZipEntry.d.ts.map +1 -1
- package/dist/core/ZipEntry.js +7 -1
- 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 +106 -63
- 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 +1 -3
- package/dist/core/constants/Headers.d.ts.map +1 -1
- package/dist/core/constants/Headers.js +14 -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 +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +14 -1
- package/dist/core/index.js.map +1 -1
- 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/version.js +1 -1
- 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/ZipDecompressNode.d.ts.map +1 -1
- package/dist/node/ZipDecompressNode.js +17 -1
- package/dist/node/ZipDecompressNode.js.map +1 -1
- package/dist/node/ZipkitNode.d.ts +9 -0
- package/dist/node/ZipkitNode.d.ts.map +1 -1
- package/dist/node/ZipkitNode.js +67 -4
- package/dist/node/ZipkitNode.js.map +1 -1
- package/package.json +3 -2
- package/src/browser/ZipkitBrowser.ts +4 -6
- package/src/core/ZipCompress.ts +14 -13
- package/src/core/ZipDecompress.ts +14 -8
- package/src/core/ZipEntry.ts +8 -1
- package/src/core/Zipkit.ts +149 -76
- package/src/core/components/HashCalculator.ts +58 -6
- package/src/core/components/Util.ts +16 -7
- package/src/core/constants/Headers.ts +14 -4
- package/src/core/constants/MetaPaths.ts +86 -0
- package/src/core/index.ts +14 -0
- package/src/core/merkle/MerkleRoot.ts +312 -0
- package/src/core/version.ts +1 -1
- package/src/node/ZipCompressNode.ts +40 -49
- package/src/node/ZipDecompressNode.ts +13 -1
- package/src/node/ZipkitNode.ts +82 -4
|
@@ -6139,6 +6139,13 @@ var NeoZipKit = (() => {
|
|
|
6139
6139
|
LOCAL_FILE_HEADER: () => LOCAL_FILE_HEADER,
|
|
6140
6140
|
LOCAL_HDR: () => LOCAL_HDR,
|
|
6141
6141
|
Logger: () => Logger,
|
|
6142
|
+
META_MANIFEST_JSON: () => META_MANIFEST_JSON,
|
|
6143
|
+
META_TIMESTAMP_NZIP: () => META_TIMESTAMP_NZIP,
|
|
6144
|
+
META_TIMESTAMP_OTS: () => META_TIMESTAMP_OTS,
|
|
6145
|
+
META_TOKEN_NZIP: () => META_TOKEN_NZIP,
|
|
6146
|
+
META_TOKEN_NZIP_LEGACY: () => META_TOKEN_NZIP_LEGACY,
|
|
6147
|
+
META_TS_SUBMIT_NZIP: () => META_TS_SUBMIT_NZIP,
|
|
6148
|
+
META_TS_SUBMIT_OTS: () => META_TS_SUBMIT_OTS,
|
|
6142
6149
|
NEOZIPKIT_INFO: () => NEOZIPKIT_INFO,
|
|
6143
6150
|
NEO_CRYPTO_EXTRA_FIELD_SIZE: () => NEO_CRYPTO_EXTRA_FIELD_SIZE,
|
|
6144
6151
|
NEO_CRYPTO_MAGIC_BYTES: () => NEO_CRYPTO_MAGIC_BYTES,
|
|
@@ -6158,12 +6165,27 @@ var NeoZipKit = (() => {
|
|
|
6158
6165
|
ZipCrypto: () => ZipCrypto,
|
|
6159
6166
|
ZipEntry: () => ZipEntry,
|
|
6160
6167
|
ZipkitBrowser: () => ZipkitBrowser,
|
|
6168
|
+
asciiPathEqualsIgnoreCase: () => asciiPathEqualsIgnoreCase,
|
|
6169
|
+
computeArchiveMerkleRoot: () => computeArchiveMerkleRoot,
|
|
6170
|
+
computeMerkleRootV0FromDigests: () => computeMerkleRootV0FromDigests,
|
|
6171
|
+
computeMerkleRootV1FromContents: () => computeMerkleRootV1FromContents,
|
|
6172
|
+
computeMerkleRootV1FromLeaves: () => computeMerkleRootV1FromLeaves,
|
|
6161
6173
|
configureLoggerFromEnvironment: () => configureLoggerFromEnvironment,
|
|
6174
|
+
contentDigest: () => contentDigest,
|
|
6162
6175
|
crc32: () => crc32,
|
|
6163
6176
|
crc32update: () => crc32update,
|
|
6177
|
+
findReservedMetaEntry: () => findReservedMetaEntry,
|
|
6164
6178
|
getTypeOf: () => getTypeOf,
|
|
6179
|
+
isMetaInfPath: () => isMetaInfPath,
|
|
6165
6180
|
isMetadataFile: () => isMetadataFile,
|
|
6181
|
+
isReservedMetaPath: () => isReservedMetaPath,
|
|
6166
6182
|
isStream: () => isStream,
|
|
6183
|
+
leafHashV0: () => leafHashV0,
|
|
6184
|
+
leafHashV1: () => leafHashV1,
|
|
6185
|
+
matchMerkleRoot: () => matchMerkleRoot,
|
|
6186
|
+
merkleRootFromLeaves: () => merkleRootFromLeaves,
|
|
6187
|
+
normalizeMerklePath: () => normalizeMerklePath,
|
|
6188
|
+
parentHash: () => parentHash,
|
|
6167
6189
|
readBigUInt64LE: () => readBigUInt64LE,
|
|
6168
6190
|
sha256: () => sha256,
|
|
6169
6191
|
uMul: () => uMul
|
|
@@ -6231,6 +6253,53 @@ var NeoZipKit = (() => {
|
|
|
6231
6253
|
ENCRYPT_FAILED: "Encryption failed"
|
|
6232
6254
|
};
|
|
6233
6255
|
|
|
6256
|
+
// src/core/constants/MetaPaths.ts
|
|
6257
|
+
var META_TOKEN_NZIP = "META-INF/TOKEN.NZIP";
|
|
6258
|
+
var META_TOKEN_NZIP_LEGACY = "META-INF/NZIP.TOKEN";
|
|
6259
|
+
var META_TS_SUBMIT_NZIP = "META-INF/TS-SUBMIT.NZIP";
|
|
6260
|
+
var META_TIMESTAMP_NZIP = "META-INF/TIMESTAMP.NZIP";
|
|
6261
|
+
var META_TS_SUBMIT_OTS = "META-INF/TS-SUBMIT.OTS";
|
|
6262
|
+
var META_TIMESTAMP_OTS = "META-INF/TIMESTAMP.OTS";
|
|
6263
|
+
var META_MANIFEST_JSON = "META-INF/manifest.json";
|
|
6264
|
+
var TIMESTAMP_SUBMITTED = META_TS_SUBMIT_OTS;
|
|
6265
|
+
var TIMESTAMP_METADATA = META_TIMESTAMP_OTS;
|
|
6266
|
+
var TOKENIZED_METADATA = META_TOKEN_NZIP;
|
|
6267
|
+
var RESERVED_CANONICAL = [
|
|
6268
|
+
META_TOKEN_NZIP,
|
|
6269
|
+
META_TOKEN_NZIP_LEGACY,
|
|
6270
|
+
META_TS_SUBMIT_NZIP,
|
|
6271
|
+
META_TIMESTAMP_NZIP,
|
|
6272
|
+
META_TS_SUBMIT_OTS,
|
|
6273
|
+
META_TIMESTAMP_OTS,
|
|
6274
|
+
META_MANIFEST_JSON
|
|
6275
|
+
];
|
|
6276
|
+
function asciiPathEqualsIgnoreCase(a, b) {
|
|
6277
|
+
if (a.length !== b.length) {
|
|
6278
|
+
}
|
|
6279
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
6280
|
+
}
|
|
6281
|
+
function isMetaInfPath(filename) {
|
|
6282
|
+
return filename.toLowerCase().startsWith("meta-inf/");
|
|
6283
|
+
}
|
|
6284
|
+
function isReservedMetaPath(filename) {
|
|
6285
|
+
const lower = filename.toLowerCase();
|
|
6286
|
+
return RESERVED_CANONICAL.some((c) => c.toLowerCase() === lower);
|
|
6287
|
+
}
|
|
6288
|
+
function findReservedMetaEntry(entries, preferredCanonicalNames) {
|
|
6289
|
+
const preferred = Array.isArray(preferredCanonicalNames) ? preferredCanonicalNames : [preferredCanonicalNames];
|
|
6290
|
+
for (const name of preferred) {
|
|
6291
|
+
const exact = entries.find((e) => e.filename === name);
|
|
6292
|
+
if (exact) return exact;
|
|
6293
|
+
}
|
|
6294
|
+
for (const name of preferred) {
|
|
6295
|
+
const folded = entries.find(
|
|
6296
|
+
(e) => typeof e.filename === "string" && asciiPathEqualsIgnoreCase(e.filename, name)
|
|
6297
|
+
);
|
|
6298
|
+
if (folded) return folded;
|
|
6299
|
+
}
|
|
6300
|
+
return null;
|
|
6301
|
+
}
|
|
6302
|
+
|
|
6234
6303
|
// src/core/constants/Headers.ts
|
|
6235
6304
|
var LOCAL_FILE_HEADER = "PK";
|
|
6236
6305
|
var CENTRAL_FILE_HEADER = "PK";
|
|
@@ -6238,9 +6307,6 @@ var NeoZipKit = (() => {
|
|
|
6238
6307
|
var ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x07";
|
|
6239
6308
|
var ZIP64_CENTRAL_DIRECTORY_END = "PK";
|
|
6240
6309
|
var DATA_DESCRIPTOR = "PK\x07\b";
|
|
6241
|
-
var TIMESTAMP_SUBMITTED = "META-INF/TS-SUBMIT.OTS";
|
|
6242
|
-
var TIMESTAMP_METADATA = "META-INF/TIMESTAMP.OTS";
|
|
6243
|
-
var TOKENIZED_METADATA = "META-INF/NZIP.TOKEN";
|
|
6244
6310
|
var LOCAL_HDR = {
|
|
6245
6311
|
SIZE: 30,
|
|
6246
6312
|
// LOC header size in bytes
|
|
@@ -6481,7 +6547,7 @@ var NeoZipKit = (() => {
|
|
|
6481
6547
|
IBM2: 102,
|
|
6482
6548
|
// IBM S/390 (Z390) - Attribute
|
|
6483
6549
|
SHA256: 334,
|
|
6484
|
-
//
|
|
6550
|
+
// NeoZip Extra Field: 32-byte SHA-256 of uncompressed payload (APPNOTE §5)
|
|
6485
6551
|
NEO_CRYPTO: 590,
|
|
6486
6552
|
// NeoEncrypt metadata (see docs/NEO_CRYPTO_FORMAT.md); LE on wire: 4E 02
|
|
6487
6553
|
POSZIP: 18064,
|
|
@@ -7633,7 +7699,13 @@ var NeoZipKit = (() => {
|
|
|
7633
7699
|
this.gid = null;
|
|
7634
7700
|
// Group ID
|
|
7635
7701
|
this.sha256 = null;
|
|
7636
|
-
// SHA-256 hash of the file
|
|
7702
|
+
// SHA-256 hash of the file (Extra Field 0x014E, bare content digest)
|
|
7703
|
+
/**
|
|
7704
|
+
* APPNOTE §6.3 v1 Merkle leaf: SHA-256(0x00 ‖ uncompressed payload).
|
|
7705
|
+
* In-memory only (not written as a ZIP Extra today). Filled in the same streaming hash
|
|
7706
|
+
* pass as `sha256` during create/extract — never reconstruct by re-reading payload when set.
|
|
7707
|
+
*/
|
|
7708
|
+
this.merkleLeafV1 = null;
|
|
7637
7709
|
// Symbolic link data
|
|
7638
7710
|
this.isSymlink = false;
|
|
7639
7711
|
// Entry is a symbolic link
|
|
@@ -8557,24 +8629,30 @@ var NeoZipKit = (() => {
|
|
|
8557
8629
|
// Incremental hash calculation state (from StreamHashCalculator)
|
|
8558
8630
|
this.crc32State = ~0;
|
|
8559
8631
|
this.sha256Hash = null;
|
|
8632
|
+
/** Domain-separated leaf hasher (APPNOTE §6.3): SHA-256(0x00 ‖ payload). */
|
|
8633
|
+
this.merkleLeafHash = null;
|
|
8560
8634
|
this.useSHA256 = false;
|
|
8561
8635
|
// Hash accumulation state (from HashAccumulator)
|
|
8562
8636
|
this.hashes = [];
|
|
8563
8637
|
this.xorResult = Buffer.alloc(32, 0);
|
|
8564
8638
|
this.enableAccumulation = false;
|
|
8565
|
-
// Merkle tree state (from MerkleTree)
|
|
8639
|
+
// Merkle tree state (from MerkleTree) — generic digest folding; archive roots use merkle/MerkleRoot.ts
|
|
8566
8640
|
this.merkleLeaves = [];
|
|
8567
8641
|
this.merkleLayers = [];
|
|
8568
8642
|
this.merkleOptions = {
|
|
8569
8643
|
hashLeaves: false,
|
|
8570
|
-
|
|
8571
|
-
|
|
8644
|
+
// Archive content roots sort by path in MerkleRoot; generic accumulator keeps insertion order.
|
|
8645
|
+
sortLeaves: false,
|
|
8646
|
+
sortPairs: false,
|
|
8647
|
+
// match APPNOTE v0 odd-leaf rule when used as a generic Bitcoin-style tree
|
|
8572
8648
|
duplicateOdd: true
|
|
8573
8649
|
};
|
|
8574
8650
|
this.useSHA256 = options?.useSHA256 || false;
|
|
8575
8651
|
this.enableAccumulation = options?.enableAccumulation || false;
|
|
8576
8652
|
if (this.useSHA256) {
|
|
8577
8653
|
this.sha256Hash = createHash("sha256");
|
|
8654
|
+
this.merkleLeafHash = createHash("sha256");
|
|
8655
|
+
this.merkleLeafHash.update(Buffer.from([0]));
|
|
8578
8656
|
}
|
|
8579
8657
|
}
|
|
8580
8658
|
// ============================================================================
|
|
@@ -8592,6 +8670,9 @@ var NeoZipKit = (() => {
|
|
|
8592
8670
|
if (this.sha256Hash) {
|
|
8593
8671
|
this.sha256Hash.update(chunk);
|
|
8594
8672
|
}
|
|
8673
|
+
if (this.merkleLeafHash) {
|
|
8674
|
+
this.merkleLeafHash.update(chunk);
|
|
8675
|
+
}
|
|
8595
8676
|
}
|
|
8596
8677
|
/**
|
|
8597
8678
|
* Get final CRC-32 value
|
|
@@ -8601,7 +8682,7 @@ var NeoZipKit = (() => {
|
|
|
8601
8682
|
return ~this.crc32State >>> 0;
|
|
8602
8683
|
}
|
|
8603
8684
|
/**
|
|
8604
|
-
* Get final SHA-256 hash as hex string
|
|
8685
|
+
* Get final SHA-256 hash as hex string (Extra Field 0x014E — bare content digest)
|
|
8605
8686
|
* @returns SHA-256 hash as hex string, or null if SHA-256 not enabled
|
|
8606
8687
|
*/
|
|
8607
8688
|
finalizeSHA256() {
|
|
@@ -8610,13 +8691,46 @@ var NeoZipKit = (() => {
|
|
|
8610
8691
|
}
|
|
8611
8692
|
return null;
|
|
8612
8693
|
}
|
|
8694
|
+
/**
|
|
8695
|
+
* Get APPNOTE §6.3 v1 Merkle leaf: SHA-256(0x00 ‖ uncompressed payload) as hex.
|
|
8696
|
+
* Consumes the leaf hasher; call after or instead of finalizeSHA256 for dual streams.
|
|
8697
|
+
*/
|
|
8698
|
+
finalizeMerkleLeafV1() {
|
|
8699
|
+
if (this.merkleLeafHash) {
|
|
8700
|
+
return this.merkleLeafHash.digest("hex");
|
|
8701
|
+
}
|
|
8702
|
+
return null;
|
|
8703
|
+
}
|
|
8704
|
+
/**
|
|
8705
|
+
* Apply stream digests to a ZIP entry in one place.
|
|
8706
|
+
*
|
|
8707
|
+
* When SHA-256 was enabled, both Extra Field 0x014E (`sha256`) and the v1 Merkle leaf
|
|
8708
|
+
* (`merkleLeafV1`) are finalized from the **same** chunk stream — no second payload pass.
|
|
8709
|
+
*/
|
|
8710
|
+
applyToEntry(entry, options) {
|
|
8711
|
+
if (options.setCrc) {
|
|
8712
|
+
entry.crc = this.finalizeCRC32();
|
|
8713
|
+
}
|
|
8714
|
+
if (options.setSha256) {
|
|
8715
|
+
const digest = this.finalizeSHA256();
|
|
8716
|
+
const leaf = this.finalizeMerkleLeafV1();
|
|
8717
|
+
if (digest) {
|
|
8718
|
+
entry.sha256 = digest;
|
|
8719
|
+
}
|
|
8720
|
+
if (leaf) {
|
|
8721
|
+
entry.merkleLeafV1 = leaf;
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8613
8725
|
/**
|
|
8614
8726
|
* Reset the incremental hash calculation state
|
|
8615
8727
|
*/
|
|
8616
8728
|
reset() {
|
|
8617
8729
|
this.crc32State = ~0;
|
|
8618
|
-
if (this.
|
|
8730
|
+
if (this.useSHA256) {
|
|
8619
8731
|
this.sha256Hash = createHash("sha256");
|
|
8732
|
+
this.merkleLeafHash = createHash("sha256");
|
|
8733
|
+
this.merkleLeafHash.update(Buffer.from([0]));
|
|
8620
8734
|
}
|
|
8621
8735
|
}
|
|
8622
8736
|
// ============================================================================
|
|
@@ -9224,18 +9338,16 @@ var NeoZipKit = (() => {
|
|
|
9224
9338
|
const hashCalculator = needsHashCalculation ? new HashCalculator({ useSHA256: options?.useSHA256 && !entry.sha256 || false }) : null;
|
|
9225
9339
|
if (hashCalculator) {
|
|
9226
9340
|
hashCalculator.update(data);
|
|
9227
|
-
|
|
9228
|
-
entry.crc
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
entry.sha256 = hashCalculator.finalizeSHA256();
|
|
9232
|
-
}
|
|
9341
|
+
hashCalculator.applyToEntry(entry, {
|
|
9342
|
+
setCrc: !entry.crc || entry.crc === 0,
|
|
9343
|
+
setSha256: !!(options?.useSHA256 && !entry.sha256)
|
|
9344
|
+
});
|
|
9233
9345
|
this.log(`Final hashes: CRC32=0x${entry.crc.toString(16).padStart(8, "0")}, SHA256=${entry.sha256 || "N/A"}`);
|
|
9234
9346
|
}
|
|
9235
9347
|
const enc = options?.encryptionMethod;
|
|
9236
9348
|
const useZipCrypto = enc === "zipcrypto";
|
|
9237
|
-
const
|
|
9238
|
-
const
|
|
9349
|
+
const useAes = enc === "aes256";
|
|
9350
|
+
const useNeo = !!options?.password && !useZipCrypto && !useAes;
|
|
9239
9351
|
if (options?.password) {
|
|
9240
9352
|
const label = useZipCrypto ? "ZipCrypto" : useNeo ? "NeoEncrypt (AES-256)" : "WinZip AES-256";
|
|
9241
9353
|
this.log(`Encrypting compressed data for entry: ${entry.filename} (method: ${label})`);
|
|
@@ -9438,9 +9550,11 @@ var NeoZipKit = (() => {
|
|
|
9438
9550
|
entry.uncompressedSize = fileData.length;
|
|
9439
9551
|
const hashCalculator = new HashCalculator({ useSHA256: cmpOptions?.useSHA256 || false });
|
|
9440
9552
|
hashCalculator.update(fileData);
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9553
|
+
hashCalculator.applyToEntry(entry, {
|
|
9554
|
+
setCrc: true,
|
|
9555
|
+
setSha256: !!cmpOptions?.useSHA256
|
|
9556
|
+
});
|
|
9557
|
+
if (entry.sha256) {
|
|
9444
9558
|
this.log(`SHA-256 calculated: ${entry.sha256}`);
|
|
9445
9559
|
}
|
|
9446
9560
|
return await this.compressData(entry, fileData, cmpOptions);
|
|
@@ -9587,17 +9701,21 @@ var NeoZipKit = (() => {
|
|
|
9587
9701
|
} else {
|
|
9588
9702
|
throw new Error(`Unsupported compression method: ${method}`);
|
|
9589
9703
|
}
|
|
9704
|
+
const hashCalc = new HashCalculator({ useSHA256: true });
|
|
9705
|
+
hashCalc.update(outBuf);
|
|
9706
|
+
const calculatedCRC = hashCalc.finalizeCRC32();
|
|
9707
|
+
const calculatedHash = hashCalc.finalizeSHA256();
|
|
9708
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
9709
|
+
if (leaf) {
|
|
9710
|
+
entry.merkleLeafV1 = leaf;
|
|
9711
|
+
}
|
|
9590
9712
|
if (!skipHashCheck) {
|
|
9591
9713
|
if (entry.sha256) {
|
|
9592
|
-
|
|
9593
|
-
if (!isValid) {
|
|
9714
|
+
if (calculatedHash !== entry.sha256) {
|
|
9594
9715
|
throw new Error(Errors_default.INVALID_SHA256);
|
|
9595
9716
|
}
|
|
9596
|
-
} else {
|
|
9597
|
-
|
|
9598
|
-
if (!isValid) {
|
|
9599
|
-
throw new Error(Errors_default.INVALID_CRC);
|
|
9600
|
-
}
|
|
9717
|
+
} else if (calculatedCRC !== entry.crc) {
|
|
9718
|
+
throw new Error(Errors_default.INVALID_CRC);
|
|
9601
9719
|
}
|
|
9602
9720
|
}
|
|
9603
9721
|
return outBuf;
|
|
@@ -9608,6 +9726,176 @@ var NeoZipKit = (() => {
|
|
|
9608
9726
|
var ZipDecompress = _ZipDecompress;
|
|
9609
9727
|
var ZipDecompress_default = ZipDecompress;
|
|
9610
9728
|
|
|
9729
|
+
// src/core/merkle/MerkleRoot.ts
|
|
9730
|
+
var LEAF_DOMAIN = Buffer.from([0]);
|
|
9731
|
+
var NODE_DOMAIN = Buffer.from([1]);
|
|
9732
|
+
function sha2562(data) {
|
|
9733
|
+
const h = createHash("sha256");
|
|
9734
|
+
if (Array.isArray(data)) {
|
|
9735
|
+
for (const part of data) {
|
|
9736
|
+
h.update(part);
|
|
9737
|
+
}
|
|
9738
|
+
} else {
|
|
9739
|
+
h.update(data);
|
|
9740
|
+
}
|
|
9741
|
+
return h.digest();
|
|
9742
|
+
}
|
|
9743
|
+
function asBuffer(data, encoding) {
|
|
9744
|
+
if (typeof data === "string") {
|
|
9745
|
+
return Buffer.from(data, encoding ?? "hex");
|
|
9746
|
+
}
|
|
9747
|
+
return Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
9748
|
+
}
|
|
9749
|
+
function normalizeMerklePath(path) {
|
|
9750
|
+
let p = path.replace(/\\/g, "/");
|
|
9751
|
+
if (p.startsWith("./")) {
|
|
9752
|
+
p = p.slice(2);
|
|
9753
|
+
}
|
|
9754
|
+
while (p.startsWith("/")) {
|
|
9755
|
+
p = p.slice(1);
|
|
9756
|
+
}
|
|
9757
|
+
if (typeof p.normalize === "function") {
|
|
9758
|
+
p = p.normalize("NFC");
|
|
9759
|
+
}
|
|
9760
|
+
return p;
|
|
9761
|
+
}
|
|
9762
|
+
function pathSortKey(path, algorithm) {
|
|
9763
|
+
const normalized = algorithm === "v1" ? normalizeMerklePath(path) : path;
|
|
9764
|
+
return Buffer.from(normalized, "utf8");
|
|
9765
|
+
}
|
|
9766
|
+
function contentDigest(content) {
|
|
9767
|
+
return sha2562(asBuffer(content));
|
|
9768
|
+
}
|
|
9769
|
+
function leafHashV0(contentOrDigest, isDigest = false) {
|
|
9770
|
+
const buf = asBuffer(contentOrDigest);
|
|
9771
|
+
return isDigest ? buf : sha2562(buf);
|
|
9772
|
+
}
|
|
9773
|
+
function leafHashV1(content) {
|
|
9774
|
+
return sha2562([LEAF_DOMAIN, asBuffer(content)]);
|
|
9775
|
+
}
|
|
9776
|
+
function parentHash(left, right, algorithm) {
|
|
9777
|
+
if (algorithm === "v1") {
|
|
9778
|
+
return sha2562([NODE_DOMAIN, left, right]);
|
|
9779
|
+
}
|
|
9780
|
+
return sha2562([left, right]);
|
|
9781
|
+
}
|
|
9782
|
+
function merkleRootFromLeaves(leaves, algorithm) {
|
|
9783
|
+
if (leaves.length === 0) {
|
|
9784
|
+
return null;
|
|
9785
|
+
}
|
|
9786
|
+
let level = leaves.map((l) => Buffer.from(l));
|
|
9787
|
+
while (level.length > 1) {
|
|
9788
|
+
const next = [];
|
|
9789
|
+
for (let i = 0; i + 1 < level.length; i += 2) {
|
|
9790
|
+
next.push(parentHash(level[i], level[i + 1], algorithm));
|
|
9791
|
+
}
|
|
9792
|
+
if (level.length % 2 === 1) {
|
|
9793
|
+
const last = level[level.length - 1];
|
|
9794
|
+
if (algorithm === "v0") {
|
|
9795
|
+
next.push(parentHash(last, last, algorithm));
|
|
9796
|
+
} else {
|
|
9797
|
+
next.push(last);
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
level = next;
|
|
9801
|
+
}
|
|
9802
|
+
return level[0].toString("hex");
|
|
9803
|
+
}
|
|
9804
|
+
function resolveLeaf(entry, algorithm) {
|
|
9805
|
+
if (algorithm === "v0") {
|
|
9806
|
+
if (entry.contentSha256) {
|
|
9807
|
+
const d = asBuffer(entry.contentSha256, typeof entry.contentSha256 === "string" ? "hex" : void 0);
|
|
9808
|
+
if (d.length !== 32) {
|
|
9809
|
+
return null;
|
|
9810
|
+
}
|
|
9811
|
+
return d;
|
|
9812
|
+
}
|
|
9813
|
+
if (entry.content) {
|
|
9814
|
+
return contentDigest(entry.content);
|
|
9815
|
+
}
|
|
9816
|
+
return null;
|
|
9817
|
+
}
|
|
9818
|
+
if (entry.merkleLeafV1) {
|
|
9819
|
+
const d = asBuffer(
|
|
9820
|
+
entry.merkleLeafV1,
|
|
9821
|
+
typeof entry.merkleLeafV1 === "string" ? "hex" : void 0
|
|
9822
|
+
);
|
|
9823
|
+
if (d.length !== 32) {
|
|
9824
|
+
return null;
|
|
9825
|
+
}
|
|
9826
|
+
return d;
|
|
9827
|
+
}
|
|
9828
|
+
if (entry.content) {
|
|
9829
|
+
return leafHashV1(entry.content);
|
|
9830
|
+
}
|
|
9831
|
+
return null;
|
|
9832
|
+
}
|
|
9833
|
+
function computeArchiveMerkleRoot(entries, algorithm = "v1") {
|
|
9834
|
+
const prepared = [];
|
|
9835
|
+
for (const entry of entries) {
|
|
9836
|
+
if (isMetaInfPath(entry.path || "")) {
|
|
9837
|
+
continue;
|
|
9838
|
+
}
|
|
9839
|
+
const leaf = resolveLeaf(entry, algorithm);
|
|
9840
|
+
if (!leaf) {
|
|
9841
|
+
continue;
|
|
9842
|
+
}
|
|
9843
|
+
prepared.push({
|
|
9844
|
+
key: pathSortKey(entry.path || "", algorithm),
|
|
9845
|
+
leaf
|
|
9846
|
+
});
|
|
9847
|
+
}
|
|
9848
|
+
if (prepared.length === 0) {
|
|
9849
|
+
return null;
|
|
9850
|
+
}
|
|
9851
|
+
prepared.sort((a, b) => Buffer.compare(a.key, b.key));
|
|
9852
|
+
return merkleRootFromLeaves(
|
|
9853
|
+
prepared.map((p) => p.leaf),
|
|
9854
|
+
algorithm
|
|
9855
|
+
);
|
|
9856
|
+
}
|
|
9857
|
+
function computeMerkleRootV0FromDigests(entries) {
|
|
9858
|
+
return computeArchiveMerkleRoot(
|
|
9859
|
+
entries.map((e) => ({ path: e.path, contentSha256: e.sha256 })),
|
|
9860
|
+
"v0"
|
|
9861
|
+
);
|
|
9862
|
+
}
|
|
9863
|
+
function computeMerkleRootV1FromContents(entries) {
|
|
9864
|
+
return computeArchiveMerkleRoot(
|
|
9865
|
+
entries.map((e) => ({ path: e.path, content: e.content })),
|
|
9866
|
+
"v1"
|
|
9867
|
+
);
|
|
9868
|
+
}
|
|
9869
|
+
function computeMerkleRootV1FromLeaves(entries) {
|
|
9870
|
+
return computeArchiveMerkleRoot(
|
|
9871
|
+
entries.map((e) => ({ path: e.path, merkleLeafV1: e.merkleLeafV1 })),
|
|
9872
|
+
"v1"
|
|
9873
|
+
);
|
|
9874
|
+
}
|
|
9875
|
+
function matchMerkleRoot(entries, declaredRoot) {
|
|
9876
|
+
const expected = declaredRoot.toLowerCase().replace(/^0x/, "");
|
|
9877
|
+
if (!/^[0-9a-f]{64}$/.test(expected)) {
|
|
9878
|
+
return null;
|
|
9879
|
+
}
|
|
9880
|
+
const hasV1Material = entries.some(
|
|
9881
|
+
(e) => !isMetaInfPath(e.path || "") && (!!e.merkleLeafV1 || !!e.content)
|
|
9882
|
+
);
|
|
9883
|
+
if (hasV1Material) {
|
|
9884
|
+
const v1 = computeArchiveMerkleRoot(entries, "v1");
|
|
9885
|
+
if (v1 && v1 === expected) {
|
|
9886
|
+
return { algorithm: "v1", security: "high" };
|
|
9887
|
+
}
|
|
9888
|
+
}
|
|
9889
|
+
const v0 = computeArchiveMerkleRoot(entries, "v0");
|
|
9890
|
+
if (v0 && v0 === expected) {
|
|
9891
|
+
return { algorithm: "v0", security: "legacy" };
|
|
9892
|
+
}
|
|
9893
|
+
if (hasV1Material) {
|
|
9894
|
+
return null;
|
|
9895
|
+
}
|
|
9896
|
+
return null;
|
|
9897
|
+
}
|
|
9898
|
+
|
|
9611
9899
|
// src/core/Zipkit.ts
|
|
9612
9900
|
var Zipkit = class {
|
|
9613
9901
|
/**
|
|
@@ -9827,15 +10115,22 @@ var NeoZipKit = (() => {
|
|
|
9827
10115
|
return this.zipEntries;
|
|
9828
10116
|
}
|
|
9829
10117
|
/**
|
|
9830
|
-
* Get specific ZIP entry by filename from zipEntries[] array
|
|
9831
|
-
*
|
|
10118
|
+
* Get specific ZIP entry by filename from zipEntries[] array.
|
|
10119
|
+
* Reserved META-INF paths (APPNOTE §2.3) also match ASCII case-insensitively
|
|
10120
|
+
* when an exact spelling is not found.
|
|
10121
|
+
*
|
|
9832
10122
|
* @param filename - The name/path of the file/directory to find
|
|
9833
10123
|
* @returns ZipEntry object if found, null if not found
|
|
9834
10124
|
*/
|
|
9835
10125
|
getZipEntry(filename) {
|
|
9836
10126
|
try {
|
|
9837
10127
|
const centralDir = this.getDirectory();
|
|
9838
|
-
|
|
10128
|
+
const exact = centralDir.find((entry) => entry.filename === filename);
|
|
10129
|
+
if (exact) return exact;
|
|
10130
|
+
if (isReservedMetaPath(filename)) {
|
|
10131
|
+
return findReservedMetaEntry(centralDir, filename);
|
|
10132
|
+
}
|
|
10133
|
+
return null;
|
|
9839
10134
|
} catch (error) {
|
|
9840
10135
|
return null;
|
|
9841
10136
|
}
|
|
@@ -10079,14 +10374,23 @@ var NeoZipKit = (() => {
|
|
|
10079
10374
|
return ceBuf;
|
|
10080
10375
|
}
|
|
10081
10376
|
/**
|
|
10082
|
-
* Calculate Merkle
|
|
10083
|
-
*
|
|
10084
|
-
*
|
|
10085
|
-
*
|
|
10086
|
-
*
|
|
10087
|
-
*
|
|
10377
|
+
* Calculate Merkle root of archive content (non-META-INF entries).
|
|
10378
|
+
*
|
|
10379
|
+
* Per NEOZIP_APPNOTE.md §6:
|
|
10380
|
+
* - Default **v1**: domain-separated leaves SHA-256(0x00 ‖ uncompressed bytes) and
|
|
10381
|
+
* SHA-256(0x01 ‖ left ‖ right) parents; paths NFC-normalized before sort.
|
|
10382
|
+
* - Legacy **v0**: bare 0x014E digests as leaves, duplicate-odd pairing, path as stored.
|
|
10383
|
+
*
|
|
10384
|
+
* When only Extra Field digests are available (typical after open), **v1 requires
|
|
10385
|
+
* payload bytes**. This sync method therefore returns null for v1; use
|
|
10386
|
+
* {@link getMerkleRootAsync} (extracts content) or `{ algorithm: 'v0' }`.
|
|
10387
|
+
* When you already hold payloads, use {@link merkleRootFromEntries}.
|
|
10388
|
+
*
|
|
10389
|
+
* @param options.algorithm - `v1` (default) or legacy `v0`
|
|
10390
|
+
* @returns Lowercase hex Merkle root, or null if calculation fails
|
|
10088
10391
|
*/
|
|
10089
|
-
getMerkleRoot() {
|
|
10392
|
+
getMerkleRoot(options) {
|
|
10393
|
+
const algorithm = options?.algorithm ?? "v1";
|
|
10090
10394
|
let zipEntries;
|
|
10091
10395
|
try {
|
|
10092
10396
|
zipEntries = this.getDirectory();
|
|
@@ -10098,40 +10402,40 @@ var NeoZipKit = (() => {
|
|
|
10098
10402
|
if (!zipEntries || zipEntries.length === 0) {
|
|
10099
10403
|
return null;
|
|
10100
10404
|
}
|
|
10101
|
-
const
|
|
10102
|
-
|
|
10103
|
-
const
|
|
10104
|
-
return
|
|
10105
|
-
}
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
if (!merkleRoot) {
|
|
10114
|
-
return null;
|
|
10405
|
+
const contentEntries = zipEntries.filter((entry) => !isMetaInfPath(entry.filename || ""));
|
|
10406
|
+
if (algorithm === "v0") {
|
|
10407
|
+
const digests = contentEntries.filter((e) => e.sha256).map((e) => ({ path: e.filename || "", sha256: e.sha256 }));
|
|
10408
|
+
return computeMerkleRootV0FromDigests(digests);
|
|
10409
|
+
}
|
|
10410
|
+
if (contentEntries.length > 0 && contentEntries.every((e) => !!e.merkleLeafV1)) {
|
|
10411
|
+
return computeMerkleRootV1FromLeaves(
|
|
10412
|
+
contentEntries.map((e) => ({
|
|
10413
|
+
path: e.filename || "",
|
|
10414
|
+
merkleLeafV1: e.merkleLeafV1
|
|
10415
|
+
}))
|
|
10416
|
+
);
|
|
10115
10417
|
}
|
|
10116
|
-
return
|
|
10418
|
+
return null;
|
|
10117
10419
|
}
|
|
10118
10420
|
/**
|
|
10119
|
-
* Calculate Merkle
|
|
10120
|
-
*
|
|
10121
|
-
*
|
|
10122
|
-
*
|
|
10123
|
-
*
|
|
10124
|
-
*
|
|
10421
|
+
* Calculate Merkle root (preferred for v1 / new archives).
|
|
10422
|
+
*
|
|
10423
|
+
* **v1 (default):** uses precomputed stream leaves when available; otherwise extracts
|
|
10424
|
+
* each content entry once and folds APPNOTE §6.3 root (no second content hash pass
|
|
10425
|
+
* when extract captures merkleLeafV1).
|
|
10426
|
+
* **v0:** uses Extra Field digests only (no extract), APPNOTE §6.2.
|
|
10427
|
+
*
|
|
10428
|
+
* META-INF/** is excluded. File-streaming mode: use ZipkitNode override (testEntry
|
|
10429
|
+
* pipeline) rather than buffering the whole archive.
|
|
10430
|
+
*
|
|
10431
|
+
* @param options.algorithm - `v1` (default) or legacy `v0`
|
|
10432
|
+
* @returns Promise resolving to lowercase hex root, or null
|
|
10125
10433
|
*/
|
|
10126
|
-
async getMerkleRootAsync() {
|
|
10434
|
+
async getMerkleRootAsync(options) {
|
|
10435
|
+
const algorithm = options?.algorithm ?? "v1";
|
|
10127
10436
|
let zipEntries = [];
|
|
10128
10437
|
try {
|
|
10129
|
-
|
|
10130
|
-
zipEntries = this.getDirectory();
|
|
10131
|
-
} else {
|
|
10132
|
-
Logger.error("getMerkleRootAsync() called on file-based ZIP. Use ZipkitServer.getMerkleRootAsync() instead.");
|
|
10133
|
-
return null;
|
|
10134
|
-
}
|
|
10438
|
+
zipEntries = this.getDirectory();
|
|
10135
10439
|
} catch (error) {
|
|
10136
10440
|
Logger.error(`Failed to get directory for merkle root calculation: ${error instanceof Error ? error.message : String(error)}`);
|
|
10137
10441
|
return null;
|
|
@@ -10139,22 +10443,60 @@ var NeoZipKit = (() => {
|
|
|
10139
10443
|
if (!zipEntries || zipEntries.length === 0) {
|
|
10140
10444
|
return null;
|
|
10141
10445
|
}
|
|
10142
|
-
const
|
|
10143
|
-
|
|
10144
|
-
const
|
|
10145
|
-
return
|
|
10146
|
-
}
|
|
10446
|
+
const contentEntries = zipEntries.filter((entry) => !isMetaInfPath(entry.filename || ""));
|
|
10447
|
+
if (algorithm === "v0") {
|
|
10448
|
+
const digests = contentEntries.filter((e) => e.sha256).map((e) => ({ path: e.filename || "", sha256: e.sha256 }));
|
|
10449
|
+
return computeMerkleRootV0FromDigests(digests);
|
|
10450
|
+
}
|
|
10451
|
+
if (contentEntries.length > 0 && contentEntries.every((e) => !!e.merkleLeafV1)) {
|
|
10452
|
+
return computeMerkleRootV1FromLeaves(
|
|
10453
|
+
contentEntries.map((e) => ({
|
|
10454
|
+
path: e.filename || "",
|
|
10455
|
+
merkleLeafV1: e.merkleLeafV1
|
|
10456
|
+
}))
|
|
10457
|
+
);
|
|
10458
|
+
}
|
|
10459
|
+
if (!this.inBuffer) {
|
|
10460
|
+
Logger.error(
|
|
10461
|
+
"getMerkleRootAsync() called on file-based ZIP without precomputed merkle leaves. Use ZipkitNode (streaming testEntry) or ensure create captured merkleLeafV1."
|
|
10462
|
+
);
|
|
10463
|
+
return null;
|
|
10464
|
+
}
|
|
10147
10465
|
for (const entry of contentEntries) {
|
|
10148
|
-
|
|
10149
|
-
const
|
|
10150
|
-
|
|
10466
|
+
try {
|
|
10467
|
+
const data = await this.extract(entry, false);
|
|
10468
|
+
if (entry.merkleLeafV1) {
|
|
10469
|
+
continue;
|
|
10470
|
+
}
|
|
10471
|
+
if (data === null || data.length === 0) {
|
|
10472
|
+
entry.merkleLeafV1 = leafHashV1(Buffer.alloc(0)).toString("hex");
|
|
10473
|
+
} else {
|
|
10474
|
+
entry.merkleLeafV1 = leafHashV1(data).toString("hex");
|
|
10475
|
+
}
|
|
10476
|
+
} catch (error) {
|
|
10477
|
+
Logger.error(
|
|
10478
|
+
`getMerkleRootAsync(): failed to extract ${entry.filename}: ${error instanceof Error ? error.message : String(error)}`
|
|
10479
|
+
);
|
|
10480
|
+
return null;
|
|
10151
10481
|
}
|
|
10152
10482
|
}
|
|
10153
|
-
|
|
10154
|
-
if (!merkleRoot) {
|
|
10483
|
+
if (!contentEntries.every((e) => !!e.merkleLeafV1)) {
|
|
10155
10484
|
return null;
|
|
10156
10485
|
}
|
|
10157
|
-
return
|
|
10486
|
+
return computeMerkleRootV1FromLeaves(
|
|
10487
|
+
contentEntries.map((e) => ({
|
|
10488
|
+
path: e.filename || "",
|
|
10489
|
+
merkleLeafV1: e.merkleLeafV1
|
|
10490
|
+
}))
|
|
10491
|
+
);
|
|
10492
|
+
}
|
|
10493
|
+
/**
|
|
10494
|
+
* Compute Merkle root from known digests and/or payloads (APPNOTE §6).
|
|
10495
|
+
* Prefer this during create when uncompressed bytes are still in hand, or when
|
|
10496
|
+
* stream leaves (`merkleLeafV1`) were already produced.
|
|
10497
|
+
*/
|
|
10498
|
+
static merkleRootFromEntries(entries, algorithm = "v1") {
|
|
10499
|
+
return computeArchiveMerkleRoot(entries, algorithm);
|
|
10158
10500
|
}
|
|
10159
10501
|
};
|
|
10160
10502
|
Zipkit.version = NEOZIPKIT_INFO.version;
|
|
@@ -10508,7 +10850,7 @@ var NeoZipKit = (() => {
|
|
|
10508
10850
|
}
|
|
10509
10851
|
}
|
|
10510
10852
|
var isMetadataFile = (filename) => {
|
|
10511
|
-
return filename
|
|
10853
|
+
return isMetaInfPath(filename);
|
|
10512
10854
|
};
|
|
10513
10855
|
|
|
10514
10856
|
// src/core/components/ProgressTracker.ts
|
|
@@ -10727,7 +11069,7 @@ var NeoZipKit = (() => {
|
|
|
10727
11069
|
var currentDate = /* @__PURE__ */ new Date();
|
|
10728
11070
|
var releaseDate = `${String(currentDate.getMonth() + 1).padStart(2, "0")}-${String(currentDate.getDate()).padStart(2, "0")}-${currentDate.getFullYear()}`;
|
|
10729
11071
|
var VERSION = {
|
|
10730
|
-
number: "1.0.
|
|
11072
|
+
number: "1.0.5",
|
|
10731
11073
|
// packageJson.version
|
|
10732
11074
|
date: releaseDate
|
|
10733
11075
|
};
|
|
@@ -10845,7 +11187,7 @@ var NeoZipKit = (() => {
|
|
|
10845
11187
|
const buffers = [];
|
|
10846
11188
|
for (const entry of zipEntries) {
|
|
10847
11189
|
if (!entry.isUpdated) {
|
|
10848
|
-
if (entry.filename
|
|
11190
|
+
if (isMetaInfPath(entry.filename || "")) {
|
|
10849
11191
|
entry.isUpdated = true;
|
|
10850
11192
|
} else {
|
|
10851
11193
|
const inData = await super.copyEntry(entry);
|
|
@@ -10870,7 +11212,7 @@ var NeoZipKit = (() => {
|
|
|
10870
11212
|
_onEntryDone(entry, "Written (pre-compressed)");
|
|
10871
11213
|
continue;
|
|
10872
11214
|
}
|
|
10873
|
-
if (entry.filename
|
|
11215
|
+
if (isMetaInfPath(entry.filename || "")) {
|
|
10874
11216
|
const buffer = Buffer.from(fileBuffer);
|
|
10875
11217
|
entry.crc = crc32(buffer);
|
|
10876
11218
|
entry.cmpMethod = 0;
|
|
@@ -10911,7 +11253,7 @@ var NeoZipKit = (() => {
|
|
|
10911
11253
|
* @returns boolean - True if the file is a metadata file
|
|
10912
11254
|
*/
|
|
10913
11255
|
isMetadataFile(filename) {
|
|
10914
|
-
return filename
|
|
11256
|
+
return isMetaInfPath(filename);
|
|
10915
11257
|
}
|
|
10916
11258
|
// --------------------------------------
|
|
10917
11259
|
// Helper method: Add a file to the ZIP with compression
|