neozipkit 0.8.0 → 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 +57 -21
- package/WHATS_NEW.md +146 -0
- 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 +28 -0
- package/dist/browser/zstd-browser-shim.d.ts.map +1 -0
- package/dist/browser/zstd-browser-shim.js +66 -0
- package/dist/browser/zstd-browser-shim.js.map +1 -0
- package/dist/browser-esm/index.js +2110 -41755
- package/dist/browser-esm/index.js.map +4 -4
- package/dist/browser-umd/index.js +2110 -41755
- package/dist/browser-umd/index.js.map +4 -4
- package/dist/browser-umd/index.min.js +3 -21
- package/dist/core/ZipCompress.d.ts +2 -7
- package/dist/core/ZipCompress.d.ts.map +1 -1
- package/dist/core/ZipCompress.js +57 -33
- package/dist/core/ZipCompress.js.map +1 -1
- package/dist/core/ZipDecompress.d.ts +1 -3
- package/dist/core/ZipDecompress.d.ts.map +1 -1
- package/dist/core/ZipDecompress.js +27 -17
- 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/encryption/AesCrypto.d.ts +55 -11
- package/dist/core/encryption/AesCrypto.d.ts.map +1 -1
- package/dist/core/encryption/AesCrypto.js +223 -68
- package/dist/core/encryption/AesCrypto.js.map +1 -1
- package/dist/core/encryption/index.d.ts +1 -1
- package/dist/core/encryption/index.d.ts.map +1 -1
- package/dist/core/encryption/index.js +3 -1
- package/dist/core/encryption/index.js.map +1 -1
- 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/LegacyZstd.d.ts +67 -0
- package/dist/node/LegacyZstd.d.ts.map +1 -0
- package/dist/node/LegacyZstd.js +209 -0
- package/dist/node/LegacyZstd.js.map +1 -0
- package/dist/node/ZipCompressNode.d.ts +9 -7
- package/dist/node/ZipCompressNode.d.ts.map +1 -1
- package/dist/node/ZipCompressNode.js +105 -75
- package/dist/node/ZipCompressNode.js.map +1 -1
- package/dist/node/ZipDecompressNode.d.ts +7 -3
- package/dist/node/ZipDecompressNode.d.ts.map +1 -1
- package/dist/node/ZipDecompressNode.js +68 -56
- package/dist/node/ZipDecompressNode.js.map +1 -1
- package/dist/node/ZipkitNode.d.ts +23 -0
- package/dist/node/ZipkitNode.d.ts.map +1 -1
- package/dist/node/ZipkitNode.js +195 -41
- package/dist/node/ZipkitNode.js.map +1 -1
- package/dist/node/ZstdNode.d.ts +39 -0
- package/dist/node/ZstdNode.d.ts.map +1 -0
- package/dist/node/ZstdNode.js +171 -0
- package/dist/node/ZstdNode.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 +11 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +4 -3
- package/src/browser/ZipkitBrowser.ts +4 -6
- package/src/browser/zstd-browser-shim.ts +76 -0
- package/src/core/ZipCompress.ts +71 -42
- package/src/core/ZipDecompress.ts +35 -18
- 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/encryption/AesCrypto.ts +265 -86
- package/src/core/encryption/index.ts +1 -1
- 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/LegacyZstd.ts +270 -0
- package/src/node/ZipCompressNode.ts +127 -84
- package/src/node/ZipDecompressNode.ts +93 -67
- package/src/node/ZipkitNode.ts +230 -42
- package/src/node/ZstdNode.ts +155 -0
- package/src/node/index.ts +10 -0
- package/dist/core/ZstdManager.d.ts +0 -56
- package/dist/core/ZstdManager.d.ts.map +0 -1
- package/dist/core/ZstdManager.js +0 -144
- package/dist/core/ZstdManager.js.map +0 -1
- package/src/core/ZstdManager.ts +0 -165
|
@@ -45,6 +45,8 @@ export class HashCalculator {
|
|
|
45
45
|
// Incremental hash calculation state (from StreamHashCalculator)
|
|
46
46
|
private crc32State: number = ~0;
|
|
47
47
|
private sha256Hash: crypto.Hash | null = null;
|
|
48
|
+
/** Domain-separated leaf hasher (APPNOTE §6.3): SHA-256(0x00 ‖ payload). */
|
|
49
|
+
private merkleLeafHash: crypto.Hash | null = null;
|
|
48
50
|
private useSHA256: boolean = false;
|
|
49
51
|
|
|
50
52
|
// Hash accumulation state (from HashAccumulator)
|
|
@@ -52,13 +54,15 @@ export class HashCalculator {
|
|
|
52
54
|
private xorResult: Buffer = Buffer.alloc(32, 0);
|
|
53
55
|
private enableAccumulation: boolean = false;
|
|
54
56
|
|
|
55
|
-
// Merkle tree state (from MerkleTree)
|
|
57
|
+
// Merkle tree state (from MerkleTree) — generic digest folding; archive roots use merkle/MerkleRoot.ts
|
|
56
58
|
private merkleLeaves: Buffer[] = [];
|
|
57
59
|
private merkleLayers: Buffer[][] = [];
|
|
58
60
|
private merkleOptions: MerkleTreeOptions = {
|
|
59
61
|
hashLeaves: false,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// Archive content roots sort by path in MerkleRoot; generic accumulator keeps insertion order.
|
|
63
|
+
sortLeaves: false,
|
|
64
|
+
sortPairs: false,
|
|
65
|
+
// match APPNOTE v0 odd-leaf rule when used as a generic Bitcoin-style tree
|
|
62
66
|
duplicateOdd: true
|
|
63
67
|
};
|
|
64
68
|
|
|
@@ -77,6 +81,8 @@ export class HashCalculator {
|
|
|
77
81
|
|
|
78
82
|
if (this.useSHA256) {
|
|
79
83
|
this.sha256Hash = crypto.createHash('sha256');
|
|
84
|
+
this.merkleLeafHash = crypto.createHash('sha256');
|
|
85
|
+
this.merkleLeafHash.update(Buffer.from([0x00]));
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
@@ -95,10 +101,13 @@ export class HashCalculator {
|
|
|
95
101
|
this.crc32State = crc32update(this.crc32State, chunk[i]);
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
// Update SHA-256 incrementally
|
|
104
|
+
// Update SHA-256 and v1 Merkle leaf hash incrementally
|
|
99
105
|
if (this.sha256Hash) {
|
|
100
106
|
this.sha256Hash.update(chunk);
|
|
101
107
|
}
|
|
108
|
+
if (this.merkleLeafHash) {
|
|
109
|
+
this.merkleLeafHash.update(chunk);
|
|
110
|
+
}
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
/**
|
|
@@ -110,7 +119,7 @@ export class HashCalculator {
|
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
/**
|
|
113
|
-
* Get final SHA-256 hash as hex string
|
|
122
|
+
* Get final SHA-256 hash as hex string (Extra Field 0x014E — bare content digest)
|
|
114
123
|
* @returns SHA-256 hash as hex string, or null if SHA-256 not enabled
|
|
115
124
|
*/
|
|
116
125
|
finalizeSHA256(): string | null {
|
|
@@ -120,13 +129,55 @@ export class HashCalculator {
|
|
|
120
129
|
return null;
|
|
121
130
|
}
|
|
122
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Get APPNOTE §6.3 v1 Merkle leaf: SHA-256(0x00 ‖ uncompressed payload) as hex.
|
|
134
|
+
* Consumes the leaf hasher; call after or instead of finalizeSHA256 for dual streams.
|
|
135
|
+
*/
|
|
136
|
+
finalizeMerkleLeafV1(): string | null {
|
|
137
|
+
if (this.merkleLeafHash) {
|
|
138
|
+
return this.merkleLeafHash.digest('hex');
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Apply stream digests to a ZIP entry in one place.
|
|
145
|
+
*
|
|
146
|
+
* When SHA-256 was enabled, both Extra Field 0x014E (`sha256`) and the v1 Merkle leaf
|
|
147
|
+
* (`merkleLeafV1`) are finalized from the **same** chunk stream — no second payload pass.
|
|
148
|
+
*/
|
|
149
|
+
applyToEntry(
|
|
150
|
+
entry: {
|
|
151
|
+
crc: number;
|
|
152
|
+
sha256: string | null;
|
|
153
|
+
merkleLeafV1?: string | null;
|
|
154
|
+
},
|
|
155
|
+
options: { setCrc?: boolean; setSha256?: boolean }
|
|
156
|
+
): void {
|
|
157
|
+
if (options.setCrc) {
|
|
158
|
+
entry.crc = this.finalizeCRC32();
|
|
159
|
+
}
|
|
160
|
+
if (options.setSha256) {
|
|
161
|
+
const digest = this.finalizeSHA256();
|
|
162
|
+
const leaf = this.finalizeMerkleLeafV1();
|
|
163
|
+
if (digest) {
|
|
164
|
+
entry.sha256 = digest;
|
|
165
|
+
}
|
|
166
|
+
if (leaf) {
|
|
167
|
+
entry.merkleLeafV1 = leaf;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
123
172
|
/**
|
|
124
173
|
* Reset the incremental hash calculation state
|
|
125
174
|
*/
|
|
126
175
|
reset(): void {
|
|
127
176
|
this.crc32State = ~0;
|
|
128
|
-
if (this.
|
|
177
|
+
if (this.useSHA256) {
|
|
129
178
|
this.sha256Hash = crypto.createHash('sha256');
|
|
179
|
+
this.merkleLeafHash = crypto.createHash('sha256');
|
|
180
|
+
this.merkleLeafHash.update(Buffer.from([0x00]));
|
|
130
181
|
}
|
|
131
182
|
}
|
|
132
183
|
|
|
@@ -264,6 +315,7 @@ export class HashCalculator {
|
|
|
264
315
|
if (this.merkleOptions.sortPairs && Buffer.compare(left, right) > 0) {
|
|
265
316
|
[left, right] = [right, left];
|
|
266
317
|
}
|
|
318
|
+
// Generic accumulator tree (not APPNOTE archive root — see merkle/MerkleRoot.ts)
|
|
267
319
|
return this.hash(Buffer.concat([left, right]));
|
|
268
320
|
}
|
|
269
321
|
|
|
@@ -6,7 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
import Support from "./Support";
|
|
8
8
|
import Errors from "../constants/Errors";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
isMetaInfPath,
|
|
11
|
+
findReservedMetaEntry,
|
|
12
|
+
isReservedMetaPath,
|
|
13
|
+
asciiPathEqualsIgnoreCase,
|
|
14
|
+
} from "../constants/MetaPaths";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
findReservedMetaEntry,
|
|
18
|
+
isReservedMetaPath,
|
|
19
|
+
isMetaInfPath,
|
|
20
|
+
asciiPathEqualsIgnoreCase,
|
|
21
|
+
} from "../constants/MetaPaths";
|
|
10
22
|
|
|
11
23
|
export const DATATYPE = {
|
|
12
24
|
STRING: "string",
|
|
@@ -80,12 +92,9 @@ export function getTypeOf(input: string|Buffer|Blob|Array<any>|ArrayBuffer|Uint8
|
|
|
80
92
|
};
|
|
81
93
|
|
|
82
94
|
/**
|
|
83
|
-
* Check if a filename is
|
|
84
|
-
*
|
|
85
|
-
* @returns True if the file is a metadata file (META-INF)
|
|
95
|
+
* Check if a filename is NeoZip metadata (any META-INF/** path).
|
|
96
|
+
* Case-insensitive on the META-INF/ prefix (NEOZIP_APPNOTE.md §2.3 / §6.2).
|
|
86
97
|
*/
|
|
87
98
|
export const isMetadataFile = (filename: string): boolean => {
|
|
88
|
-
return filename
|
|
89
|
-
filename === TIMESTAMP_METADATA ||
|
|
90
|
-
filename === TOKENIZED_METADATA;
|
|
99
|
+
return isMetaInfPath(filename);
|
|
91
100
|
};
|
|
@@ -11,9 +11,19 @@ export const ZIP64_CENTRAL_DIRECTORY_LOCATOR = 'PK\x06\x07';
|
|
|
11
11
|
export const ZIP64_CENTRAL_DIRECTORY_END = 'PK\x06\x06';
|
|
12
12
|
export const DATA_DESCRIPTOR = 'PK\x07\x08';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
|
|
14
|
+
// Reserved META-INF paths — see constants/MetaPaths.ts and NEOZIP_APPNOTE.md §2
|
|
15
|
+
export {
|
|
16
|
+
META_TOKEN_NZIP,
|
|
17
|
+
META_TOKEN_NZIP_LEGACY,
|
|
18
|
+
META_TS_SUBMIT_NZIP,
|
|
19
|
+
META_TIMESTAMP_NZIP,
|
|
20
|
+
META_TS_SUBMIT_OTS,
|
|
21
|
+
META_TIMESTAMP_OTS,
|
|
22
|
+
META_MANIFEST_JSON,
|
|
23
|
+
TIMESTAMP_SUBMITTED,
|
|
24
|
+
TIMESTAMP_METADATA,
|
|
25
|
+
TOKENIZED_METADATA,
|
|
26
|
+
} from './MetaPaths';
|
|
17
27
|
|
|
18
28
|
// Local file header
|
|
19
29
|
export const LOCAL_HDR = {
|
|
@@ -176,7 +186,7 @@ export const HDR_ID = {
|
|
|
176
186
|
x509PKCS7RL: 0x0019, // X.509 Certificate Revocation List (PKCS#7)
|
|
177
187
|
IBM1: 0x0065, // IBM S/390 (Z390) - Attribute
|
|
178
188
|
IBM2: 0x0066, // IBM S/390 (Z390) - Attribute
|
|
179
|
-
SHA256: 0x014E, //
|
|
189
|
+
SHA256: 0x014E, // NeoZip Extra Field: 32-byte SHA-256 of uncompressed payload (APPNOTE §5)
|
|
180
190
|
NEO_CRYPTO: 0x024E, // NeoEncrypt metadata (see docs/NEO_CRYPTO_FORMAT.md); LE on wire: 4E 02
|
|
181
191
|
POSZIP: 0x4690, // POSZIP 4690
|
|
182
192
|
UNV_TIME: 0x5455, // Universal Time (UT) (32-bit) 5 data bytes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeoZip reserved META-INF paths and ASCII case-insensitive discovery.
|
|
3
|
+
* Matches NEOZIP_APPNOTE.md §2.3: writers emit canonical spellings; readers fold case.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Canonical writers MUST emit these spellings. */
|
|
7
|
+
export const META_TOKEN_NZIP = 'META-INF/TOKEN.NZIP';
|
|
8
|
+
export const META_TOKEN_NZIP_LEGACY = 'META-INF/NZIP.TOKEN';
|
|
9
|
+
export const META_TS_SUBMIT_NZIP = 'META-INF/TS-SUBMIT.NZIP';
|
|
10
|
+
export const META_TIMESTAMP_NZIP = 'META-INF/TIMESTAMP.NZIP';
|
|
11
|
+
export const META_TS_SUBMIT_OTS = 'META-INF/TS-SUBMIT.OTS';
|
|
12
|
+
export const META_TIMESTAMP_OTS = 'META-INF/TIMESTAMP.OTS';
|
|
13
|
+
export const META_MANIFEST_JSON = 'META-INF/manifest.json';
|
|
14
|
+
|
|
15
|
+
/** @deprecated Prefer META_TS_SUBMIT_OTS — kept for existing imports. */
|
|
16
|
+
export const TIMESTAMP_SUBMITTED = META_TS_SUBMIT_OTS;
|
|
17
|
+
/** @deprecated Prefer META_TIMESTAMP_OTS — kept for existing imports. */
|
|
18
|
+
export const TIMESTAMP_METADATA = META_TIMESTAMP_OTS;
|
|
19
|
+
/** @deprecated Prefer META_TOKEN_NZIP — kept for existing imports (canonical TOKEN.NZIP). */
|
|
20
|
+
export const TOKENIZED_METADATA = META_TOKEN_NZIP;
|
|
21
|
+
|
|
22
|
+
const RESERVED_CANONICAL = [
|
|
23
|
+
META_TOKEN_NZIP,
|
|
24
|
+
META_TOKEN_NZIP_LEGACY,
|
|
25
|
+
META_TS_SUBMIT_NZIP,
|
|
26
|
+
META_TIMESTAMP_NZIP,
|
|
27
|
+
META_TS_SUBMIT_OTS,
|
|
28
|
+
META_TIMESTAMP_OTS,
|
|
29
|
+
META_MANIFEST_JSON,
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type ZipEntryLikeFilename = { filename?: string };
|
|
33
|
+
|
|
34
|
+
/** ASCII case-insensitive path compare (APPNOTE §2.3). */
|
|
35
|
+
export function asciiPathEqualsIgnoreCase(a: string, b: string): boolean {
|
|
36
|
+
if (a.length !== b.length) {
|
|
37
|
+
// Still allow length mismatch only via toLowerCase for UTF-8 safety on ASCII paths
|
|
38
|
+
}
|
|
39
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True when path is under META-INF/ (ASCII case-insensitive). */
|
|
43
|
+
export function isMetaInfPath(filename: string): boolean {
|
|
44
|
+
return filename.toLowerCase().startsWith('meta-inf/');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** True when filename is a reserved NeoZip meta path (any casing). */
|
|
48
|
+
export function isReservedMetaPath(filename: string): boolean {
|
|
49
|
+
const lower = filename.toLowerCase();
|
|
50
|
+
return RESERVED_CANONICAL.some((c) => c.toLowerCase() === lower);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* True for any META-INF/** entry (integrity merkle leaves exclude these per APPNOTE §6.2).
|
|
55
|
+
*/
|
|
56
|
+
export function isMetadataFile(filename: string): boolean {
|
|
57
|
+
return isMetaInfPath(filename);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find a reserved meta entry among ZIP directory entries.
|
|
62
|
+
* Prefers an exact canonical match, then ASCII case-insensitive match.
|
|
63
|
+
* When multiple preferred names are given, earlier names win.
|
|
64
|
+
*/
|
|
65
|
+
export function findReservedMetaEntry<T extends ZipEntryLikeFilename>(
|
|
66
|
+
entries: T[],
|
|
67
|
+
preferredCanonicalNames: string | readonly string[]
|
|
68
|
+
): T | null {
|
|
69
|
+
const preferred = Array.isArray(preferredCanonicalNames)
|
|
70
|
+
? preferredCanonicalNames
|
|
71
|
+
: [preferredCanonicalNames];
|
|
72
|
+
|
|
73
|
+
for (const name of preferred) {
|
|
74
|
+
const exact = entries.find((e) => e.filename === name);
|
|
75
|
+
if (exact) return exact;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const name of preferred) {
|
|
79
|
+
const folded = entries.find(
|
|
80
|
+
(e) => typeof e.filename === 'string' && asciiPathEqualsIgnoreCase(e.filename, name)
|
|
81
|
+
);
|
|
82
|
+
if (folded) return folded;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|