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
|
@@ -1,63 +1,143 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser
|
|
3
|
-
*
|
|
2
|
+
* Browser zstd codec for in-memory buffers.
|
|
3
|
+
* Uses CompressionStream / DecompressionStream ('zstd') so the old WASM codec stays out.
|
|
4
|
+
* Node builds use src/node/ZstdNode.ts (native zlib) instead of this file.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
'ZSTD requires Node.js with native zlib support (Node.js >= 22.15 or >= 23.8). ' +
|
|
8
|
-
'Browser builds do not include a zstd codec; use Deflate or process zstd archives in Node.';
|
|
7
|
+
import { Buffer } from 'buffer';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/** Bytes accepted by the buffer zstd codec. */
|
|
10
|
+
export type ZstdInput = Buffer | Uint8Array | ArrayBuffer;
|
|
11
|
+
|
|
12
|
+
const ZSTD_UNAVAILABLE =
|
|
13
|
+
'This browser does not expose CompressionStream zstd. ' +
|
|
14
|
+
'Use a current Chromium or Safari, or inflate the archive in Node.';
|
|
15
|
+
|
|
16
|
+
function asBytes(data: ZstdInput): Uint8Array {
|
|
17
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
18
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toBuffer(bytes: Uint8Array): Buffer {
|
|
22
|
+
return Buffer.from(bytes);
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
export function isNativeZstdAvailable(): boolean {
|
|
15
|
-
|
|
26
|
+
try {
|
|
27
|
+
if (typeof CompressionStream === 'undefined' || typeof DecompressionStream === 'undefined') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// 'zstd' is not in the older CompressionFormat union.
|
|
31
|
+
new CompressionStream('zstd' as CompressionFormat);
|
|
32
|
+
new DecompressionStream('zstd' as CompressionFormat);
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function mapCompressionLevel(level: number): number {
|
|
40
|
+
return Math.min(Math.max(1, Math.floor(level * 2.1)), 19);
|
|
16
41
|
}
|
|
17
42
|
|
|
18
|
-
|
|
19
|
-
|
|
43
|
+
function assertZstd(): void {
|
|
44
|
+
if (!isNativeZstdAvailable()) {
|
|
45
|
+
throw new Error(ZSTD_UNAVAILABLE);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function transformZstd(data: Uint8Array, mode: 'compress' | 'decompress'): Promise<Uint8Array> {
|
|
50
|
+
assertZstd();
|
|
51
|
+
const Stream = mode === 'compress' ? CompressionStream : DecompressionStream;
|
|
52
|
+
const stream = new Stream('zstd' as CompressionFormat);
|
|
53
|
+
const copy = new Uint8Array(data.byteLength);
|
|
54
|
+
copy.set(data);
|
|
55
|
+
const writer = stream.writable.getWriter();
|
|
56
|
+
await writer.write(copy);
|
|
57
|
+
await writer.close();
|
|
58
|
+
const reader = stream.readable.getReader();
|
|
59
|
+
const chunks: Uint8Array[] = [];
|
|
60
|
+
let total = 0;
|
|
61
|
+
for (;;) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done) break;
|
|
64
|
+
if (value) {
|
|
65
|
+
chunks.push(value);
|
|
66
|
+
total += value.byteLength;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const out = new Uint8Array(total);
|
|
70
|
+
let offset = 0;
|
|
71
|
+
for (const chunk of chunks) {
|
|
72
|
+
out.set(chunk, offset);
|
|
73
|
+
offset += chunk.byteLength;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
20
76
|
}
|
|
21
77
|
|
|
22
78
|
export function createCompressTransform(_level?: number): never {
|
|
23
|
-
|
|
79
|
+
throw new Error('Browser zstd uses buffer compress(); Node zlib transforms are not available.');
|
|
24
80
|
}
|
|
25
81
|
|
|
26
82
|
export function createDecompressTransform(): never {
|
|
27
|
-
|
|
83
|
+
throw new Error('Browser zstd uses buffer decompress(); Node zlib transforms are not available.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function compress(data: ZstdInput, _level?: number): Promise<Buffer> {
|
|
87
|
+
return toBuffer(await transformZstd(asBytes(data), 'compress'));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function decompress(data: ZstdInput): Promise<Buffer> {
|
|
91
|
+
return toBuffer(await transformZstd(asBytes(data), 'decompress'));
|
|
28
92
|
}
|
|
29
93
|
|
|
30
|
-
export
|
|
31
|
-
|
|
94
|
+
export function compressSync(_data: ZstdInput, _level?: number): Buffer {
|
|
95
|
+
throw new Error('Browser zstd compress is async. Use compress().');
|
|
32
96
|
}
|
|
33
97
|
|
|
34
|
-
export
|
|
35
|
-
return
|
|
98
|
+
export function compressSyncAtLevel(data: ZstdInput, _zstdLevel?: number): Buffer {
|
|
99
|
+
return compressSync(data);
|
|
36
100
|
}
|
|
37
101
|
|
|
38
|
-
export function
|
|
39
|
-
|
|
102
|
+
export function decompressSync(_data: ZstdInput): Buffer {
|
|
103
|
+
throw new Error('Browser zstd decompress is async. Use decompress().');
|
|
40
104
|
}
|
|
41
105
|
|
|
42
|
-
export function
|
|
43
|
-
return
|
|
106
|
+
export function asZstdBuffer(data: ZstdInput): Buffer {
|
|
107
|
+
return toBuffer(asBytes(data));
|
|
44
108
|
}
|
|
45
109
|
|
|
46
110
|
export async function compressChunks(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
readChunk: (position: number, size: number) => Buffer,
|
|
112
|
+
totalSize: number,
|
|
113
|
+
chunkSize: number,
|
|
114
|
+
level: number,
|
|
51
115
|
_onPlainChunk?: (chunk: Buffer) => void,
|
|
52
|
-
|
|
116
|
+
onCompressedChunk?: (chunk: Buffer) => void | Promise<void>
|
|
53
117
|
): Promise<number> {
|
|
54
|
-
|
|
118
|
+
const parts: Buffer[] = [];
|
|
119
|
+
let position = 0;
|
|
120
|
+
while (position < totalSize) {
|
|
121
|
+
const size = Math.min(chunkSize, totalSize - position);
|
|
122
|
+
const chunk = readChunk(position, size);
|
|
123
|
+
parts.push(Buffer.from(chunk));
|
|
124
|
+
position += chunk.length;
|
|
125
|
+
}
|
|
126
|
+
const compressed = await compress(Buffer.concat(parts), level);
|
|
127
|
+
if (onCompressedChunk) {
|
|
128
|
+
await onCompressedChunk(compressed);
|
|
129
|
+
}
|
|
130
|
+
return compressed.length;
|
|
55
131
|
}
|
|
56
132
|
|
|
57
133
|
export async function* decompressStream(
|
|
58
|
-
|
|
134
|
+
compressedStream: AsyncIterable<Buffer>
|
|
59
135
|
): AsyncGenerator<Buffer> {
|
|
60
|
-
|
|
136
|
+
const parts: Buffer[] = [];
|
|
137
|
+
for await (const chunk of compressedStream) {
|
|
138
|
+
parts.push(Buffer.from(chunk));
|
|
139
|
+
}
|
|
140
|
+
yield await decompress(Buffer.concat(parts));
|
|
61
141
|
}
|
|
62
142
|
|
|
63
143
|
export const ZstdNode = {
|
|
@@ -68,7 +148,9 @@ export const ZstdNode = {
|
|
|
68
148
|
compress,
|
|
69
149
|
decompress,
|
|
70
150
|
compressSync,
|
|
151
|
+
compressSyncAtLevel,
|
|
71
152
|
decompressSync,
|
|
153
|
+
asZstdBuffer,
|
|
72
154
|
compressChunks,
|
|
73
155
|
decompressStream,
|
|
74
156
|
};
|
package/src/core/ZipCompress.ts
CHANGED
|
@@ -31,10 +31,16 @@ import { NeoCrypto, NEO_CRYPTO_ALGORITHM_AES256_V1 } from './encryption/NeoCrypt
|
|
|
31
31
|
export interface CompressOptions {
|
|
32
32
|
level?: number; // Compression level (1-9, 0=store)
|
|
33
33
|
password?: string | null; // Password for encryption
|
|
34
|
-
encryptionMethod?: 'aes256' | 'zipcrypto' | 'neo-aes256'; // default
|
|
34
|
+
encryptionMethod?: 'aes256' | 'zipcrypto' | 'neo-aes256'; // default NeoEncrypt when password set; 'aes256' = WinZip AE (method 99); 'neo-aes256' = Extra Field 0x024E
|
|
35
35
|
useSHA256?: boolean; // Whether to calculate SHA256 hash default is false
|
|
36
|
-
useZstd?: boolean; //
|
|
36
|
+
useZstd?: boolean; // Use Zstandard (method 93); default false (Deflate). Node ≥ 22.15 required when true.
|
|
37
37
|
bufferSize?: number; // Override default buffer size
|
|
38
|
+
/**
|
|
39
|
+
* Force APPNOTE Zip64 Version 1 even when sizes/offsets/counts fit classic fields.
|
|
40
|
+
* Useful for testing Zip64 read/list/integrity without multi-GiB payloads.
|
|
41
|
+
* Node streaming writers emit Zip64 EOCD + locator and per-member Extra `0x0001`.
|
|
42
|
+
*/
|
|
43
|
+
forceZip64?: boolean;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -132,20 +138,19 @@ export class ZipCompress {
|
|
|
132
138
|
// Calculate hashes if needed
|
|
133
139
|
if (hashCalculator) {
|
|
134
140
|
hashCalculator.update(data);
|
|
135
|
-
|
|
136
|
-
entry.crc
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
entry.sha256 = hashCalculator.finalizeSHA256();
|
|
140
|
-
}
|
|
141
|
+
hashCalculator.applyToEntry(entry, {
|
|
142
|
+
setCrc: !entry.crc || entry.crc === 0,
|
|
143
|
+
setSha256: !!(options?.useSHA256 && !entry.sha256),
|
|
144
|
+
});
|
|
141
145
|
this.log(`Final hashes: CRC32=0x${entry.crc.toString(16).padStart(8, '0')}, SHA256=${entry.sha256 || 'N/A'}`);
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
// Encrypt if password provided
|
|
145
149
|
const enc = options?.encryptionMethod;
|
|
146
150
|
const useZipCrypto = enc === 'zipcrypto';
|
|
147
|
-
|
|
148
|
-
const useAes =
|
|
151
|
+
// Explicit WinZip AE only when encryptionMethod is 'aes256'. Otherwise NeoEncrypt (default).
|
|
152
|
+
const useAes = enc === 'aes256';
|
|
153
|
+
const useNeo = !!options?.password && !useZipCrypto && !useAes;
|
|
149
154
|
if (options?.password) {
|
|
150
155
|
const label = useZipCrypto ? 'ZipCrypto' : useNeo ? 'NeoEncrypt (AES-256)' : 'WinZip AES-256';
|
|
151
156
|
this.log(`Encrypting compressed data for entry: ${entry.filename} (method: ${label})`);
|
|
@@ -425,9 +430,11 @@ export class ZipCompress {
|
|
|
425
430
|
hashCalculator.update(fileData);
|
|
426
431
|
|
|
427
432
|
// Set hashes
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
hashCalculator.applyToEntry(entry, {
|
|
434
|
+
setCrc: true,
|
|
435
|
+
setSha256: !!cmpOptions?.useSHA256,
|
|
436
|
+
});
|
|
437
|
+
if (entry.sha256) {
|
|
431
438
|
this.log(`SHA-256 calculated: ${entry.sha256}`);
|
|
432
439
|
}
|
|
433
440
|
|
package/src/core/ZipCopy.ts
CHANGED
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
import ZipEntry from './ZipEntry';
|
|
12
12
|
import Zipkit from './Zipkit';
|
|
13
|
-
import { LOCAL_HDR,
|
|
13
|
+
import { LOCAL_HDR, GP_FLAG } from './constants/Headers';
|
|
14
|
+
import {
|
|
15
|
+
assertBufferAllowsEntryZip64,
|
|
16
|
+
buildEndRecords,
|
|
17
|
+
dataDescriptorByteLength,
|
|
18
|
+
needsZip64SizeOrOffset,
|
|
19
|
+
ZIP64_BUFFER_SIZE_OFFSET_ERROR,
|
|
20
|
+
} from './zip64/Zip64';
|
|
14
21
|
|
|
15
22
|
/**
|
|
16
23
|
* Options for copying ZIP files
|
|
@@ -174,9 +181,19 @@ export class ZipCopy {
|
|
|
174
181
|
const localHeaderSize = this.calculateLocalHeaderSize(sourceBuffer, entry);
|
|
175
182
|
|
|
176
183
|
// Determine total entry size
|
|
177
|
-
// For data descriptor entries, add
|
|
184
|
+
// For data descriptor entries, add Zip64 (24) or classic (16) descriptor bytes
|
|
178
185
|
const hasDataDescriptor = (entry.bitFlags & GP_FLAG.DATA_DESC) !== 0;
|
|
179
|
-
const
|
|
186
|
+
const descLen = hasDataDescriptor
|
|
187
|
+
? dataDescriptorByteLength(entry.usesZip64Extra)
|
|
188
|
+
: 0;
|
|
189
|
+
const totalEntrySize = localHeaderSize + entry.compressedSize + descLen;
|
|
190
|
+
|
|
191
|
+
// Buffer copy must remain memory-feasible
|
|
192
|
+
assertBufferAllowsEntryZip64(
|
|
193
|
+
entry.uncompressedSize,
|
|
194
|
+
entry.compressedSize,
|
|
195
|
+
entry.localHdrOffset
|
|
196
|
+
);
|
|
180
197
|
|
|
181
198
|
// Verify we have enough data in the buffer
|
|
182
199
|
if (entry.localHdrOffset + totalEntrySize > sourceBuffer.length) {
|
|
@@ -264,6 +281,17 @@ export class ZipCopy {
|
|
|
264
281
|
cloned.originalEntry = entry.originalEntry;
|
|
265
282
|
cloned.inode = entry.inode;
|
|
266
283
|
|
|
284
|
+
cloned.usesZip64Extra = entry.usesZip64Extra;
|
|
285
|
+
if (entry.additionalExtra) {
|
|
286
|
+
cloned.additionalExtra = Buffer.from(entry.additionalExtra);
|
|
287
|
+
}
|
|
288
|
+
cloned.aesVersion = entry.aesVersion;
|
|
289
|
+
cloned.aesStrength = entry.aesStrength;
|
|
290
|
+
cloned.realCmpMethod = entry.realCmpMethod;
|
|
291
|
+
cloned.neoCryptoPayloadVersion = entry.neoCryptoPayloadVersion;
|
|
292
|
+
cloned.neoCryptoAlgorithm = entry.neoCryptoAlgorithm;
|
|
293
|
+
cloned.neoCryptoFlags = entry.neoCryptoFlags;
|
|
294
|
+
|
|
267
295
|
return cloned;
|
|
268
296
|
}
|
|
269
297
|
|
|
@@ -276,6 +304,19 @@ export class ZipCopy {
|
|
|
276
304
|
zipComment: string,
|
|
277
305
|
centralDirOffset: number
|
|
278
306
|
): Buffer {
|
|
307
|
+
if (
|
|
308
|
+
needsZip64SizeOrOffset(0, 0, 0, 0, centralDirOffset) ||
|
|
309
|
+
entries.some((e) =>
|
|
310
|
+
needsZip64SizeOrOffset(
|
|
311
|
+
e.uncompressedSize,
|
|
312
|
+
e.compressedSize,
|
|
313
|
+
e.localHdrOffset
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
throw new Error(ZIP64_BUFFER_SIZE_OFFSET_ERROR);
|
|
318
|
+
}
|
|
319
|
+
|
|
279
320
|
const centralDirChunks: Buffer[] = [];
|
|
280
321
|
for (const entry of entries) {
|
|
281
322
|
centralDirChunks.push(entry.centralDirEntry());
|
|
@@ -283,32 +324,20 @@ export class ZipCopy {
|
|
|
283
324
|
const centralDirBuffer = Buffer.concat(centralDirChunks);
|
|
284
325
|
const centralDirSize = centralDirBuffer.length;
|
|
285
326
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const eocdBuffer = Buffer.alloc(22 + commentLength);
|
|
290
|
-
let pos = 0;
|
|
291
|
-
eocdBuffer.writeUInt32LE(CENTRAL_END.SIGNATURE, pos);
|
|
292
|
-
pos += 4;
|
|
293
|
-
eocdBuffer.writeUInt16LE(0, pos);
|
|
294
|
-
pos += 2;
|
|
295
|
-
eocdBuffer.writeUInt16LE(0, pos);
|
|
296
|
-
pos += 2;
|
|
297
|
-
eocdBuffer.writeUInt16LE(entries.length, pos);
|
|
298
|
-
pos += 2;
|
|
299
|
-
eocdBuffer.writeUInt16LE(entries.length, pos);
|
|
300
|
-
pos += 2;
|
|
301
|
-
eocdBuffer.writeUInt32LE(centralDirSize, pos);
|
|
302
|
-
pos += 4;
|
|
303
|
-
eocdBuffer.writeUInt32LE(centralDirOffset, pos);
|
|
304
|
-
pos += 4;
|
|
305
|
-
eocdBuffer.writeUInt16LE(commentLength, pos);
|
|
306
|
-
pos += 2;
|
|
307
|
-
if (commentLength > 0) {
|
|
308
|
-
commentBytes.copy(eocdBuffer, pos, 0, commentLength);
|
|
327
|
+
if (needsZip64SizeOrOffset(0, 0, 0, centralDirSize, centralDirOffset)) {
|
|
328
|
+
throw new Error(ZIP64_BUFFER_SIZE_OFFSET_ERROR);
|
|
309
329
|
}
|
|
310
330
|
|
|
311
|
-
|
|
331
|
+
const endRecords = buildEndRecords({
|
|
332
|
+
totalEntries: entries.length,
|
|
333
|
+
centralDirSize,
|
|
334
|
+
centralDirOffset,
|
|
335
|
+
zip64EocdOffset: centralDirOffset + centralDirSize,
|
|
336
|
+
archiveComment: zipComment,
|
|
337
|
+
allowSizeOffsetZip64: false,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return Buffer.concat([centralDirBuffer, endRecords]);
|
|
312
341
|
}
|
|
313
342
|
|
|
314
343
|
/**
|
|
@@ -23,6 +23,7 @@ import Errors from './constants/Errors';
|
|
|
23
23
|
import { CMP_METHOD } from './constants/Headers';
|
|
24
24
|
import { ZipCrypto } from './encryption/ZipCrypto';
|
|
25
25
|
import { AesCrypto } from './encryption/AesCrypto';
|
|
26
|
+
import { HashCalculator } from './components/HashCalculator';
|
|
26
27
|
|
|
27
28
|
export interface DecompressionResult {
|
|
28
29
|
success: boolean;
|
|
@@ -189,18 +190,23 @@ class ZipDecompress {
|
|
|
189
190
|
throw new Error(`Unsupported compression method: ${method}`);
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
//
|
|
193
|
+
// One pass: CRC + bare SHA-256 (0x014E) + v1 Merkle leaf — no second payload read
|
|
194
|
+
const hashCalc = new HashCalculator({ useSHA256: true });
|
|
195
|
+
hashCalc.update(outBuf);
|
|
196
|
+
const calculatedCRC = hashCalc.finalizeCRC32();
|
|
197
|
+
const calculatedHash = hashCalc.finalizeSHA256();
|
|
198
|
+
const leaf = hashCalc.finalizeMerkleLeafV1();
|
|
199
|
+
if (leaf) {
|
|
200
|
+
entry.merkleLeafV1 = leaf;
|
|
201
|
+
}
|
|
202
|
+
|
|
193
203
|
if (!skipHashCheck) {
|
|
194
204
|
if (entry.sha256) {
|
|
195
|
-
|
|
196
|
-
if (!isValid) {
|
|
205
|
+
if (calculatedHash !== entry.sha256) {
|
|
197
206
|
throw new Error(Errors.INVALID_SHA256);
|
|
198
207
|
}
|
|
199
|
-
} else {
|
|
200
|
-
|
|
201
|
-
if (!isValid) {
|
|
202
|
-
throw new Error(Errors.INVALID_CRC);
|
|
203
|
-
}
|
|
208
|
+
} else if (calculatedCRC !== entry.crc) {
|
|
209
|
+
throw new Error(Errors.INVALID_CRC);
|
|
204
210
|
}
|
|
205
211
|
}
|
|
206
212
|
|