@neoware_inc/neozipkit 0.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/README.md +134 -0
- package/dist/browser/ZipkitBrowser.d.ts +27 -0
- package/dist/browser/ZipkitBrowser.d.ts.map +1 -0
- package/dist/browser/ZipkitBrowser.js +303 -0
- package/dist/browser/ZipkitBrowser.js.map +1 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.esm.d.ts +12 -0
- package/dist/browser/index.esm.d.ts.map +1 -0
- package/dist/browser/index.esm.js +46 -0
- package/dist/browser/index.esm.js.map +1 -0
- package/dist/browser/index.js +38 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser-esm/index.d.ts +9 -0
- package/dist/browser-esm/index.js +50211 -0
- package/dist/browser-esm/index.js.map +7 -0
- package/dist/browser-umd/index.d.ts +9 -0
- package/dist/browser-umd/index.js +50221 -0
- package/dist/browser-umd/index.js.map +7 -0
- package/dist/browser-umd/index.min.js +39 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.js +38 -0
- package/dist/core/ZipCompress.d.ts +99 -0
- package/dist/core/ZipCompress.d.ts.map +1 -0
- package/dist/core/ZipCompress.js +287 -0
- package/dist/core/ZipCompress.js.map +1 -0
- package/dist/core/ZipCopy.d.ts +175 -0
- package/dist/core/ZipCopy.d.ts.map +1 -0
- package/dist/core/ZipCopy.js +310 -0
- package/dist/core/ZipCopy.js.map +1 -0
- package/dist/core/ZipDecompress.d.ts +57 -0
- package/dist/core/ZipDecompress.d.ts.map +1 -0
- package/dist/core/ZipDecompress.js +155 -0
- package/dist/core/ZipDecompress.js.map +1 -0
- package/dist/core/ZipEntry.d.ts +138 -0
- package/dist/core/ZipEntry.d.ts.map +1 -0
- package/dist/core/ZipEntry.js +829 -0
- package/dist/core/ZipEntry.js.map +1 -0
- package/dist/core/Zipkit.d.ts +315 -0
- package/dist/core/Zipkit.d.ts.map +1 -0
- package/dist/core/Zipkit.js +647 -0
- package/dist/core/Zipkit.js.map +1 -0
- package/dist/core/ZstdManager.d.ts +56 -0
- package/dist/core/ZstdManager.d.ts.map +1 -0
- package/dist/core/ZstdManager.js +144 -0
- package/dist/core/ZstdManager.js.map +1 -0
- package/dist/core/components/HashCalculator.d.ts +138 -0
- package/dist/core/components/HashCalculator.d.ts.map +1 -0
- package/dist/core/components/HashCalculator.js +360 -0
- package/dist/core/components/HashCalculator.js.map +1 -0
- package/dist/core/components/Logger.d.ts +73 -0
- package/dist/core/components/Logger.d.ts.map +1 -0
- package/dist/core/components/Logger.js +156 -0
- package/dist/core/components/Logger.js.map +1 -0
- package/dist/core/components/ProgressTracker.d.ts +43 -0
- package/dist/core/components/ProgressTracker.d.ts.map +1 -0
- package/dist/core/components/ProgressTracker.js +112 -0
- package/dist/core/components/ProgressTracker.js.map +1 -0
- package/dist/core/components/Support.d.ts +64 -0
- package/dist/core/components/Support.d.ts.map +1 -0
- package/dist/core/components/Support.js +71 -0
- package/dist/core/components/Support.js.map +1 -0
- package/dist/core/components/Util.d.ts +26 -0
- package/dist/core/components/Util.d.ts.map +1 -0
- package/dist/core/components/Util.js +95 -0
- package/dist/core/components/Util.js.map +1 -0
- package/dist/core/constants/Errors.d.ts +52 -0
- package/dist/core/constants/Errors.d.ts.map +1 -0
- package/dist/core/constants/Errors.js +67 -0
- package/dist/core/constants/Errors.js.map +1 -0
- package/dist/core/constants/Headers.d.ts +170 -0
- package/dist/core/constants/Headers.d.ts.map +1 -0
- package/dist/core/constants/Headers.js +194 -0
- package/dist/core/constants/Headers.js.map +1 -0
- package/dist/core/encryption/Manager.d.ts +58 -0
- package/dist/core/encryption/Manager.d.ts.map +1 -0
- package/dist/core/encryption/Manager.js +121 -0
- package/dist/core/encryption/Manager.js.map +1 -0
- package/dist/core/encryption/ZipCrypto.d.ts +172 -0
- package/dist/core/encryption/ZipCrypto.d.ts.map +1 -0
- package/dist/core/encryption/ZipCrypto.js +554 -0
- package/dist/core/encryption/ZipCrypto.js.map +1 -0
- package/dist/core/encryption/index.d.ts +9 -0
- package/dist/core/encryption/index.d.ts.map +1 -0
- package/dist/core/encryption/index.js +17 -0
- package/dist/core/encryption/index.js.map +1 -0
- package/dist/core/encryption/types.d.ts +29 -0
- package/dist/core/encryption/types.d.ts.map +1 -0
- package/dist/core/encryption/types.js +12 -0
- package/dist/core/encryption/types.js.map +1 -0
- package/dist/core/index.d.ts +27 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +59 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/version.d.ts +5 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +31 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/node/ZipCompressNode.d.ts +123 -0
- package/dist/node/ZipCompressNode.d.ts.map +1 -0
- package/dist/node/ZipCompressNode.js +565 -0
- package/dist/node/ZipCompressNode.js.map +1 -0
- package/dist/node/ZipCopyNode.d.ts +165 -0
- package/dist/node/ZipCopyNode.d.ts.map +1 -0
- package/dist/node/ZipCopyNode.js +347 -0
- package/dist/node/ZipCopyNode.js.map +1 -0
- package/dist/node/ZipDecompressNode.d.ts +197 -0
- package/dist/node/ZipDecompressNode.d.ts.map +1 -0
- package/dist/node/ZipDecompressNode.js +678 -0
- package/dist/node/ZipDecompressNode.js.map +1 -0
- package/dist/node/ZipkitNode.d.ts +466 -0
- package/dist/node/ZipkitNode.d.ts.map +1 -0
- package/dist/node/ZipkitNode.js +1426 -0
- package/dist/node/ZipkitNode.js.map +1 -0
- package/dist/node/index.d.ts +25 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +54 -0
- package/dist/node/index.js.map +1 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/examples/README.md +261 -0
- package/examples/append-data.json +44 -0
- package/examples/copy-zip-append.ts +139 -0
- package/examples/copy-zip.ts +152 -0
- package/examples/create-zip.ts +172 -0
- package/examples/extract-zip.ts +118 -0
- package/examples/list-zip.ts +161 -0
- package/examples/test-files/data.json +116 -0
- package/examples/test-files/document.md +80 -0
- package/examples/test-files/document.txt +6 -0
- package/examples/test-files/file1.txt +48 -0
- package/examples/test-files/file2.txt +80 -0
- package/examples/tsconfig.json +44 -0
- package/package.json +167 -0
- package/src/browser/ZipkitBrowser.ts +305 -0
- package/src/browser/index.esm.ts +32 -0
- package/src/browser/index.ts +19 -0
- package/src/core/ZipCompress.ts +370 -0
- package/src/core/ZipCopy.ts +434 -0
- package/src/core/ZipDecompress.ts +191 -0
- package/src/core/ZipEntry.ts +917 -0
- package/src/core/Zipkit.ts +794 -0
- package/src/core/ZstdManager.ts +165 -0
- package/src/core/components/HashCalculator.ts +384 -0
- package/src/core/components/Logger.ts +180 -0
- package/src/core/components/ProgressTracker.ts +134 -0
- package/src/core/components/Support.ts +77 -0
- package/src/core/components/Util.ts +91 -0
- package/src/core/constants/Errors.ts +78 -0
- package/src/core/constants/Headers.ts +205 -0
- package/src/core/encryption/Manager.ts +137 -0
- package/src/core/encryption/ZipCrypto.ts +650 -0
- package/src/core/encryption/index.ts +15 -0
- package/src/core/encryption/types.ts +33 -0
- package/src/core/index.ts +42 -0
- package/src/core/version.ts +33 -0
- package/src/index.ts +19 -0
- package/src/node/ZipCompressNode.ts +618 -0
- package/src/node/ZipCopyNode.ts +437 -0
- package/src/node/ZipDecompressNode.ts +793 -0
- package/src/node/ZipkitNode.ts +1706 -0
- package/src/node/index.ts +40 -0
- package/src/types/index.ts +68 -0
- package/src/types/modules.d.ts +22 -0
- package/src/types/opentimestamps.d.ts +1 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
// ======================================
|
|
2
|
+
// Zipkit.ts - Enhanced Zipkit with Dual Mode Support
|
|
3
|
+
// Copyright (c) 2025 NeoWare, Inc. All rights reserved.
|
|
4
|
+
// ======================================
|
|
5
|
+
|
|
6
|
+
import ZipEntry from './ZipEntry';
|
|
7
|
+
import Errors from './constants/Errors';
|
|
8
|
+
import { Logger } from './components/Logger';
|
|
9
|
+
import { NEOZIPKIT_INFO } from '../types';
|
|
10
|
+
import { ZipCompress, CompressOptions } from './ZipCompress';
|
|
11
|
+
import ZipDecompress from './ZipDecompress';
|
|
12
|
+
import { sha256, crc32 } from './encryption/ZipCrypto';
|
|
13
|
+
import HashCalculator from './components/HashCalculator';
|
|
14
|
+
import {
|
|
15
|
+
LOCAL_HDR,
|
|
16
|
+
ENCRYPT_HDR_SIZE,
|
|
17
|
+
CMP_METHOD,
|
|
18
|
+
CENTRAL_END,
|
|
19
|
+
CENTRAL_DIR,
|
|
20
|
+
ZIP64_CENTRAL_END,
|
|
21
|
+
ZIP64_CENTRAL_DIR ,
|
|
22
|
+
GP_FLAG,
|
|
23
|
+
TIMESTAMP_SUBMITTED,
|
|
24
|
+
TIMESTAMP_METADATA,
|
|
25
|
+
TOKENIZED_METADATA,
|
|
26
|
+
LOCAL_FILE_HEADER,
|
|
27
|
+
CENTRAL_FILE_HEADER,
|
|
28
|
+
CENTRAL_DIRECTORY_END,
|
|
29
|
+
HDR_ID
|
|
30
|
+
} from './constants/Headers';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Streaming file handle interface
|
|
34
|
+
* Used for file-based ZIP operations in server environments
|
|
35
|
+
*/
|
|
36
|
+
export interface StreamingFileHandle {
|
|
37
|
+
read(buffer: Buffer, offset: number, length: number, position: number): Promise<number>;
|
|
38
|
+
stat(): Promise<{ size: number }>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Re-export interfaces and constants (keep exports grouped at top)
|
|
43
|
+
export {
|
|
44
|
+
ZipEntry,
|
|
45
|
+
Errors,
|
|
46
|
+
CMP_METHOD,
|
|
47
|
+
TIMESTAMP_METADATA,
|
|
48
|
+
TIMESTAMP_SUBMITTED,
|
|
49
|
+
TOKENIZED_METADATA,
|
|
50
|
+
LOCAL_HDR,
|
|
51
|
+
ENCRYPT_HDR_SIZE,
|
|
52
|
+
CENTRAL_END,
|
|
53
|
+
CENTRAL_DIR,
|
|
54
|
+
ZIP64_CENTRAL_END,
|
|
55
|
+
ZIP64_CENTRAL_DIR,
|
|
56
|
+
GP_FLAG,
|
|
57
|
+
LOCAL_FILE_HEADER,
|
|
58
|
+
CENTRAL_FILE_HEADER,
|
|
59
|
+
CENTRAL_DIRECTORY_END,
|
|
60
|
+
HDR_ID
|
|
61
|
+
};
|
|
62
|
+
export type { CompressOptions, CreateZipOptions } from './ZipCompress';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configuration options for Zipkit instances
|
|
66
|
+
*
|
|
67
|
+
* Controls various aspects of ZIP file processing including memory usage,
|
|
68
|
+
* chunk sizes for streaming operations, and debug logging.
|
|
69
|
+
*
|
|
70
|
+
* @interface ZipkitConfig
|
|
71
|
+
* @property {number} [bufferSize] - Buffer size in bytes for streaming operations.
|
|
72
|
+
* Files larger than this size will be processed
|
|
73
|
+
* in chunks for memory efficiency. Default: 512KB (524288 bytes).
|
|
74
|
+
* @property {boolean} [debug] - Enable debug logging for detailed diagnostic information.
|
|
75
|
+
* When enabled, sets Logger level to 'debug'. Default: false.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const zip = new Zipkit({
|
|
79
|
+
* bufferSize: 512 * 1024, // 512KB buffer
|
|
80
|
+
* debug: true
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
export interface ZipkitConfig {
|
|
84
|
+
bufferSize?: number; // For streaming mode (default: 512KB)
|
|
85
|
+
debug?: boolean; // Enable debug logging
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Zipkit - Enhanced ZIP File Processing Library with Dual Mode Support
|
|
90
|
+
*
|
|
91
|
+
* Core ZIP file processing library supporting both buffer-based (in-memory) and
|
|
92
|
+
* file-based (streaming) modes. See README.md for detailed documentation.
|
|
93
|
+
*
|
|
94
|
+
* @class Zipkit
|
|
95
|
+
* @static {string} version - Library version string
|
|
96
|
+
* @static {string} releaseDate - Library release date
|
|
97
|
+
*/
|
|
98
|
+
export default class Zipkit {
|
|
99
|
+
static readonly version = NEOZIPKIT_INFO.version;
|
|
100
|
+
static readonly releaseDate = NEOZIPKIT_INFO.releaseDate;
|
|
101
|
+
|
|
102
|
+
// Configuration
|
|
103
|
+
private config: ZipkitConfig;
|
|
104
|
+
|
|
105
|
+
// In-memory mode data
|
|
106
|
+
private inBuffer: Buffer | null = null;
|
|
107
|
+
protected zipEntries: ZipEntry[] = []; // Protected: single source of truth for ZIP entry order (accessible by subclasses)
|
|
108
|
+
private centralDirSize: number = 0;
|
|
109
|
+
private centralDirOffset: number = 0;
|
|
110
|
+
|
|
111
|
+
// Common data
|
|
112
|
+
private zipComment: string | null = null;
|
|
113
|
+
|
|
114
|
+
// Private components (lazy-loaded)
|
|
115
|
+
private _zipkitCmp: ZipCompress | null = null;
|
|
116
|
+
private _zipkitDeCmp: ZipDecompress | null = null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Creates a new Zipkit instance with optional configuration
|
|
120
|
+
*
|
|
121
|
+
* @param config - Optional configuration object (ZipkitConfig)
|
|
122
|
+
* - bufferSize: Buffer size for streaming operations (default: 512KB)
|
|
123
|
+
* - debug: Enable debug logging (default: false)
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const zip = new Zipkit({ bufferSize: 512 * 1024, debug: true });
|
|
127
|
+
*/
|
|
128
|
+
constructor(config: ZipkitConfig = {}) {
|
|
129
|
+
this.config = {
|
|
130
|
+
bufferSize: config.bufferSize || 512 * 1024, // 512KB default (optimal for modern systems)
|
|
131
|
+
debug: config.debug || false // Default false
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Configure Logger based on debug setting
|
|
135
|
+
if (this.config.debug) {
|
|
136
|
+
Logger.setLevel('debug');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Note: ZipCompress and ZipDecompress are lazy-loaded when first accessed
|
|
140
|
+
// ZSTD codec is also lazily initialized on first use (module-level singleton)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Lazy-load ZipCompress instance
|
|
145
|
+
* @returns ZipCompress instance (created on first access)
|
|
146
|
+
*/
|
|
147
|
+
private getZipCompress(): ZipCompress {
|
|
148
|
+
if (!this._zipkitCmp) {
|
|
149
|
+
this._zipkitCmp = new ZipCompress(this);
|
|
150
|
+
}
|
|
151
|
+
return this._zipkitCmp;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Lazy-load ZipDecompress instance
|
|
156
|
+
* @returns ZipDecompress instance (created on first access)
|
|
157
|
+
*/
|
|
158
|
+
private getZipDecompress(): ZipDecompress {
|
|
159
|
+
if (!this._zipkitDeCmp) {
|
|
160
|
+
this._zipkitDeCmp = new ZipDecompress(this);
|
|
161
|
+
}
|
|
162
|
+
return this._zipkitDeCmp;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the configured buffer size for streaming operations
|
|
167
|
+
* @returns Buffer size in bytes (default: 512KB)
|
|
168
|
+
*/
|
|
169
|
+
getBufferSize(): number {
|
|
170
|
+
return this.config.bufferSize || 512 * 1024; // 512KB default (optimal for modern systems)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if ZIP is loaded in buffer mode
|
|
175
|
+
* @returns true if buffer-based ZIP is loaded
|
|
176
|
+
*/
|
|
177
|
+
hasInBuffer(): boolean {
|
|
178
|
+
return this.inBuffer !== null && this.inBuffer.length > 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// ZipCompress Wrapper Methods
|
|
184
|
+
// ============================================================================
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Compress data for a ZIP entry (Buffer-based only)
|
|
188
|
+
* Wrapper for ZipCompress.compressData()
|
|
189
|
+
*
|
|
190
|
+
* @param entry - ZIP entry to compress
|
|
191
|
+
* @param data - Buffer containing data to compress
|
|
192
|
+
* @param options - Compression options
|
|
193
|
+
* @param onOutputBuffer - Optional callback for streaming output
|
|
194
|
+
* @returns Promise resolving to Buffer containing compressed data
|
|
195
|
+
*/
|
|
196
|
+
async compressData(
|
|
197
|
+
entry: ZipEntry,
|
|
198
|
+
data: Buffer,
|
|
199
|
+
options?: CompressOptions,
|
|
200
|
+
onOutputBuffer?: (data: Buffer) => Promise<void>
|
|
201
|
+
): Promise<Buffer> {
|
|
202
|
+
return this.getZipCompress().compressData(entry, data, options, onOutputBuffer);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Compress data using deflate algorithm with chunked processing
|
|
207
|
+
* Wrapper for ZipCompress.deflateCompress()
|
|
208
|
+
*
|
|
209
|
+
* @param data - Data to compress (Buffer or chunked reader)
|
|
210
|
+
* @param options - Compression options
|
|
211
|
+
* @param bufferSize - Size of buffer to read (default: 512KB)
|
|
212
|
+
* @param entry - Optional ZIP entry for hash calculation
|
|
213
|
+
* @param onOutputBuffer - Optional callback for streaming output
|
|
214
|
+
* @returns Promise resolving to Buffer containing compressed data
|
|
215
|
+
*/
|
|
216
|
+
async deflateCompress(
|
|
217
|
+
data: Buffer | { totalSize: number, onReadChunk: (position: number, size: number) => Buffer, onOutChunk: (chunk: Buffer) => void },
|
|
218
|
+
options?: CompressOptions,
|
|
219
|
+
bufferSize?: number,
|
|
220
|
+
entry?: ZipEntry,
|
|
221
|
+
onOutputBuffer?: (data: Buffer) => Promise<void>
|
|
222
|
+
): Promise<Buffer> {
|
|
223
|
+
return this.getZipCompress().deflateCompress(data, options, bufferSize, entry, onOutputBuffer);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Compress data using deflate algorithm (synchronous, small buffers only)
|
|
228
|
+
* Wrapper for ZipCompress.deflate()
|
|
229
|
+
*
|
|
230
|
+
* @param inbuf - Buffer containing data to compress
|
|
231
|
+
* @param options - Compression options
|
|
232
|
+
* @returns Buffer containing compressed data
|
|
233
|
+
*/
|
|
234
|
+
deflate(inbuf: Buffer, options?: CompressOptions): Buffer {
|
|
235
|
+
return this.getZipCompress().deflate(inbuf, options);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Compress data using Zstandard (zstd) algorithm
|
|
240
|
+
* Wrapper for ZipCompress.zstdCompress()
|
|
241
|
+
*
|
|
242
|
+
* Note: ZSTD codec is lazily initialized on first use (module-level singleton).
|
|
243
|
+
* Initialization happens automatically when needed.
|
|
244
|
+
*
|
|
245
|
+
* @param input - Buffer to compress OR chunked reader object
|
|
246
|
+
* @param options - Compression options
|
|
247
|
+
* @param bufferSize - Size of buffer to read if using chunked reader (default: 512KB)
|
|
248
|
+
* @param entry - Optional ZIP entry for hash calculation
|
|
249
|
+
* @param onOutputBuffer - Optional callback for streaming output
|
|
250
|
+
* @returns Promise resolving to Buffer containing compressed data
|
|
251
|
+
*/
|
|
252
|
+
async zstdCompress(
|
|
253
|
+
input: Buffer | { totalSize: number, readChunk: (position: number, size: number) => Buffer },
|
|
254
|
+
options?: CompressOptions,
|
|
255
|
+
bufferSize?: number,
|
|
256
|
+
entry?: ZipEntry,
|
|
257
|
+
onOutputBuffer?: (data: Buffer) => Promise<void>
|
|
258
|
+
): Promise<Buffer> {
|
|
259
|
+
return this.getZipCompress().zstdCompress(input, options, bufferSize, entry, onOutputBuffer);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Compress file data in memory and return ZIP entry information as Buffer
|
|
264
|
+
* Wrapper for ZipCompress.compressFileBuffer()
|
|
265
|
+
*
|
|
266
|
+
* @param entry - ZIP entry to compress
|
|
267
|
+
* @param fileData - File data buffer to compress
|
|
268
|
+
* @param cmpOptions - Compression options
|
|
269
|
+
* @returns Promise resolving to Buffer containing local header + compressed data
|
|
270
|
+
*/
|
|
271
|
+
async compressFileBuffer(
|
|
272
|
+
entry: ZipEntry,
|
|
273
|
+
fileData: Buffer,
|
|
274
|
+
cmpOptions?: CompressOptions
|
|
275
|
+
): Promise<Buffer> {
|
|
276
|
+
return this.getZipCompress().compressFileBuffer(entry, fileData, cmpOptions);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// ZipDecompress Wrapper Methods
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Extract file data (Buffer-based ZIP only)
|
|
285
|
+
* Wrapper for ZipDecompress.extract()
|
|
286
|
+
*
|
|
287
|
+
* @param entry - ZIP entry to extract
|
|
288
|
+
* @param skipHashCheck - Skip hash verification (CRC-32 or SHA-256)
|
|
289
|
+
* @returns Promise resolving to Buffer containing extracted data, or null if failed
|
|
290
|
+
* @throws Error if not a Buffer-based ZIP
|
|
291
|
+
*/
|
|
292
|
+
async extract(entry: ZipEntry, skipHashCheck?: boolean): Promise<Buffer | null> {
|
|
293
|
+
return this.getZipDecompress().extract(entry, skipHashCheck);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Static Logging Wrapper Methods
|
|
299
|
+
// ============================================================================
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Note: Logging for ZipCompress and ZipDecompress is controlled by their
|
|
303
|
+
* static loggingEnabled property. Set it directly:
|
|
304
|
+
* ZipCompress.loggingEnabled = true/false;
|
|
305
|
+
* ZipDecompress.loggingEnabled = true/false;
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Load ZIP file from buffer (in-memory mode)
|
|
310
|
+
*
|
|
311
|
+
* **Required**: You must call this method (or the appropriate load method for your platform)
|
|
312
|
+
* before calling `getDirectory()` or any other ZIP operations. This method:
|
|
313
|
+
* 1. Resets all ZIP data
|
|
314
|
+
* 2. Stores the buffer in this.inBuffer
|
|
315
|
+
* 3. Loads EOCD and parses central directory
|
|
316
|
+
* 4. Populates this.zipEntries[] array
|
|
317
|
+
*
|
|
318
|
+
* For browser applications, use `ZipkitBrowser.loadZipBlob()` instead.
|
|
319
|
+
* For server applications with file paths, use `ZipkitServer.loadZipFile()` instead.
|
|
320
|
+
*
|
|
321
|
+
* @param inBuf - Buffer containing the complete ZIP file data
|
|
322
|
+
* @returns ZipEntry[] Array of all entries in the ZIP file
|
|
323
|
+
*/
|
|
324
|
+
loadZip(inBuf: Buffer): ZipEntry[] {
|
|
325
|
+
this.resetZipData();
|
|
326
|
+
// Store buffer for backward compatibility with detection logic
|
|
327
|
+
this.inBuffer = inBuf;
|
|
328
|
+
// Load EOCD and parse central directory
|
|
329
|
+
this.loadEOCDFromBuffer(inBuf);
|
|
330
|
+
const entries = this.getDirectoryFromBuffer();
|
|
331
|
+
this.zipEntries = entries;
|
|
332
|
+
return entries;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Reset all ZIP-related data to initial state
|
|
338
|
+
*
|
|
339
|
+
* Clears all internal state including zipEntries[], inBuffer, and zipComment.
|
|
340
|
+
*
|
|
341
|
+
* Note: ZipCompress and ZipDecompress instances are not recreated.
|
|
342
|
+
* ZSTD codec is a module-level singleton and is lazily initialized on first use,
|
|
343
|
+
* so no cleanup is needed. No memory buffers are allocated until compression/decompression
|
|
344
|
+
* operations are performed.
|
|
345
|
+
*/
|
|
346
|
+
private resetZipData(): void {
|
|
347
|
+
this.inBuffer = null;
|
|
348
|
+
this.zipEntries = [];
|
|
349
|
+
this.reset();
|
|
350
|
+
this.zipComment = null;
|
|
351
|
+
// Reset lazy-loaded components so they can be recreated on next access
|
|
352
|
+
this._zipkitCmp = null;
|
|
353
|
+
this._zipkitDeCmp = null;
|
|
354
|
+
// Note: ZSTD codec is module-level singleton, so no cleanup needed
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get central directory entries (synchronous, buffer-based only)
|
|
359
|
+
*
|
|
360
|
+
* Returns entries from zipEntries[] array which serves as unified storage.
|
|
361
|
+
*
|
|
362
|
+
* **Important**: This method does NOT load the ZIP file. You must call `loadZip()` first
|
|
363
|
+
* to populate zipEntries[]. If the ZIP is not loaded, this method returns an empty array.
|
|
364
|
+
*
|
|
365
|
+
* @param debug - Optional debug flag for logging
|
|
366
|
+
* @returns Array of ZipEntry objects, or empty array if ZIP not loaded
|
|
367
|
+
*/
|
|
368
|
+
getDirectory(debug?: boolean): ZipEntry[] {
|
|
369
|
+
return this.zipEntries;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get specific ZIP entry by filename from zipEntries[] array
|
|
375
|
+
*
|
|
376
|
+
* @param filename - The name/path of the file/directory to find
|
|
377
|
+
* @returns ZipEntry object if found, null if not found
|
|
378
|
+
*/
|
|
379
|
+
getZipEntry(filename: string): ZipEntry | null {
|
|
380
|
+
try {
|
|
381
|
+
const centralDir = this.getDirectory();
|
|
382
|
+
return centralDir.find((entry: ZipEntry) => entry.filename === filename) || null;
|
|
383
|
+
} catch (error) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Ensure buffer is available (in-memory mode)
|
|
390
|
+
*/
|
|
391
|
+
public ensureBuffer(): Buffer {
|
|
392
|
+
if (!this.inBuffer) {
|
|
393
|
+
this.inBuffer = Buffer.alloc(0);
|
|
394
|
+
}
|
|
395
|
+
return this.inBuffer;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Parse local header (in-memory mode)
|
|
400
|
+
*/
|
|
401
|
+
public parseLocalHeader(entry: ZipEntry, buffer: Buffer): Buffer {
|
|
402
|
+
const localData = buffer.subarray(entry.localHdrOffset);
|
|
403
|
+
|
|
404
|
+
if (localData.readUInt32LE(0) !== LOCAL_HDR.SIGNATURE) {
|
|
405
|
+
throw new Error(Errors.INVALID_CEN);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let _encryptLen = 0;
|
|
409
|
+
const _bitFlags = localData.readUInt16LE(LOCAL_HDR.FLAGS);
|
|
410
|
+
if (_bitFlags & GP_FLAG.ENCRYPTED) {
|
|
411
|
+
_encryptLen = ENCRYPT_HDR_SIZE;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let _fnameLen = localData.readUInt16LE(LOCAL_HDR.FNAME_LEN);
|
|
415
|
+
let _extraLen = localData.readUInt16LE(LOCAL_HDR.EXTRA_LEN);
|
|
416
|
+
let _localSize = LOCAL_HDR.SIZE + _fnameLen + _extraLen;
|
|
417
|
+
|
|
418
|
+
// For encrypted files: entry.compressedSize from central directory includes the 12-byte encryption header
|
|
419
|
+
// parseLocalHeader should return encrypted data WITHOUT the header (decryptBuffer will extract and prepend it)
|
|
420
|
+
// Structure: [local header][filename][extra][12-byte encryption header][encrypted compressed data]
|
|
421
|
+
// We need to read: encrypted compressed data (without the 12-byte header)
|
|
422
|
+
// Start: _localSize + _encryptLen (after the header)
|
|
423
|
+
// End: _localSize + entry.compressedSize (total includes header, so this gives us data without header)
|
|
424
|
+
const dataStart = _localSize + _encryptLen;
|
|
425
|
+
const dataEnd = _localSize + entry.compressedSize;
|
|
426
|
+
|
|
427
|
+
// Return encrypted data WITHOUT the encryption header
|
|
428
|
+
// decryptBuffer will extract the header from localSize to localSize+12 and prepend it
|
|
429
|
+
return localData.subarray(dataStart, dataEnd);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Test CRC32 checksum
|
|
434
|
+
* Always checks CRC against entry.crc from central directory
|
|
435
|
+
* For DATA_DESC files, CRC is stored in central directory, not local header
|
|
436
|
+
*/
|
|
437
|
+
public testCRC32(entry: ZipEntry, data: Buffer): boolean {
|
|
438
|
+
// Always check CRC against entry.crc from central directory
|
|
439
|
+
// For DATA_DESC files, CRC is stored in central directory, not local header
|
|
440
|
+
const _crc = crc32(data);
|
|
441
|
+
const isValid = _crc === entry.crc;
|
|
442
|
+
Logger.debug(`[DEBUG] testCRC32() comparison: calculated=${_crc}, stored=${entry.crc}, isValid=${isValid ? '✓' : '✗'}, DATA_DESC=${(entry.bitFlags & 0x8) !== 0 ? 'YES' : 'NO'}`);
|
|
443
|
+
return isValid;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Test SHA256 hash
|
|
448
|
+
*/
|
|
449
|
+
public testSHA256(entry: ZipEntry, data: Buffer): boolean {
|
|
450
|
+
if (!entry.sha256) {
|
|
451
|
+
throw new Error(Errors.UNKNOWN_SHA256);
|
|
452
|
+
}
|
|
453
|
+
const _sha256 = sha256(data);
|
|
454
|
+
const isValid = _sha256 === entry.sha256;
|
|
455
|
+
Logger.debug(`[DEBUG] testSHA256() comparison: calculated=${_sha256}, stored=${entry.sha256}, isValid=${isValid ? '✓' : '✗'}`);
|
|
456
|
+
return isValid;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get memory usage statistics
|
|
461
|
+
*
|
|
462
|
+
* @returns Object containing backend type, total memory usage in bytes, and entry count
|
|
463
|
+
*/
|
|
464
|
+
getMemoryUsage(): { backend: 'buffer' | 'file' | 'none'; memoryUsage: number; entries: number } {
|
|
465
|
+
const baseMemory = this.inBuffer?.length || 0;
|
|
466
|
+
const entriesMemory = this.zipEntries.length * 1024; // Approximate memory per entry
|
|
467
|
+
|
|
468
|
+
let backend: 'buffer' | 'file' | 'none';
|
|
469
|
+
if (this.inBuffer) {
|
|
470
|
+
backend = 'buffer';
|
|
471
|
+
} else {
|
|
472
|
+
// Zipkit is buffer-based only, so if no buffer, backend is 'none'
|
|
473
|
+
// For file-based operations, use ZipkitServer
|
|
474
|
+
backend = 'none';
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
backend,
|
|
479
|
+
memoryUsage: baseMemory + entriesMemory,
|
|
480
|
+
entries: this.zipEntries.length
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Create a new ZIP entry and automatically add it to zipEntries[]
|
|
486
|
+
*
|
|
487
|
+
* @param filename - Entry filename
|
|
488
|
+
* @param data - Optional entry data (for CRC calculation)
|
|
489
|
+
* @param options - Optional entry options
|
|
490
|
+
* @returns Created ZipEntry instance
|
|
491
|
+
*/
|
|
492
|
+
createZipEntry(filename: string, data?: Buffer, options?: any): ZipEntry {
|
|
493
|
+
const entry = new ZipEntry(filename, null, false);
|
|
494
|
+
// Set basic properties
|
|
495
|
+
if (data) {
|
|
496
|
+
entry.crc = crc32(data);
|
|
497
|
+
entry.uncompressedSize = data.length;
|
|
498
|
+
entry.compressedSize = data.length;
|
|
499
|
+
} else {
|
|
500
|
+
entry.crc = 0;
|
|
501
|
+
entry.uncompressedSize = 0;
|
|
502
|
+
entry.compressedSize = 0;
|
|
503
|
+
}
|
|
504
|
+
entry.cmpMethod = CMP_METHOD.STORED;
|
|
505
|
+
entry.bitFlags = 0;
|
|
506
|
+
// Note: lastModTime and lastModDate properties may not exist in ZipEntry
|
|
507
|
+
// entry.lastModTime = Math.floor(Date.now() / 1000);
|
|
508
|
+
// entry.lastModDate = Math.floor(Date.now() / 1000);
|
|
509
|
+
|
|
510
|
+
// Automatically add entry to zipEntries[] (maintains order as single source of truth)
|
|
511
|
+
this.zipEntries.push(entry);
|
|
512
|
+
|
|
513
|
+
return entry;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Copy entry from another ZIP (buffer-based only)
|
|
518
|
+
* For file-based ZIP operations, use ZipkitServer.copyEntry()
|
|
519
|
+
*/
|
|
520
|
+
async copyEntry(sourceEntry: ZipEntry): Promise<Buffer> {
|
|
521
|
+
// For Buffer-based ZIP, we need to reconstruct the entry data
|
|
522
|
+
// This is a simplified version - in practice, you'd need to read from the original ZIP
|
|
523
|
+
if (sourceEntry.cmpData) {
|
|
524
|
+
const localHdr = sourceEntry.createLocalHdr();
|
|
525
|
+
return Buffer.concat([localHdr, sourceEntry.cmpData]);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
throw new Error('Cannot copy entry: no compressed data available');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Central end header getter (for compatibility)
|
|
533
|
+
*
|
|
534
|
+
* Returns a function that creates the central end header using totalEntries from zipEntries[].
|
|
535
|
+
*
|
|
536
|
+
* @returns Function that creates central end header buffer
|
|
537
|
+
*/
|
|
538
|
+
get centralEndHdr(): (centralDirSize: number, centralDirOffset: number) => Buffer {
|
|
539
|
+
// Return a function that calls centralEndHdr with totalEntries from zipEntries[]
|
|
540
|
+
return (centralDirSize: number, centralDirOffset: number) => {
|
|
541
|
+
const totalEntries = this.zipEntries.length;
|
|
542
|
+
return this.centralEndHdrMethod(centralDirSize, centralDirOffset, totalEntries);
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Get ZIP comment
|
|
548
|
+
*
|
|
549
|
+
* @returns ZIP file comment string or null
|
|
550
|
+
*/
|
|
551
|
+
getZipComment(): string | null {
|
|
552
|
+
return this.zipComment;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ============================================================================
|
|
556
|
+
// Buffer-based ZIP Loading Methods (merged from ZipLoadEntries)
|
|
557
|
+
// ============================================================================
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Load EOCD from in-memory buffer (buffer mode)
|
|
561
|
+
*/
|
|
562
|
+
private loadEOCDFromBuffer(buffer: Buffer): void {
|
|
563
|
+
const fileSize = buffer.length;
|
|
564
|
+
const searchSize = Math.min(0xFFFF + 22, fileSize);
|
|
565
|
+
const searchStart = fileSize - searchSize;
|
|
566
|
+
const scan = buffer.subarray(searchStart, searchStart + searchSize);
|
|
567
|
+
|
|
568
|
+
// Find EOCD signature
|
|
569
|
+
let eocdOffset = -1;
|
|
570
|
+
for (let i = scan.length - 22; i >= 0; i--) {
|
|
571
|
+
if (scan[i] === 0x50) {
|
|
572
|
+
if (scan.readUInt32LE(i) === CENTRAL_END.SIGNATURE) {
|
|
573
|
+
eocdOffset = searchStart + i;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (eocdOffset === -1) {
|
|
579
|
+
throw new Error(Errors.INVALID_FORMAT);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const eocdBuffer = buffer.subarray(eocdOffset, eocdOffset + 22);
|
|
583
|
+
if (eocdBuffer.readUInt32LE(0) === CENTRAL_END.SIGNATURE) {
|
|
584
|
+
this.centralDirSize = eocdBuffer.readUInt32LE(CENTRAL_END.CENTRAL_DIR_SIZE);
|
|
585
|
+
this.centralDirOffset = eocdBuffer.readUInt32LE(CENTRAL_END.CENTRAL_DIR_OFFSET);
|
|
586
|
+
|
|
587
|
+
if (this.centralDirOffset === 0xFFFFFFFF) {
|
|
588
|
+
// ZIP64: locate locator (20 bytes before EOCD) and then ZIP64 EOCD (56 bytes)
|
|
589
|
+
const locatorOffset = eocdOffset - 20;
|
|
590
|
+
const locatorBuffer = buffer.subarray(locatorOffset, locatorOffset + 20);
|
|
591
|
+
if (locatorBuffer.readUInt32LE(0) === ZIP64_CENTRAL_END.SIGNATURE) {
|
|
592
|
+
const zip64Offset = Number(locatorBuffer.readBigUInt64LE(8));
|
|
593
|
+
const zip64Buffer = buffer.subarray(zip64Offset, zip64Offset + 56);
|
|
594
|
+
this.centralDirSize = Number(zip64Buffer.readBigUInt64LE(ZIP64_CENTRAL_DIR.CENTRAL_DIR_SIZE));
|
|
595
|
+
this.centralDirOffset = Number(zip64Buffer.readBigUInt64LE(ZIP64_CENTRAL_DIR.CENTRAL_DIR_OFFSET));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
throw new Error(Errors.INVALID_FORMAT);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// ZIP comment
|
|
603
|
+
const commentLength = eocdBuffer.readUInt16LE(CENTRAL_END.ZIP_COMMENT_LEN);
|
|
604
|
+
if (commentLength > 0) {
|
|
605
|
+
const commentStart = eocdOffset + 22;
|
|
606
|
+
this.zipComment = buffer.subarray(commentStart, commentStart + commentLength).toString();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Get directory from buffer (buffer mode only)
|
|
612
|
+
* Note: zipEntries[] in Zipkit is the single source of truth for caching.
|
|
613
|
+
* This method always parses fresh from the buffer.
|
|
614
|
+
*/
|
|
615
|
+
private getDirectoryFromBuffer(): ZipEntry[] {
|
|
616
|
+
if (!this.inBuffer) {
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const buffer = this.inBuffer;
|
|
621
|
+
const entries: ZipEntry[] = [];
|
|
622
|
+
let offset = this.centralDirOffset;
|
|
623
|
+
let remaining = this.centralDirSize;
|
|
624
|
+
const bufferSize = this.config.bufferSize || 512 * 1024;
|
|
625
|
+
|
|
626
|
+
while (remaining > 0) {
|
|
627
|
+
const currentBufferSize = Math.min(bufferSize, remaining);
|
|
628
|
+
const chunk = buffer.subarray(offset, offset + currentBufferSize);
|
|
629
|
+
|
|
630
|
+
// Parse entries from chunk
|
|
631
|
+
let chunkOffset = 0;
|
|
632
|
+
while (chunkOffset < chunk.length) {
|
|
633
|
+
if (chunk.readUInt32LE(chunkOffset) !== CENTRAL_DIR.SIGNATURE) {
|
|
634
|
+
break; // End of central directory
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Parse central directory entry
|
|
638
|
+
const entry = new ZipEntry(null, null, false);
|
|
639
|
+
const entryData = chunk.subarray(chunkOffset);
|
|
640
|
+
const remainingData = entry.readZipEntry(entryData);
|
|
641
|
+
|
|
642
|
+
entries.push(entry);
|
|
643
|
+
|
|
644
|
+
// Move to next entry
|
|
645
|
+
chunkOffset += (entryData.length - remainingData.length);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
offset += currentBufferSize;
|
|
649
|
+
remaining -= currentBufferSize;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Return entries in original order (do NOT sort by default)
|
|
653
|
+
// Central directory order should match data section order
|
|
654
|
+
// Note: zipEntries[] in Zipkit is the single source of truth for caching
|
|
655
|
+
return entries;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Reset internal state (private method for Zipkit to call)
|
|
660
|
+
* Clears all ZIP-related data while keeping the instance alive
|
|
661
|
+
*/
|
|
662
|
+
private reset(): void {
|
|
663
|
+
this.centralDirSize = 0;
|
|
664
|
+
this.centralDirOffset = 0;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Create central end header
|
|
669
|
+
* Note: totalEntries must be passed as parameter since zipEntries[] in Zipkit is the cache
|
|
670
|
+
*/
|
|
671
|
+
private centralEndHdrMethod(centralDirSize: number, centralDirOffset: number, totalEntries: number): Buffer {
|
|
672
|
+
const ceBuf = Buffer.alloc(CENTRAL_END.SIZE);
|
|
673
|
+
ceBuf.writeUInt32LE(CENTRAL_END.SIGNATURE, 0);
|
|
674
|
+
ceBuf.writeUInt16LE(0, 4); // Number of this disk
|
|
675
|
+
ceBuf.writeUInt16LE(0, 6); // Number of the disk with the start of the central directory
|
|
676
|
+
ceBuf.writeUInt16LE(totalEntries, 8); // Total number of entries
|
|
677
|
+
ceBuf.writeUInt16LE(totalEntries, 10); // Total number of entries on this disk
|
|
678
|
+
ceBuf.writeUInt32LE(centralDirSize, 12); // Size of central directory
|
|
679
|
+
ceBuf.writeUInt32LE(centralDirOffset, 16); // Offset of start of central directory
|
|
680
|
+
ceBuf.writeUInt16LE(0, 20); // ZIP file comment length
|
|
681
|
+
|
|
682
|
+
return ceBuf;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Calculate Merkle Root of the ZIP file
|
|
687
|
+
*
|
|
688
|
+
* Excludes metadata files (META-INF) to ensure consistent calculation.
|
|
689
|
+
* Simply calls getDirectory() and returns null if empty.
|
|
690
|
+
*
|
|
691
|
+
* @returns Merkle root string or null if calculation fails
|
|
692
|
+
*/
|
|
693
|
+
getMerkleRoot(): string | null {
|
|
694
|
+
// Simply get directory - no loading logic
|
|
695
|
+
let zipEntries: ZipEntry[];
|
|
696
|
+
try {
|
|
697
|
+
zipEntries = this.getDirectory();
|
|
698
|
+
} catch (error) {
|
|
699
|
+
// Catch any errors from getDirectory() - this should never happen
|
|
700
|
+
// but if it does, log it and return null
|
|
701
|
+
Logger.error(`getMerkleRoot(): Error calling getDirectory(): ${error instanceof Error ? error.message : String(error)}`);
|
|
702
|
+
Logger.error(`getMerkleRoot(): Stack trace: ${error instanceof Error ? error.stack : 'No stack trace'}`);
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (!zipEntries || zipEntries.length === 0) {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const hashAccumulator = new HashCalculator({ enableAccumulation: true });
|
|
711
|
+
|
|
712
|
+
// Filter out metadata files (META-INF) to ensure consistent Merkle Root calculation
|
|
713
|
+
// Inline check to avoid conflict with ZipkitBrowser's isMetadataFile() method
|
|
714
|
+
const contentEntries = zipEntries.filter(entry => {
|
|
715
|
+
const filename = entry.filename || '';
|
|
716
|
+
return filename !== TIMESTAMP_SUBMITTED &&
|
|
717
|
+
filename !== TIMESTAMP_METADATA &&
|
|
718
|
+
filename !== TOKENIZED_METADATA;
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
for (const entry of contentEntries) {
|
|
722
|
+
if (entry.sha256) {
|
|
723
|
+
// Convert hex string to Buffer
|
|
724
|
+
const hashBuffer = Buffer.from(entry.sha256, 'hex');
|
|
725
|
+
hashAccumulator.addHash(hashBuffer);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const merkleRoot = hashAccumulator.merkleRoot();
|
|
730
|
+
if (!merkleRoot) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return merkleRoot;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Calculate Merkle Root of the ZIP file asynchronously
|
|
739
|
+
*
|
|
740
|
+
* Excludes metadata files (META-INF) to ensure consistent calculation.
|
|
741
|
+
* Works with both buffer-based and file-based ZIPs.
|
|
742
|
+
*
|
|
743
|
+
* @returns Promise resolving to Merkle root string or null if calculation fails
|
|
744
|
+
*/
|
|
745
|
+
async getMerkleRootAsync(): Promise<string | null> {
|
|
746
|
+
let zipEntries: ZipEntry[] = [];
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
// Get entries based on ZIP type
|
|
750
|
+
if (this.inBuffer) {
|
|
751
|
+
zipEntries = this.getDirectory();
|
|
752
|
+
} else {
|
|
753
|
+
// File-based: cannot get entries synchronously - return null
|
|
754
|
+
// Caller should use ZipkitServer.getMerkleRootAsync() for file-based ZIPs
|
|
755
|
+
Logger.error('getMerkleRootAsync() called on file-based ZIP. Use ZipkitServer.getMerkleRootAsync() instead.');
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
} catch (error) {
|
|
759
|
+
Logger.error(`Failed to get directory for merkle root calculation: ${error instanceof Error ? error.message : String(error)}`);
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (!zipEntries || zipEntries.length === 0) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const hashAccumulator = new HashCalculator({ enableAccumulation: true });
|
|
768
|
+
|
|
769
|
+
// Filter out metadata files (META-INF) to ensure consistent Merkle Root calculation
|
|
770
|
+
// Inline check to avoid conflict with ZipkitBrowser's isMetadataFile() method
|
|
771
|
+
const contentEntries = zipEntries.filter(entry => {
|
|
772
|
+
const filename = entry.filename || '';
|
|
773
|
+
return filename !== TIMESTAMP_SUBMITTED &&
|
|
774
|
+
filename !== TIMESTAMP_METADATA &&
|
|
775
|
+
filename !== TOKENIZED_METADATA;
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
for (const entry of contentEntries) {
|
|
779
|
+
if (entry.sha256) {
|
|
780
|
+
// Convert hex string to Buffer
|
|
781
|
+
const hashBuffer = Buffer.from(entry.sha256, 'hex');
|
|
782
|
+
hashAccumulator.addHash(hashBuffer);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const merkleRoot = hashAccumulator.merkleRoot();
|
|
787
|
+
if (!merkleRoot) {
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return merkleRoot;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
}
|