neozipkit 0.1.1

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.
Files changed (135) hide show
  1. package/README.md +400 -0
  2. package/contracts/Lock.sol +34 -0
  3. package/contracts/bytecode.txt +1 -0
  4. package/contracts/compile-contract.js +168 -0
  5. package/contracts/deploy/deploy-both-contracts.js +286 -0
  6. package/contracts/deploy/deploy-enhanced-contract.js +197 -0
  7. package/contracts/deploy/deploy-private-contract.js +190 -0
  8. package/contracts/deploy/deploy-public-clean.js +1063 -0
  9. package/contracts/deploy/deploy-public-contract.js +1107 -0
  10. package/contracts/deployment.config.example.js +25 -0
  11. package/contracts/remix-compile.js +222 -0
  12. package/contracts/setup-deployment.js +51 -0
  13. package/contracts/src/NZIP-NFT-Private.sol +350 -0
  14. package/contracts/src/NZIP-NFT.sol +265 -0
  15. package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +260 -0
  16. package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +289 -0
  17. package/dist/browser/CertificateGeneratorBrowser.d.ts +75 -0
  18. package/dist/browser/CertificateGeneratorBrowser.js +433 -0
  19. package/dist/browser/TokenVerifierBrowser.d.ts +76 -0
  20. package/dist/browser/TokenVerifierBrowser.js +299 -0
  21. package/dist/browser/ZipKitBrowser.d.ts +18 -0
  22. package/dist/browser/ZipKitBrowser.js +250 -0
  23. package/dist/browser/ZipKitStampBrowser.d.ts +2 -0
  24. package/dist/browser/ZipKitStampBrowser.js +12 -0
  25. package/dist/browser-index.d.ts +21 -0
  26. package/dist/browser-index.js +58 -0
  27. package/dist/browser.d.ts +21 -0
  28. package/dist/browser.js +58 -0
  29. package/dist/index.d.ts +29 -0
  30. package/dist/index.js +76 -0
  31. package/dist/server/blockchain/CertificateToken.d.ts +48 -0
  32. package/dist/server/blockchain/CertificateToken.js +386 -0
  33. package/dist/server/blockchain/NFTMinter.d.ts +41 -0
  34. package/dist/server/blockchain/NFTMinter.js +323 -0
  35. package/dist/server/blockchain/TokenQuerier.d.ts +31 -0
  36. package/dist/server/blockchain/TokenQuerier.js +259 -0
  37. package/dist/server/blockchain/TokenVerifier.d.ts +20 -0
  38. package/dist/server/blockchain/TokenVerifier.js +395 -0
  39. package/dist/server/blockchain/WalletAnalyzer.d.ts +81 -0
  40. package/dist/server/blockchain/WalletAnalyzer.js +356 -0
  41. package/dist/server/blockchain/WalletFormatter.d.ts +39 -0
  42. package/dist/server/blockchain/WalletFormatter.js +196 -0
  43. package/dist/server/blockchain/WalletManager.d.ts +46 -0
  44. package/dist/server/blockchain/WalletManager.js +294 -0
  45. package/dist/server/blockchain/ZipMetadata.d.ts +27 -0
  46. package/dist/server/blockchain/ZipMetadata.js +131 -0
  47. package/dist/server/blockchain/ZipTokenProcessor.d.ts +44 -0
  48. package/dist/server/blockchain/ZipTokenProcessor.js +204 -0
  49. package/dist/server/blockchain/constants.d.ts +102 -0
  50. package/dist/server/blockchain/constants.js +968 -0
  51. package/dist/server/blockchain/index.d.ts +9 -0
  52. package/dist/server/blockchain/index.js +44 -0
  53. package/dist/server/blockchain/test-certificate.d.ts +8 -0
  54. package/dist/server/blockchain/test-certificate.js +100 -0
  55. package/dist/server/blockchain/types.d.ts +69 -0
  56. package/dist/server/blockchain/types.js +51 -0
  57. package/dist/server/platform/server/ZipKitServer.d.ts +105 -0
  58. package/dist/server/platform/server/ZipKitServer.js +437 -0
  59. package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +91 -0
  60. package/dist/server/platform/servercomponents/ZipKitOTS.js +271 -0
  61. package/dist/shared/ZipEntry.d.ts +131 -0
  62. package/dist/shared/ZipEntry.js +686 -0
  63. package/dist/shared/ZipKit.d.ts +249 -0
  64. package/dist/shared/ZipKit.js +789 -0
  65. package/dist/shared/components/CertificateOTS.d.ts +15 -0
  66. package/dist/shared/components/CertificateOTS.js +116 -0
  67. package/dist/shared/components/FileSys.d.ts +2 -0
  68. package/dist/shared/components/FileSys.js +113 -0
  69. package/dist/shared/components/HashAccumulator.d.ts +55 -0
  70. package/dist/shared/components/HashAccumulator.js +100 -0
  71. package/dist/shared/components/Support.d.ts +63 -0
  72. package/dist/shared/components/Support.js +70 -0
  73. package/dist/shared/components/Util.d.ts +21 -0
  74. package/dist/shared/components/Util.js +115 -0
  75. package/dist/shared/components/ZipCrypto.d.ts +3 -0
  76. package/dist/shared/components/ZipCrypto.js +78 -0
  77. package/dist/shared/components/merkleTree.d.ts +66 -0
  78. package/dist/shared/components/merkleTree.js +136 -0
  79. package/dist/shared/constants/Errors.d.ts +53 -0
  80. package/dist/shared/constants/Errors.js +69 -0
  81. package/dist/shared/constants/Headers.d.ts +167 -0
  82. package/dist/shared/constants/Headers.js +191 -0
  83. package/dist/shared/types/index.d.ts +21 -0
  84. package/dist/shared/types/index.js +3 -0
  85. package/dist/shared/types.d.ts +33 -0
  86. package/dist/shared/types.js +8 -0
  87. package/dist/shared/version.d.ts +4 -0
  88. package/dist/shared/version.js +7 -0
  89. package/dist/types/index.d.ts +21 -0
  90. package/dist/types/index.js +3 -0
  91. package/examples/unzip-token.ts +488 -0
  92. package/examples/zip-token.ts +604 -0
  93. package/package.json +117 -0
  94. package/src/browser/CertificateGeneratorBrowser.ts +498 -0
  95. package/src/browser/TokenVerifierBrowser.ts +344 -0
  96. package/src/browser/ZipKitBrowser.ts +242 -0
  97. package/src/browser/ZipKitStampBrowser.ts +9 -0
  98. package/src/browser-index.ts +36 -0
  99. package/src/index.ts +48 -0
  100. package/src/server/blockchain/CertificateToken.ts +444 -0
  101. package/src/server/blockchain/NFTMinter.ts +357 -0
  102. package/src/server/blockchain/TokenQuerier.ts +287 -0
  103. package/src/server/blockchain/TokenVerifier.ts +453 -0
  104. package/src/server/blockchain/WalletAnalyzer.ts +397 -0
  105. package/src/server/blockchain/WalletFormatter.ts +206 -0
  106. package/src/server/blockchain/WalletManager.ts +294 -0
  107. package/src/server/blockchain/ZipMetadata.ts +117 -0
  108. package/src/server/blockchain/ZipTokenProcessor.ts +234 -0
  109. package/src/server/blockchain/constants.ts +969 -0
  110. package/src/server/blockchain/index.ts +42 -0
  111. package/src/server/blockchain/test-certificate.ts +137 -0
  112. package/src/server/blockchain/types.ts +134 -0
  113. package/src/server/platform/server/ZipKitServer.ts +459 -0
  114. package/src/server/platform/servercomponents/ZipKitOTS.ts +313 -0
  115. package/src/server/platform/servercomponents/opentimestamps.d.ts +23 -0
  116. package/src/shared/ZipEntry.ts +767 -0
  117. package/src/shared/ZipKit.ts +899 -0
  118. package/src/shared/components/CertificateOTS.ts +132 -0
  119. package/src/shared/components/FileSys.ts +87 -0
  120. package/src/shared/components/HashAccumulator.ts +113 -0
  121. package/src/shared/components/Support.ts +77 -0
  122. package/src/shared/components/Util.ts +113 -0
  123. package/src/shared/components/ZipCrypto.ts +91 -0
  124. package/src/shared/components/merkleTree.ts +162 -0
  125. package/src/shared/constants/Errors.ts +82 -0
  126. package/src/shared/constants/Headers.ts +203 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/shared/types/modules.d.ts +22 -0
  129. package/src/shared/types/opentimestamps.d.ts +23 -0
  130. package/src/shared/types.d.ts +8 -0
  131. package/src/shared/types.ts +43 -0
  132. package/src/shared/version.ts +4 -0
  133. package/src/types/index.ts +25 -0
  134. package/src/types/modules.d.ts +22 -0
  135. package/src/types/opentimestamps.d.ts +23 -0
@@ -0,0 +1,899 @@
1
+ // ======================================
2
+ // ZipKit.ts
3
+ // Copyright (c) 2025 NeoWare, Inc. All rights reserved.
4
+ // ======================================
5
+
6
+ import ZipEntry from './ZipEntry';
7
+ import Errors from './constants/Errors';
8
+ import { crc32, readBigUInt64LE } from './components/Util';
9
+ import { sha256, decrypter, encrypter } from './components/ZipCrypto';
10
+ import Support from './components/Support';
11
+ import pako from 'pako';
12
+ import {
13
+ LOCAL_HDR,
14
+ ENCRYPT_HDR_SIZE,
15
+ CMP_METHOD,
16
+ CENTRAL_END,
17
+ CENTRAL_DIR,
18
+ ZIP64_CENTRAL_END,
19
+ ZIP64_CENTRAL_DIR ,
20
+ GP_FLAG,
21
+ TIMESTAMP_SUBMITTED,
22
+ TIMESTAMP_METADATA,
23
+ TOKENIZED_METADATA,
24
+ LOCAL_FILE_HEADER,
25
+ CENTRAL_FILE_HEADER,
26
+ CENTRAL_DIRECTORY_END,
27
+ HDR_ID
28
+ } from './constants/Headers';
29
+ import HashAccumulater from './components/HashAccumulator';
30
+ import { ZstdInit, ZstdSimple } from '@oneidentity/zstd-js';
31
+ import { NEOZIPKIT_INFO } from './types';
32
+
33
+ // We'll initialize zstd when needed
34
+ let zstdCodec: { ZstdSimple: typeof ZstdSimple } | null = null;
35
+
36
+ // Async initialization function for zstd
37
+ async function initZstd(): Promise<{ ZstdSimple: typeof ZstdSimple }> {
38
+ if (!zstdCodec) {
39
+ zstdCodec = await ZstdInit();
40
+ }
41
+ return zstdCodec;
42
+ }
43
+
44
+ export {
45
+ ZipEntry,
46
+ Errors,
47
+ CMP_METHOD,
48
+ TIMESTAMP_METADATA,
49
+ TIMESTAMP_SUBMITTED,
50
+ TOKENIZED_METADATA,
51
+ LOCAL_HDR,
52
+ ENCRYPT_HDR_SIZE,
53
+ CENTRAL_END,
54
+ CENTRAL_DIR,
55
+ ZIP64_CENTRAL_END,
56
+ ZIP64_CENTRAL_DIR ,
57
+ GP_FLAG,
58
+ LOCAL_FILE_HEADER,
59
+ CENTRAL_FILE_HEADER,
60
+ CENTRAL_DIRECTORY_END,
61
+ HDR_ID
62
+ };
63
+
64
+ /**
65
+ * Options for compressing files in a ZIP archive
66
+ */
67
+ export interface CompressOptions {
68
+ level?: number; // Compression level (1-9, 0=store)
69
+ password?: string | null; // Password for encryption
70
+ useSHA256?: boolean; // Whether to calculate SHA256 hash default is false
71
+ useZstd?: boolean; // Whether to use Zstandard compression default is true
72
+ }
73
+
74
+ /**
75
+ * Callbacks for ZIP file creation process
76
+ */
77
+ export interface CreateZipOptions {
78
+ onError?: (error: Error) => void; // Called when an error occurs
79
+ onEntryDone?: (entry: ZipEntry, status: string) => void; // Called when entry processing completes
80
+ onReadEntryData?: (entry: ZipEntry) => Buffer; // Called to read entry data
81
+ onOutputBuffer?: (data: Buffer) => void; // Called to write output data
82
+ }
83
+
84
+ /**
85
+ * Main class for ZIP file operations including compression, extraction, and encryption
86
+ */
87
+ export default class ZipKit {
88
+ static readonly version = NEOZIPKIT_INFO.version;
89
+ static readonly releaseDate = NEOZIPKIT_INFO.releaseDate;
90
+
91
+ private inBuffer: Buffer | null = null;
92
+ private zipEntries: ZipEntry[] = [];
93
+ private sortName = false;
94
+
95
+ // Central End Directory values for existing Zip file
96
+ private centralDirSize: number = 0;
97
+ private centralDirOffset: number = 0;
98
+
99
+ // Timestamp values
100
+ private stampEntry: ZipEntry | null = null;
101
+
102
+ zipComment: string | null = null;
103
+
104
+ static support = Support;
105
+
106
+ /**
107
+ * Creates a new ZipKit instance
108
+ */
109
+ constructor() {
110
+ }
111
+
112
+ /**
113
+ * Resets all ZIP-related data to initial state
114
+ */
115
+ resetZipData() {
116
+ this.inBuffer = null;
117
+ this.zipEntries = [];
118
+ this.centralDirSize = 0;
119
+ this.centralDirOffset = 0;
120
+ this.zipComment = null;
121
+ }
122
+
123
+ /**
124
+ * Loads a ZIP file from a buffer
125
+ * @param inBuf - Buffer containing ZIP file data
126
+ */
127
+ loadZip(inBuf: Buffer) {
128
+ this.resetZipData();
129
+ this.inBuffer = inBuf;
130
+ }
131
+
132
+ /**
133
+ * Tests if data matches the CRC32 checksum
134
+ * @param entry - ZIP entry to check
135
+ * @param data - Data buffer to verify
136
+ * @returns boolean indicating if CRC matches
137
+ */
138
+ testCRC32(entry: ZipEntry, data: Buffer): boolean {
139
+ // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written
140
+ if ((entry.bitFlags & 0x8) !== 0x8) {
141
+ const _crc = crc32(data);
142
+ // console.log(' CRC: '+ sprintf("%08x", _crc));
143
+ // console.log('ent.CRC: ' + sprintf("%08x", entry.crc));
144
+ if (_crc !== entry.crc) {
145
+ return false;
146
+ }
147
+ } else {
148
+ // @TODO: load and check data descriptor header
149
+ // The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure
150
+ // (optionally preceded by a 4-byte signature) immediately after the compressed data:
151
+ }
152
+
153
+ return true;
154
+ }
155
+
156
+ /**
157
+ * Tests if data matches the SHA256 hash
158
+ * @param entry - ZIP entry to check
159
+ * @param data - Data buffer to verify
160
+ * @returns boolean indicating if SHA256 matches
161
+ * @throws Error if entry has no SHA256 hash
162
+ */
163
+ testSHA256(entry: ZipEntry, data: Buffer): boolean {
164
+ if (!entry.sha256) {
165
+ throw new Error(Errors.UNKNOWN_SHA256);
166
+ }
167
+ const _sha256 = sha256(data);
168
+ return _sha256 === entry.sha256;
169
+ }
170
+
171
+ /**
172
+ * Locates and parses the central end directory
173
+ * @param inBuffer - Buffer containing ZIP file data
174
+ * @returns Number of entries in ZIP file
175
+ * @throws Error if central directory is invalid
176
+ */
177
+ getCentralEnd(inBuffer: Buffer): number {
178
+ let i = inBuffer.length - CENTRAL_END.SIZE;
179
+ let max = Math.max(0, i - 0xffff); // 0xFFFF is the max zip file comment length
180
+ let endStart = inBuffer.length;
181
+ let endOffset = -1; // Start offset of the END header
182
+ let commentEnd = 0;
183
+ let volEntries = 0;
184
+ let totalEntries = 0;
185
+
186
+ for (let n=max; i >= n; i--) {
187
+ if (inBuffer[i] !== 0x50) continue; // quick check that the byte is 'P'
188
+ if (inBuffer.readUInt32LE(i) === CENTRAL_END.SIGNATURE) {
189
+ // "PK\005\006"
190
+ endOffset = i;
191
+ commentEnd = i;
192
+ endStart = i + CENTRAL_END.SIZE;
193
+ // We already found a regular signature, let's look just a bit further to check if there's any zip64 signature
194
+ n = i - ZIP64_CENTRAL_END.SIZE;
195
+ continue;
196
+ }
197
+
198
+ if (inBuffer.readUInt32LE(i) === ZIP64_CENTRAL_END.SIGNATURE) {
199
+ // Found a zip64 signature, let's continue reading the whole zip64 record
200
+ n = max;
201
+ continue;
202
+ }
203
+
204
+ if (inBuffer.readUInt32LE(i) === ZIP64_CENTRAL_DIR.SIGNATURE) {
205
+ // Found the zip64 record, let's determine it's size
206
+ endOffset = i;
207
+ endStart = i + readBigUInt64LE(inBuffer, i + ZIP64_CENTRAL_DIR.SIZE) + ZIP64_CENTRAL_DIR.LEAD;
208
+ break;
209
+ }
210
+ }
211
+
212
+ if (!~endOffset) throw new Error(Errors.INVALID_FORMAT);
213
+
214
+ let data = inBuffer.subarray(endOffset, endStart);
215
+
216
+ if (data.readUInt32LE(0) == CENTRAL_END.SIGNATURE) {
217
+ // number of entries on this volume
218
+ volEntries = data.readUInt16LE(CENTRAL_END.VOL_ENTRIES);
219
+ // total number of entries
220
+ totalEntries = data.readUInt16LE(CENTRAL_END.TOTAL_ENTRIES);
221
+ // central directory size in bytes
222
+ this.centralDirSize = data.readUInt32LE(CENTRAL_END.CENTRAL_DIR_SIZE);
223
+ // offset of first CEN header
224
+ this.centralDirOffset = data.readUInt32LE(CENTRAL_END.CENTRAL_DIR_OFFSET);
225
+ } else {
226
+ // number of entries on this volume
227
+ volEntries = readBigUInt64LE(data, ZIP64_CENTRAL_DIR.VOL_ENTRIES);
228
+ // total number of entries
229
+ totalEntries = readBigUInt64LE(data, ZIP64_CENTRAL_DIR.TOTAL_ENTRIES);
230
+ // central directory size in bytes
231
+ this.centralDirSize = readBigUInt64LE(data, ZIP64_CENTRAL_DIR.CENTRAL_DIR_SIZE);
232
+ // offset of first CEN header
233
+ this.centralDirOffset = readBigUInt64LE(data, ZIP64_CENTRAL_DIR.CENTRAL_DIR_OFFSET);
234
+ }
235
+
236
+ // zip file comment length
237
+ let commentLength = data.readUInt16LE(CENTRAL_END.ZIP_COMMENT_LEN);
238
+ if (commentLength > 0) {
239
+ this.zipComment = inBuffer.subarray(endStart, endStart + commentLength).toString();
240
+ }
241
+
242
+ // console.log('totalEntries: ', totalEntries);
243
+ // console.log('centralDirSize; ', this.centralDirSize);
244
+ // console.log('centralDirOffset: ', this.centralDirOffset);
245
+ // console.log('commentLength: ', commentLength);
246
+ // console.log('zipComment: ', this.zipComment);
247
+
248
+ return totalEntries;
249
+ }
250
+
251
+ /**
252
+ * Creates the central end directory header
253
+ * @param centralDirSize - Size of central directory
254
+ * @param centralDirOffset - Offset to central directory
255
+ * @returns Buffer containing end directory header
256
+ */
257
+ centralEndHdr(centralDirSize: number, centralDirOffset: number): Buffer {
258
+ const commentLen = this.zipComment ? this.zipComment.length : 0;
259
+ const data = Buffer.alloc(CENTRAL_END.SIZE + commentLen);
260
+
261
+ // "PK\005\006"
262
+ data.writeUInt32LE(CENTRAL_END.SIGNATURE, 0);
263
+ // number of this disk
264
+ data.writeUInt16LE(0, CENTRAL_END.VOL_NUM);
265
+ // number of the volume/disk with start of the Central Directory
266
+ data.writeUInt16LE(0, CENTRAL_END.VOLDIR_START);
267
+ // number of entries on this volume/disk
268
+ data.writeUInt16LE(0, CENTRAL_END.VOL_ENTRIES);
269
+ // total number of entries on this disk
270
+ data.writeUInt16LE(this.zipEntries.length, CENTRAL_END.TOTAL_ENTRIES);
271
+ // central directory size in bytes
272
+ data.writeUInt32LE(centralDirSize, CENTRAL_END.CENTRAL_DIR_SIZE);
273
+ // offset of first Central Directory Entry
274
+ data.writeUInt32LE(centralDirOffset, CENTRAL_END.CENTRAL_DIR_OFFSET);
275
+ // Zip file comment length
276
+ data.writeUInt16LE(commentLen, CENTRAL_END.ZIP_COMMENT_LEN);
277
+
278
+ if (commentLen > 0 && this.zipComment) {
279
+ const zipCommentBuf = Buffer.from(this.zipComment, 'utf8');
280
+ zipCommentBuf.copy(data, CENTRAL_END.SIZE);
281
+ }
282
+
283
+ return data;
284
+ }
285
+
286
+ /**
287
+ * Reads and parses the ZIP file directory
288
+ * @param debug - Enable debug logging
289
+ * @returns Array of ZIP entries
290
+ */
291
+ getDirectory(debug?: boolean): ZipEntry[] {
292
+ if (this.zipEntries.length > 0) {
293
+ return this.zipEntries;
294
+ }
295
+ const buffer = this.ensureBuffer();
296
+
297
+ // Add this check for empty buffers - if empty, there's no need to try parsing
298
+ if (buffer.length === 0) {
299
+ // This is a new ZIP file, so we return an empty array
300
+ return this.zipEntries;
301
+ }
302
+
303
+ const entries = this.getCentralEnd(buffer);
304
+ if (entries === 0) return [];
305
+
306
+ var entryData = buffer.subarray(this.centralDirOffset, this.centralDirOffset + this.centralDirSize);
307
+ if (entryData.readUInt32LE(0) !== CENTRAL_DIR.SIGNATURE) throw new Error(Errors.INVALID_CEN);
308
+
309
+ const entryList: ZipEntry[] = [];
310
+ for (var i=0; i < entries; i++) {
311
+ let entry = new ZipEntry(null, null, debug);
312
+ entryData = entry.readZipEntry(entryData);
313
+ entryList.push(entry);
314
+ // entry.outputZipEntry();
315
+ }
316
+
317
+ this.zipEntries = entryList;
318
+
319
+ // Sort Zip Entries if sortName option set
320
+ if (this.sortName && this.zipEntries.length > 1)
321
+ this.zipEntries.sort((a,b) => {
322
+ return (a.filename || '').toLowerCase()
323
+ .localeCompare((b.filename || '').toLowerCase());
324
+ });
325
+
326
+ return this.zipEntries;
327
+ }
328
+
329
+ /**
330
+ * Gets a ZIP entry by filename
331
+ * @param fname - Filename to search for
332
+ * @returns ZipEntry if found, null otherwise
333
+ */
334
+ getZipEntry(fname: string): ZipEntry | null {
335
+ try {
336
+ const centralDir = this.getDirectory();
337
+ if (centralDir) {
338
+ for (const entry of centralDir) {
339
+ if (entry.filename === fname) {
340
+ return entry;
341
+ }
342
+ }
343
+ }
344
+ return null;
345
+ } catch (error) {
346
+ // If we get here, treat as a new file (no existing entry)
347
+ return null;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Creates a new ZIP entry
353
+ * @param fname - Filename for new entry
354
+ * @param comment - Optional entry comment
355
+ * @returns Created ZipEntry
356
+ */
357
+ createZipEntry(fname: string, comment?: string): ZipEntry {
358
+ const entry = new ZipEntry(fname, comment);
359
+ this.zipEntries.push(entry);
360
+ return entry;
361
+ }
362
+
363
+ /**
364
+ * Deletes an entry from the ZIP file
365
+ * @param fname - Filename to delete
366
+ * @returns boolean indicating if file was deleted
367
+ */
368
+ deleteFile(fname: string): boolean {
369
+ const entries = this.ensureZipEntries();
370
+ const index = entries.findIndex(zipEntry => zipEntry.filename === fname);
371
+ if (index < 0) return false;
372
+ entries.splice(index, 1);
373
+ return true;
374
+ }
375
+
376
+ /**
377
+ * Parses local header and returns entry data
378
+ * @param entry - ZIP entry to parse
379
+ * @param withLocalHdrData - Include header in returned data
380
+ * @returns Buffer containing entry data
381
+ * @throws Error if header is invalid
382
+ */
383
+ parseLocalHeader(entry: ZipEntry, withLocalHdrData?: boolean): Buffer {
384
+ const buffer = this.ensureBuffer();
385
+ const localData = buffer.subarray(entry.localHdrOffset);
386
+
387
+ // data should be 30 bytes and start with "PK 03 04"
388
+ if (localData.readUInt32LE(0) !== LOCAL_HDR.SIGNATURE) {
389
+ throw new Error(Errors. INVALID_CEN);
390
+ }
391
+
392
+ // Check for Data Descriptor Flag
393
+ let _encryptLen = 0;
394
+ const _bitFlags = localData.readUInt16LE(LOCAL_HDR.FLAGS);
395
+ if (_bitFlags & GP_FLAG.ENCRYPTED) {
396
+ _encryptLen = ENCRYPT_HDR_SIZE;
397
+ }
398
+ if (_bitFlags & GP_FLAG.DATA_DESC) {
399
+ entry.bitFlags |= GP_FLAG.DATA_DESC;
400
+ entry.lastModTimeDate = localData.readUInt32LE(LOCAL_HDR.TIMEDATE_DOS);
401
+ }
402
+
403
+ let _fnameLen = localData.readUInt16LE(LOCAL_HDR.FNAME_LEN);
404
+ let _extraLen = localData.readUInt16LE(LOCAL_HDR.EXTRA_LEN);
405
+ let _localSize = LOCAL_HDR.SIZE + _fnameLen + _extraLen;
406
+ if (_encryptLen != 0) {
407
+ entry.encryptHdr = Buffer.from(localData.subarray(_localSize, _localSize + _encryptLen));
408
+ }
409
+
410
+ if (withLocalHdrData)
411
+ return localData.subarray(0, _localSize + entry.compressedSize);
412
+
413
+ return localData.subarray(_localSize + _encryptLen, _localSize + entry.compressedSize);
414
+ }
415
+
416
+ /**
417
+ * Copies an entry's data from source ZIP
418
+ * @param entry - Entry to copy
419
+ * @returns Buffer containing entry data with headers
420
+ */
421
+ copyEntry(entry: ZipEntry): Buffer {
422
+ return this.parseLocalHeader(entry, true);
423
+ }
424
+
425
+ /**
426
+ * Compresses data for a ZIP entry
427
+ * @param entry - ZIP entry to compress
428
+ * @param data - Data to compress
429
+ * @param options - Compression options
430
+ * @returns Buffer containing compressed data
431
+ */
432
+ async compressData(entry: ZipEntry, data: Buffer, options?: CompressOptions) : Promise<Buffer> {
433
+ // Initialize with empty buffer instead of null
434
+ let buffer: Buffer = Buffer.alloc(0);
435
+
436
+ // Determine compression method based on options
437
+ if (options?.level === 0) {
438
+ // Level 0 means "store" (no compression)
439
+ entry.cmpMethod = CMP_METHOD.STORED;
440
+ buffer = data;
441
+ } else if (options?.useZstd) {
442
+ // Use Zstandard compression
443
+ if (data.length < 100) {
444
+ // ZStd requires at least 100 bytes, store it without compression
445
+ entry.cmpMethod = CMP_METHOD.STORED;
446
+ buffer = data;
447
+ } else {
448
+ entry.cmpMethod = CMP_METHOD.ZSTD;
449
+ buffer = await this.zstdCompress(data, options);
450
+ }
451
+ } else {
452
+ // Default to deflate
453
+ entry.cmpMethod = CMP_METHOD.DEFLATED;
454
+ buffer = this.deflate(data, options);
455
+ }
456
+
457
+ entry.crc = crc32(data);
458
+ // console.log('CRC: '+ sprintf("%08x", entry.crc));
459
+
460
+ if (options?.useSHA256) {
461
+ entry.sha256 = sha256(data);
462
+ console.log('SHA256: ', entry.sha256);
463
+ };
464
+
465
+ if (options?.password) {
466
+ entry.bitFlags = GP_FLAG.ENCRYPTED;
467
+ const _check = (entry.crc >> 24) & 0xff;
468
+ const encryptedBuffer = encrypter(buffer, _check, options.password);
469
+ // Handle potential null return from encrypter
470
+ if (!encryptedBuffer) {
471
+ throw new Error(Errors.ENCRYPT_FAILED);
472
+ }
473
+ buffer = encryptedBuffer;
474
+ }
475
+ entry.compressedSize = buffer.length;
476
+
477
+ return buffer;
478
+ }
479
+
480
+ /**
481
+ * Compresses data using deflate algorithm
482
+ * @param inbuf - Data to compress
483
+ * @param options - Compression options
484
+ * @returns Compressed data buffer
485
+ */
486
+ private deflate(inbuf: Buffer, options?: CompressOptions): Buffer {
487
+ if (options?.level == 0) {
488
+ return inbuf; // Store without compression
489
+ }
490
+ try {
491
+ const level = options?.level as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | undefined;
492
+ const result = pako.deflateRaw(inbuf, {
493
+ level: level ?? 6
494
+ });
495
+ return Buffer.from(result.buffer, result.byteOffset, result.byteLength);
496
+ } catch (e) {
497
+ console.error('Error during compression:', e);
498
+ throw new Error(Errors.COMPRESSION_ERROR);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Compresses data using Zstandard (zstd) algorithm
504
+ * @param inbuf - Data to compress
505
+ * @param options - Compression options
506
+ * @returns Compressed data buffer
507
+ */
508
+ private async zstdCompress(inbuf: Buffer, options?: CompressOptions): Promise<Buffer> {
509
+ if (options?.level == 0) {
510
+ return inbuf; // Store without compression
511
+ }
512
+ try {
513
+ // Ensure zstd is initialized
514
+ const codec = await initZstd();
515
+
516
+ // Zstd compression levels range from 1 (fastest) to 22 (highest compression)
517
+ // Map our 1-9 level to a reasonable zstd range (1-19)
518
+ const level = options?.level ?? 6;
519
+ const zstdLevel = Math.min(Math.max(1, Math.floor(level * 2.1)), 19);
520
+
521
+ // Compress the data with zstd
522
+ const compressedData = codec.ZstdSimple.compress(inbuf, zstdLevel);
523
+ return Buffer.from(compressedData);
524
+ } catch (e) {
525
+ console.error('Error during zstd compression:', e);
526
+ throw new Error(Errors.COMPRESSION_ERROR);
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Decompresses data using Zstandard (zstd) algorithm
532
+ * @param inbuf - Compressed data buffer
533
+ * @returns Decompressed data buffer
534
+ */
535
+ async zstdDecompress(inbuf: Buffer): Promise<Buffer> {
536
+ try {
537
+ // Ensure zstd is initialized
538
+ const codec = await initZstd();
539
+
540
+ const decompressedData = codec.ZstdSimple.decompress(inbuf);
541
+ return Buffer.from(decompressedData);
542
+ } catch (e) {
543
+ console.error('Error during zstd decompression:', e);
544
+ throw new Error(Errors.DECOMPRESSION_ERROR);
545
+ }
546
+ }
547
+
548
+ // ======================================
549
+ // deflateAsync(inbuf: Buffer, callback: (result: Buffer) => void) {
550
+ // var opts = { chunkSize: ((inbuf.length >> 10) + 1) * 1024 };
551
+
552
+ // var tmp = zlib.createDeflateRaw(opts),
553
+ // parts = [],
554
+ // total = 0;
555
+ // tmp.on("data", function (data) {
556
+ // parts.push(data);
557
+ // total += data.length;
558
+ // });
559
+ // tmp.on("end", function () {
560
+ // var buf = Buffer.alloc(total),
561
+ // written = 0;
562
+ // buf.fill(0);
563
+ // for (var i = 0; i < parts.length; i++) {
564
+ // var part = parts[i];
565
+ // part.copy(buf, written);
566
+ // written += part.length;
567
+ // }
568
+ // callback && callback(buf);
569
+ // });
570
+ // tmp.end(inbuf);
571
+ // }
572
+
573
+ /**
574
+ * Creates new Zip data file with the passed ZipEntries
575
+ * @param cmpOptions - Compression options
576
+ * @param zipOptions - Create ZIP options
577
+ * @returns Promise<number> - The offset of the last entry
578
+ */
579
+ async createZipFileData(cmpOptions?: CompressOptions, zipOptions?: CreateZipOptions): Promise<number> {
580
+ const _onError = zipOptions?.onError || ((error: Error) => { throw error; });
581
+ const _onEntryDone = zipOptions?.onEntryDone || (() => {});
582
+ const _onReadEntryData = zipOptions?.onReadEntryData ||
583
+ (() => { throw new Error('onReadEntryData callback is required'); });
584
+ const _onOutputBuffer = zipOptions?.onOutputBuffer || (() => {});
585
+
586
+ if (this.zipEntries.length === 0) {
587
+ _onError(new Error(Errors.NO_FILES));
588
+ return 0; // Return 0 instead of undefined
589
+ }
590
+
591
+ // console.log('Creating Zip File Data with ZipEntries: ', this.zipEntries);
592
+ let offset = 0;
593
+ let entryCnt = this.zipEntries.length;
594
+ for (let index = 0; index < entryCnt; index++) {
595
+ const entry = this.zipEntries[index];
596
+
597
+ // Remove any existing Timestamp Metadata entries
598
+ if (entry.filename === TIMESTAMP_METADATA || entry.filename === TIMESTAMP_SUBMITTED) {
599
+ this.zipEntries.splice(index, 1);
600
+ entryCnt--;
601
+ index--;
602
+ console.log('Deleting Timestamp Metadata entry: ', entry.filename);
603
+ continue;
604
+ }
605
+
606
+ if (!entry.isUpdated) {
607
+ console.log('Copying Entry: ', entry.filename);
608
+ const inData = this.copyEntry(entry);
609
+ entry.localHdrOffset = offset
610
+ offset += inData.length;
611
+ _onOutputBuffer(inData);
612
+ _onEntryDone(entry, 'Copied ');
613
+ continue;
614
+ }
615
+
616
+ // Compress the file data to set all zipEntry fields correctly
617
+ const fileBuffer = _onReadEntryData(entry);
618
+ const cmpData = await this.compressData(entry, fileBuffer, cmpOptions);
619
+
620
+ // Create a local header data buffer
621
+ const localHdr = entry.createLocalHdr();
622
+
623
+ // Save local header offset for entry and calculate next offset
624
+ entry.localHdrOffset = offset;
625
+
626
+ offset += localHdr.length + cmpData.length;
627
+
628
+ // Write the data to the output file
629
+ _onOutputBuffer(localHdr);
630
+ _onOutputBuffer(cmpData);
631
+ _onEntryDone(entry, 'Written');
632
+ }
633
+
634
+ return offset;
635
+ }
636
+
637
+ /**
638
+ * Finishes Zip data file by adding any Metadata entries and the Central End Directory
639
+ * @param offset - The current offset in the zip data file
640
+ * @param onOutputBuffer - The callback function to output the data buffer
641
+ * @param metaEntry - The Metadata ZipEntry to add to the Zip file
642
+ */
643
+ async finishZipFileData(offset: number, onOutputBuffer: (data: Buffer) => void, metaEntry?: ZipEntry) {
644
+ if (metaEntry && metaEntry.fileBuffer) {
645
+ const localHdr = metaEntry.createLocalHdr();
646
+ metaEntry.localHdrOffset = offset;
647
+ offset += localHdr.length + metaEntry.fileBuffer.length;
648
+
649
+ onOutputBuffer(localHdr);
650
+ onOutputBuffer(metaEntry.fileBuffer);
651
+ }
652
+
653
+ // Output ZipEntries as Central End Directory
654
+ const centralDirOffset = offset;
655
+ for (const entry of this.zipEntries) {
656
+ const centralDirEntry = entry.centralDirEntry();
657
+ offset += centralDirEntry.length;
658
+ onOutputBuffer(centralDirEntry);
659
+ }
660
+
661
+ const centralDirSize = offset - centralDirOffset;
662
+ const ceBuf = this.centralEndHdr(centralDirSize, centralDirOffset);
663
+ onOutputBuffer(ceBuf);
664
+ }
665
+
666
+ /**
667
+ * Unzips the data buffer using pako inflate method
668
+ * @param inbuf - Data buffer to inflate
669
+ * @returns Inflated data buffer
670
+ * @throws Error if extraction fails
671
+ */
672
+ inflate(inbuf: Buffer): Buffer {
673
+ const result = pako.inflateRaw(inbuf);
674
+ return Buffer.from(result.buffer, result.byteOffset, result.byteLength);
675
+ }
676
+
677
+ /**
678
+ * Extracts the data from the ZipEntry and returns the data as a Buffer
679
+ * @param entry - ZIP entry to extract
680
+ * @param skipHashCheck - Whether to skip hash checking
681
+ * @returns Buffer containing extracted data or null if extraction fails
682
+ */
683
+ async extract(entry: ZipEntry, skipHashCheck?: boolean): Promise<Buffer | null> {
684
+ const fdata = this.parseLocalHeader(entry);
685
+ if (fdata.length == 0) {
686
+ return null;
687
+ }
688
+
689
+ let outBuf = null;
690
+ switch (entry.cmpMethod) {
691
+ case CMP_METHOD.STORED:
692
+ outBuf = fdata;
693
+ break;
694
+ case CMP_METHOD.DEFLATED:
695
+ outBuf = this.inflate(fdata);
696
+ break;
697
+ case CMP_METHOD.ZSTD:
698
+ outBuf = await this.zstdDecompress(fdata);
699
+ break;
700
+ default:
701
+ throw new Error(Errors.UNKNOWN_METHOD);
702
+ }
703
+
704
+ // If no hash checking is requested, return the unmodified data
705
+ if (skipHashCheck) {
706
+ return outBuf;
707
+ }
708
+
709
+ // Verify integrity - use SHA256 if available, otherwise CRC32
710
+ if (entry.sha256) {
711
+ if (!this.testSHA256(entry, outBuf)) {
712
+ throw new Error(Errors.INVALID_SHA256);
713
+ }
714
+ } else {
715
+ if (!this.testCRC32(entry, outBuf)) {
716
+ throw new Error(Errors.INVALID_CRC);
717
+ }
718
+ }
719
+
720
+ return outBuf;
721
+ }
722
+
723
+ /**
724
+ * Decrypts the entry data
725
+ * @param entry - ZIP entry to decrypt
726
+ * @param password - Password for decryption
727
+ * @returns Decrypted data buffer or null if decryption fails
728
+ */
729
+ decryptEntry(entry: ZipEntry, password: string): Buffer | null {
730
+ if (!entry.isEncrypted) return null;
731
+ if (entry.isStrongEncrypt)
732
+ throw new Error(Errors.STRONG_ENCRYPT_UNSUPPORTED);
733
+ if (!password) return null;
734
+
735
+ // Read the encryption header at start of data
736
+ let _localData = this.parseLocalHeader(entry);
737
+ const _decrypt = decrypter(password);
738
+
739
+ const _encHdr = entry.encryptHdr;
740
+ if (!_encHdr) return null;
741
+
742
+ const decryptedHeader = _decrypt(_encHdr);
743
+
744
+ const _check = (entry.bitFlags & GP_FLAG.DATA_DESC) ?
745
+ (entry.lastModTimeDate >> 8) & 0xff :
746
+ ((entry.crc >> 24) & 0xff);
747
+
748
+ if (decryptedHeader[11] !== _check)
749
+ return null;
750
+
751
+ _localData = _decrypt(_localData);
752
+ return _localData;
753
+ }
754
+
755
+ /**
756
+ * Checks if a filename is a blockchain metadata file that should be excluded from Merkle Root calculation
757
+ * @param filename - The filename to check
758
+ * @returns boolean - True if the file is a metadata file
759
+ */
760
+ private isMetadataFile(filename: string): boolean {
761
+ return filename === TIMESTAMP_SUBMITTED ||
762
+ filename === TIMESTAMP_METADATA ||
763
+ filename === TOKENIZED_METADATA;
764
+ }
765
+
766
+ /**
767
+ * Calculates the Merkle Root of the ZIP file
768
+ * Excludes blockchain metadata files (META-INF/*) to ensure consistent calculation
769
+ * @returns string - The Merkle Root of the ZIP file
770
+ */
771
+ getMerkleRoot(): string | null {
772
+ const zipEntries = this.getDirectory();
773
+ if (zipEntries.length === 0) return null;
774
+
775
+ let _hashAccumulator = new HashAccumulater();
776
+
777
+ // Filter out blockchain metadata files to ensure consistent Merkle Root calculation
778
+ const contentEntries = zipEntries.filter(entry => !this.isMetadataFile(entry.filename));
779
+
780
+ for (const entry of contentEntries) {
781
+ if (entry.sha256) {
782
+ _hashAccumulator.addHash(entry.sha256);
783
+ }
784
+ }
785
+
786
+ const merkleRoot = _hashAccumulator.merkleRoot();
787
+ if (!merkleRoot) return null;
788
+
789
+ return merkleRoot;
790
+ }
791
+
792
+ /**
793
+ * Checks if a ZIP file contains valid timestamp metadata
794
+ * @returns { merkleRoot: string | null, ots: Buffer | null }
795
+ * Returns the merkle root hash and timestamp metadata if found, null otherwise
796
+ */
797
+ checkForTimestamp(): string | null {
798
+ const zipEntries = this.getDirectory();
799
+ if (zipEntries.length === 0) return null;
800
+
801
+ this.stampEntry = null;
802
+ let _hashAccumulator = new HashAccumulater();
803
+ for (const entry of zipEntries) {
804
+ if (entry.filename === TIMESTAMP_METADATA) {
805
+ this.stampEntry = entry;
806
+ } else if (entry.filename === TIMESTAMP_SUBMITTED) {
807
+ if (this.stampEntry == null)
808
+ this.stampEntry = entry;
809
+ } else if (!this.isMetadataFile(entry.filename) && entry.sha256) {
810
+ // Only include content files (non-metadata) in Merkle Root calculation
811
+ _hashAccumulator.addHash(entry.sha256);
812
+ }
813
+ }
814
+ if (!this.stampEntry) return null;
815
+
816
+ const merkleRoot = _hashAccumulator.merkleRoot();
817
+ return merkleRoot;
818
+ }
819
+
820
+ /**
821
+ * Gets the OTS from the timestamp metadata entry
822
+ * @returns Promise<Buffer | null> - The OTS from the timestamp metadata entry or null if not found
823
+ */
824
+ async getOts(): Promise<Buffer | null> {
825
+ if (!this.stampEntry) return null;
826
+ const ots = await this.extract(this.stampEntry);
827
+ return ots;
828
+ }
829
+
830
+ /**
831
+ * Creates a new timestamp metadata entry for the ZIP file
832
+ * Calculates merkle root from file hashes and obtains blockchain timestamp
833
+ * @returns Promise<ZipEntry | null> The created timestamp metadata entry or null if failed
834
+ */
835
+ createTimestampMetadataEntry(ots: Buffer | null): ZipEntry | null {
836
+ if (!ots) return null;
837
+ const tsEntry = this.createZipEntry(TIMESTAMP_SUBMITTED);
838
+ tsEntry.crc = crc32(ots);
839
+ tsEntry.uncompressedSize = ots.length;
840
+ tsEntry.compressedSize = ots.length;
841
+ tsEntry.cmpMethod = CMP_METHOD.STORED;
842
+ tsEntry.fileBuffer = ots;
843
+ tsEntry.isMetaData = true;
844
+
845
+ return tsEntry;
846
+ }
847
+
848
+ /**
849
+ * Sets the ZIP file comment
850
+ * @param msg - Comment to set
851
+ */
852
+ setZipComment(msg: string | null) {
853
+ this.zipComment = msg;
854
+ }
855
+ /**
856
+ * Gets the ZIP file comment
857
+ * @returns string | null - The ZIP file comment
858
+ */
859
+ getZipComment(): string | null {
860
+ return this.zipComment;
861
+ }
862
+
863
+ /**
864
+ * Converts the compression method to a string
865
+ * @param cmpMethod - Compression method number
866
+ * @returns string - Compression method as a string
867
+ */
868
+ cmpMethodToString(cmpMethod: number): string {
869
+ switch (cmpMethod) {
870
+ case 0: return 'Stored';
871
+ case 8: return 'Deflate';
872
+ case 93: return 'Zstd';
873
+ default: return 'Unknown';
874
+ }
875
+ }
876
+
877
+ /**
878
+ * Ensures ZIP entries are loaded
879
+ * @returns Array of ZIP entries
880
+ * @throws Error if no ZIP entries are loaded
881
+ */
882
+ private ensureZipEntries(): ZipEntry[] {
883
+ if (this.zipEntries.length === 0) throw new Error('No ZIP entries loaded');
884
+ return this.zipEntries;
885
+ }
886
+
887
+ /**
888
+ * Ensures ZIP data buffer is loaded
889
+ * @returns Buffer containing ZIP data
890
+ * @throws Error if no ZIP data is loaded
891
+ */
892
+ private ensureBuffer(): Buffer {
893
+ if (!this.inBuffer) {
894
+ // Instead of throwing an error, return an empty buffer
895
+ this.inBuffer = Buffer.alloc(0);
896
+ }
897
+ return this.inBuffer;
898
+ }
899
+ }