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,686 @@
1
+ "use strict";
2
+ // ======================================
3
+ // ZipEntry.ts
4
+ // Copyright (c) 2025 NeoWare, Inc. All rights reserved.
5
+ // ======================================
6
+ // Zip Directory Item class
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ const Errors_1 = __importDefault(require("./constants/Errors"));
12
+ const Headers_1 = require("./constants/Headers");
13
+ const Util_1 = require("./components/Util");
14
+ const VER_ENCODING = 30;
15
+ const VER_EXTRACT = 10; // Version needed to extract (1.0)
16
+ /**
17
+ * Class representing a single entry (file or directory) within a ZIP archive
18
+ */
19
+ class ZipEntry {
20
+ /**
21
+ * Creates a new ZIP entry
22
+ * @param fname - Name of the file within the ZIP
23
+ * @param comment - Optional comment for this entry
24
+ * @param debug - Enable debug logging
25
+ */
26
+ constructor(fname, comment, debug) {
27
+ this.debug = true;
28
+ this.verMadeBy = 0; // Read Version Made By
29
+ this.verExtract = 0; // Read Version Needed to Extract
30
+ this.bitFlags = 0; // General purpose bit flag
31
+ this.cmpMethod = 0; // Compression method
32
+ this.timeDateDOS = 0; // DOS File Time(2 bytes) & Date(2 bytes)
33
+ this.crc = 0; // CRC-32
34
+ this.compressedSize = 0; // Compressed size
35
+ this.uncompressedSize = 0; // Uncompressed size
36
+ this.volNumber = 0; // Disk number start
37
+ this.intFileAttr = 0; // Internal file attributes
38
+ this.extFileAttr = 0; // External file attributes
39
+ this.localHdrOffset = 0; // Relative offset to local header from File/Disk start
40
+ this.filename = ''; // File name
41
+ this.extraField = null; // Extra field
42
+ this.comment = null; // Entry comment
43
+ // File Data
44
+ this.fileBuffer = null; // File Data Buffer
45
+ // Zip Compressed Data
46
+ this.cmpData = null; // Compressed Data Buffer
47
+ this.isEncrypted = false; // Zip Entry is encrypted
48
+ this.isStrongEncrypt = false; // Zip Entry is strong encrypted
49
+ this.encryptHdr = null; // Encrypted Header (12 bytes)
50
+ this.lastModTimeDate = 0; // Data Descriptor File Time & Date
51
+ this.decrypt = null; // Decrypt Class Function
52
+ this.isUpdated = true; // Entry has been updated
53
+ this.isDirectory = false; // Entry is a directory
54
+ this.isMetaData = false; // Entry is Zip MetaData
55
+ // Platform specific data
56
+ this.platform = null; // Platform
57
+ this.universalTime = null; // Universal Time
58
+ this.uid = null; // User ID
59
+ this.gid = null; // Group ID
60
+ this.sha256 = null; // SHA-256 hash of the file
61
+ this.isMSDOS = this.platform === null;
62
+ this.isUnixLike = this.platform != null && this.platform !== 'win32';
63
+ this.isMacOS = this.platform != null && this.platform === 'darwin';
64
+ this.isLinux = this.platform != null && this.platform === 'linux';
65
+ this.VER_MADE_BY = (() => {
66
+ switch (this.platform) {
67
+ case 'darwin':
68
+ return (Headers_1.FILE_SYSTEM.DARWIN << 8) | VER_ENCODING; // macOS/Darwin
69
+ case 'win32':
70
+ return (Headers_1.FILE_SYSTEM.NTFS << 8) | VER_ENCODING; // Windows
71
+ default:
72
+ return (Headers_1.FILE_SYSTEM.UNIX << 8) | VER_ENCODING; // Unix/Linux
73
+ }
74
+ })();
75
+ this.filename = fname || '';
76
+ this.comment = comment || null;
77
+ this.debug = debug || false;
78
+ // Set the UTF-8 Language Encoding Flag (EFS) to indicate UTF-8 encoding for filenames
79
+ this.bitFlags |= Headers_1.GP_FLAG.EFS;
80
+ }
81
+ /**
82
+ * Reads ZIP entry data from a central directory buffer
83
+ * @param data - Buffer containing central directory entry data
84
+ * @returns Buffer positioned at start of next entry
85
+ * @throws Error if central directory entry is invalid
86
+ */
87
+ readZipEntry(data) {
88
+ // Check if buffer is too small before trying to read from it
89
+ if (data.length < Headers_1.CENTRAL_DIR.SIZE) {
90
+ throw new Error('Zip entry data is too small or corrupt');
91
+ }
92
+ // Verify this is a Central Directory Header
93
+ // data should be 46 bytes and start with "PK 01 02"
94
+ if (data.readUInt32LE(0) !== Headers_1.CENTRAL_DIR.SIGNATURE) {
95
+ throw new Error(Errors_1.default.INVALID_CEN);
96
+ }
97
+ // Read Zip version made by
98
+ this.verMadeBy = data.readUInt16LE(Headers_1.CENTRAL_DIR.VER_MADE);
99
+ // Read Zip version needed to extract
100
+ this.verExtract = data.readUInt16LE(Headers_1.CENTRAL_DIR.VER_EXT);
101
+ // encrypt, decrypt flags
102
+ this.bitFlags = data.readUInt16LE(Headers_1.CENTRAL_DIR.FLAGS);
103
+ // Test if Zip Entry is encrypted
104
+ if ((this.bitFlags & Headers_1.GP_FLAG.ENCRYPTED) != 0) {
105
+ this.isEncrypted = true;
106
+ if ((this.bitFlags & Headers_1.GP_FLAG.STRONG_ENCRYPT) != 0)
107
+ this.isStrongEncrypt = true;
108
+ }
109
+ // compression method
110
+ this.cmpMethod = data.readUInt16LE(Headers_1.CENTRAL_DIR.CMP_METHOD);
111
+ // modification time (2 bytes time, 2 bytes date)
112
+ this.timeDateDOS = data.readUInt32LE(Headers_1.CENTRAL_DIR.TIMEDATE_DOS);
113
+ // uncompressed file crc-32 value
114
+ this.crc = data.readUInt32LE(Headers_1.CENTRAL_DIR.CRC);
115
+ // compressed size
116
+ this.compressedSize = data.readUInt32LE(Headers_1.CENTRAL_DIR.CMP_SIZE);
117
+ // uncompressed size
118
+ this.uncompressedSize = data.readUInt32LE(Headers_1.CENTRAL_DIR.UNCMP_SIZE);
119
+ // volume number start
120
+ this.volNumber = data.readUInt16LE(Headers_1.CENTRAL_DIR.DISK_NUM);
121
+ // internal file attributes
122
+ this.intFileAttr = data.readUInt16LE(Headers_1.CENTRAL_DIR.INT_FILE_ATTR);
123
+ // external file attributes
124
+ this.extFileAttr = data.readUInt32LE(Headers_1.CENTRAL_DIR.EXT_FILE_ATTR);
125
+ if (this.extFileAttr & Headers_1.DOS_FILE_ATTR.DIRECTORY)
126
+ this.isDirectory = true;
127
+ // LOC header offset
128
+ this.localHdrOffset = data.readUInt32LE(Headers_1.CENTRAL_DIR.LOCAL_HDR_OFFSET);
129
+ // Filename Length - 2 bytes
130
+ let fnameLen = data.readUInt16LE(Headers_1.CENTRAL_DIR.FNAME_LEN);
131
+ const filename = data.toString('utf8', Headers_1.CENTRAL_DIR.SIZE, Headers_1.CENTRAL_DIR.SIZE + fnameLen);
132
+ this.filename = filename;
133
+ if (this.filename.endsWith('/'))
134
+ this.isDirectory = true;
135
+ // Extra Field Length - 2 bytes
136
+ let extraLen = data.readUInt16LE(Headers_1.CENTRAL_DIR.EXTRA_LEN);
137
+ if (extraLen > 0) {
138
+ this.extraField = data.subarray(Headers_1.CENTRAL_DIR.SIZE + fnameLen, Headers_1.CENTRAL_DIR.SIZE + fnameLen + extraLen);
139
+ // First pass: Check for Unicode Path to ensure correct filename before processing other fields
140
+ for (let i = 0; i < extraLen;) {
141
+ let _id = this.extraField.readUInt16LE(i);
142
+ let _len = this.extraField.readUInt16LE(i + 2);
143
+ let _data = this.extraField.subarray(i + 4, i + 4 + _len);
144
+ if (_id === Headers_1.HDR_ID.UNICODE_PATH && _len >= 5) {
145
+ // Unicode Path Extra Field
146
+ const version = _data.readUInt8(0);
147
+ const nameCrc32 = _data.readUInt32LE(1);
148
+ // Calculate CRC32 of the current filename
149
+ const fnameBuf = Buffer.from(this.filename);
150
+ const calculatedCrc = (0, Util_1.crc32)(fnameBuf);
151
+ // If CRCs match, use the UTF-8 filename from the extra field
152
+ if (calculatedCrc === nameCrc32) {
153
+ const unicodeName = _data.subarray(5).toString('utf8');
154
+ this.filename = unicodeName;
155
+ if (this.debug) {
156
+ console.log(`Using Unicode Path: ${this.filename}`);
157
+ }
158
+ }
159
+ }
160
+ i += 4 + _len;
161
+ }
162
+ // Second pass: Process all other extra fields
163
+ for (let i = 0; i < extraLen;) {
164
+ let _id = this.extraField.readUInt16LE(i);
165
+ let _len = this.extraField.readUInt16LE(i + 2);
166
+ let _data = this.extraField.subarray(i + 4, i + 4 + _len);
167
+ if (_id === Headers_1.HDR_ID.SHA256) {
168
+ if (_len === 64)
169
+ // Early versions of NeoZip used a UTF-8 encoded string
170
+ this.sha256 = _data.toString('utf8');
171
+ else
172
+ this.sha256 = _data.toString('hex');
173
+ }
174
+ else if (_id === Headers_1.HDR_ID.UNV_TIME) {
175
+ // Universal Time field has a flag byte followed by a 4-byte timestamp
176
+ if (_len >= 5) {
177
+ const flags = _data.readUInt8(0);
178
+ // Check if modification time is present (bit 0)
179
+ if (flags & 0x01) {
180
+ this.universalTime = _data.readUInt32LE(1);
181
+ }
182
+ }
183
+ }
184
+ else if (_id === Headers_1.HDR_ID.UID_GID) {
185
+ // Extract UID/GID if present
186
+ if (_len >= 5) { // Version + UID size + UID + GID size + GID
187
+ const version = _data.readUInt8(0);
188
+ const uidSize = _data.readUInt8(1);
189
+ if (uidSize <= 4 && _len >= 2 + uidSize) {
190
+ // Read UID based on its size (1-4 bytes)
191
+ this.uid = _data.readUIntLE(2, uidSize);
192
+ // Check if GID is also present
193
+ if (_len >= 2 + uidSize + 1) {
194
+ const gidSize = _data.readUInt8(2 + uidSize);
195
+ if (gidSize <= 4 && _len >= 2 + uidSize + 1 + gidSize) {
196
+ this.gid = _data.readUIntLE(2 + uidSize + 1, gidSize);
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ // Skip Unicode Path here as we already processed it
203
+ i += 4 + _len;
204
+ }
205
+ }
206
+ // File Comment Length - 2 bytes
207
+ let comLen = data.readUInt16LE(Headers_1.CENTRAL_DIR.COMMENT_LEN);
208
+ if (comLen > 0)
209
+ this.comment = data.toString('utf8', Headers_1.CENTRAL_DIR.SIZE + fnameLen + extraLen, Headers_1.CENTRAL_DIR.SIZE + fnameLen + extraLen + comLen);
210
+ if (this.debug)
211
+ this.showVerboseInfo();
212
+ // Calculate the Buffer for the next entry
213
+ let rawSize = Headers_1.CENTRAL_DIR.SIZE + fnameLen + extraLen + comLen;
214
+ return data.subarray(rawSize);
215
+ }
216
+ /**
217
+ * Checks if the filename contains characters that require Unicode handling
218
+ * @returns true if the filename contains non-ASCII characters or special characters
219
+ */
220
+ needsUnicodeHandling() {
221
+ // Check if filename contains non-ASCII characters or special characters like apostrophes
222
+ return /[^\x00-\x7E]|['"]/.test(this.filename);
223
+ }
224
+ /**
225
+ * Adds UTF-8 Unicode Path field to a buffer
226
+ * @param buffer - The buffer to write to
227
+ * @param offset - The offset in the buffer to start writing
228
+ * @returns The new offset after writing
229
+ */
230
+ addUnicodePathField(buffer, offset) {
231
+ // Create a UTF-8 buffer of the filename
232
+ const unicodePathBuf = Buffer.from(this.filename, 'utf8');
233
+ // Calculate CRC32 of the ASCII version of the filename
234
+ // Create an ASCII version by replacing non-ASCII chars with '?'
235
+ const asciiName = this.filename.replace(/[^\x00-\x7E]/g, '?');
236
+ const asciiNameBuf = Buffer.from(asciiName, 'ascii');
237
+ const nameCrc32 = (0, Util_1.crc32)(asciiNameBuf);
238
+ // 1 byte version + 4 bytes CRC + filename
239
+ const unicodePathLen = 5 + unicodePathBuf.length;
240
+ // Write Unicode Path Extra Field (0x7075)
241
+ buffer.writeUInt16LE(Headers_1.HDR_ID.UNICODE_PATH, offset); // "up" header ID
242
+ buffer.writeUInt16LE(unicodePathLen, offset + 2); // data length
243
+ buffer.writeUInt8(1, offset + 4); // version (1)
244
+ buffer.writeUInt32LE(nameCrc32, offset + 5); // CRC-32 of standard filename
245
+ unicodePathBuf.copy(buffer, offset + 9); // UTF-8 version of filename
246
+ return offset + 4 + unicodePathLen; // 4 bytes for header + data length
247
+ }
248
+ /**
249
+ * Creates a local header for this ZIP entry
250
+ * @returns Buffer containing the local header data
251
+ */
252
+ createLocalHdr() {
253
+ const commentLen = this.comment?.length ?? 0;
254
+ let extraFieldLen = 0;
255
+ // Only create Unicode Path field if needed
256
+ const needsUnicode = this.needsUnicodeHandling();
257
+ if (needsUnicode) {
258
+ // 1 byte version + 4 bytes CRC + filename + 4 bytes header
259
+ const unicodeNameLen = Buffer.from(this.filename, 'utf8').length;
260
+ extraFieldLen = 5 + unicodeNameLen + 4;
261
+ }
262
+ const data = Buffer.alloc(Headers_1.LOCAL_HDR.SIZE + this.filename.length + commentLen + extraFieldLen);
263
+ // "PK\003\004"
264
+ data.writeUInt32LE(Headers_1.LOCAL_HDR.SIGNATURE, 0);
265
+ // version needed to extract
266
+ data.writeUInt16LE(VER_EXTRACT, Headers_1.LOCAL_HDR.VER_EXTRACT);
267
+ // general purpose bit flag
268
+ data.writeUInt16LE(this.bitFlags >>> 0, Headers_1.LOCAL_HDR.FLAGS);
269
+ // compression method
270
+ data.writeUInt16LE(this.cmpMethod, Headers_1.LOCAL_HDR.COMPRESSION);
271
+ // modification time (2 bytes time, 2 bytes date)
272
+ data.writeUInt32LE(this.timeDateDOS >>> 0, Headers_1.LOCAL_HDR.TIMEDATE_DOS);
273
+ // uncompressed file crc-32 value
274
+ data.writeUInt32LE(this.crc, Headers_1.LOCAL_HDR.CRC);
275
+ // compressed size
276
+ data.writeUInt32LE(this.compressedSize, Headers_1.LOCAL_HDR.CMP_SIZE);
277
+ // uncompressed size
278
+ data.writeUInt32LE(this.uncompressedSize, Headers_1.LOCAL_HDR.UNCMP_SIZE);
279
+ // filename length
280
+ data.writeUInt16LE(this.filename.length, Headers_1.LOCAL_HDR.FNAME_LEN);
281
+ // extra field length
282
+ data.writeUInt16LE(extraFieldLen, Headers_1.LOCAL_HDR.EXTRA_LEN);
283
+ // Write filename - use ASCII filename (replacing non-ASCII with ?)
284
+ // This ensures compatibility with older ZIP readers
285
+ const asciiName = this.filename.replace(/[^\x00-\x7E]/g, '?');
286
+ const fnameBuf = Buffer.from(asciiName);
287
+ fnameBuf.copy(data, Headers_1.LOCAL_HDR.SIZE);
288
+ let extraOffset = Headers_1.LOCAL_HDR.SIZE + fnameBuf.length;
289
+ // Add Unicode Path Extra Field only if needed
290
+ if (needsUnicode) {
291
+ extraOffset = this.addUnicodePathField(data, extraOffset);
292
+ }
293
+ // Add comment if present (after extra field)
294
+ if (commentLen > 0 && this.comment) {
295
+ const commentBuf = Buffer.from(this.comment, 'utf8');
296
+ commentBuf.copy(data, extraOffset);
297
+ }
298
+ return data;
299
+ }
300
+ /**
301
+ * Creates a central directory entry for this ZIP entry
302
+ * @returns Buffer containing the central directory entry data
303
+ */
304
+ centralDirEntry() {
305
+ // Calculate the length of the extra fields
306
+ const commentLen = this.comment?.length ?? 0;
307
+ const utfLen = this.universalTime ? 9 : 0; // 4 bytes header + 5 bytes data
308
+ const uidgidLen = this.uid && this.gid ? 11 + 4 : 0; // 1 byte version + 1 byte size + 4 bytes data + 1 byte size + 4 bytes data
309
+ const sha256Buf = this.sha256 ? Buffer.from(this.sha256, 'hex') : null;
310
+ const sha256Len = sha256Buf ? (sha256Buf.length + 4) : 0;
311
+ // Only add Unicode Path field if needed
312
+ const needsUnicode = this.needsUnicodeHandling();
313
+ let unicodePathLen = 0;
314
+ if (needsUnicode) {
315
+ // 1 byte version + 4 bytes CRC + filename + 4 bytes header
316
+ const unicodeNameLen = Buffer.from(this.filename, 'utf8').length;
317
+ unicodePathLen = 5 + unicodeNameLen + 4;
318
+ }
319
+ const extraLen = utfLen + sha256Len + uidgidLen + (needsUnicode ? unicodePathLen : 0);
320
+ // Central directory header size (46 Bytes + filename + comment + extra fields)
321
+ const data = Buffer.alloc(Headers_1.CENTRAL_DIR.SIZE + this.filename.length + commentLen + extraLen);
322
+ // "PK\001\002"
323
+ data.writeUInt32LE(Headers_1.CENTRAL_DIR.SIGNATURE, 0);
324
+ // Version made by - Needs to be set for NeoZip
325
+ data.writeUInt16LE(this.isUpdated ? this.VER_MADE_BY : this.verMadeBy, Headers_1.CENTRAL_DIR.VER_MADE);
326
+ // Version needed to extract
327
+ data.writeInt16LE(this.isUpdated ? VER_EXTRACT : this.verMadeBy, Headers_1.CENTRAL_DIR.VER_EXT);
328
+ // Encrypt, Decrypt Flags
329
+ data.writeInt16LE(this.bitFlags >>> 0, Headers_1.CENTRAL_DIR.FLAGS);
330
+ // Compression method
331
+ data.writeInt16LE(this.cmpMethod, Headers_1.CENTRAL_DIR.CMP_METHOD);
332
+ // Modification time (2 bytes time, 2 bytes date)
333
+ data.writeUInt32LE(this.timeDateDOS >>> 0, Headers_1.CENTRAL_DIR.TIMEDATE_DOS);
334
+ // Uncompressed file CRC-32 value
335
+ data.writeUInt32LE(this.crc, Headers_1.CENTRAL_DIR.CRC);
336
+ // Compressed Size
337
+ data.writeUInt32LE(this.compressedSize, Headers_1.CENTRAL_DIR.CMP_SIZE);
338
+ // Uncompressed Size
339
+ data.writeUInt32LE(this.uncompressedSize, Headers_1.CENTRAL_DIR.UNCMP_SIZE);
340
+ // Filename Length
341
+ data.writeUInt16LE(this.filename.length, Headers_1.CENTRAL_DIR.FNAME_LEN);
342
+ // Extra Field Length
343
+ data.writeUInt16LE(extraLen, Headers_1.CENTRAL_DIR.EXTRA_LEN);
344
+ // File Comment Length
345
+ data.writeUInt16LE(commentLen, Headers_1.CENTRAL_DIR.COMMENT_LEN);
346
+ // Volume Number Start
347
+ data.writeUInt16LE(0, Headers_1.CENTRAL_DIR.DISK_NUM);
348
+ // Internal File Attributes
349
+ data.writeUInt16LE(this.intFileAttr >>> 0, Headers_1.CENTRAL_DIR.INT_FILE_ATTR);
350
+ // External File Attributes
351
+ data.writeUInt32LE(this.extFileAttr >>> 0, Headers_1.CENTRAL_DIR.EXT_FILE_ATTR);
352
+ // Local Header Offset
353
+ data.writeUInt32LE(this.localHdrOffset, Headers_1.CENTRAL_DIR.LOCAL_HDR_OFFSET);
354
+ // Write filename - use ASCII filename (replacing non-ASCII with ?)
355
+ // This ensures compatibility with older ZIP readers
356
+ const asciiName = this.filename.replace(/[^\x00-\x7E]/g, '?');
357
+ const fnameBuf = Buffer.from(asciiName);
358
+ fnameBuf.copy(data, Headers_1.CENTRAL_DIR.SIZE);
359
+ if (commentLen > 0 && this.comment) {
360
+ const commentBuf = Buffer.from(this.comment, 'utf8');
361
+ commentBuf.copy(data, Headers_1.CENTRAL_DIR.SIZE + fnameBuf.length);
362
+ }
363
+ // Add Extra Field data
364
+ let extraOffset = Headers_1.CENTRAL_DIR.SIZE + fnameBuf.length + commentLen;
365
+ // Add Universal Time field
366
+ if (this.universalTime) {
367
+ data.writeUInt16LE(Headers_1.HDR_ID.UNV_TIME, extraOffset); // 0x5455
368
+ data.writeUInt16LE(5, extraOffset + 2); // Length of data (flags + time)
369
+ data.writeUInt8(0x03, extraOffset + 4); // Flags: 1=Mod time, 2=Access time
370
+ data.writeUInt32LE(this.universalTime, extraOffset + 5); // Unix timestamp
371
+ extraOffset += 9; // 4 bytes header + 1 byte flags + 4 bytes data
372
+ }
373
+ // Unix UID/GID Extra Field (0x7875)
374
+ if (this.uid && this.gid) {
375
+ const uidgidLen = 11; // 1 byte version + 1 byte size + 4 bytes data + 1 byte size + 4 bytes data
376
+ data.writeUInt16LE(Headers_1.HDR_ID.UID_GID, extraOffset); // 0x7875
377
+ data.writeUInt16LE(uidgidLen, extraOffset + 2); // Length
378
+ data.writeUInt8(1, extraOffset + 4); // Version
379
+ data.writeUInt8(4, extraOffset + 5); // Size of UID/GID
380
+ data.writeUInt32LE(this.uid, extraOffset + 6); // UID
381
+ data.writeUInt8(4, extraOffset + 10); // Size of UID/GID
382
+ data.writeUInt32LE(this.gid, extraOffset + 11); // GID
383
+ extraOffset += uidgidLen + 4;
384
+ }
385
+ // Add Unicode Path Extra Field only if needed
386
+ if (needsUnicode) {
387
+ extraOffset = this.addUnicodePathField(data, extraOffset);
388
+ }
389
+ // Add SHA256 field
390
+ if (sha256Buf) {
391
+ data.writeUInt16LE(Headers_1.HDR_ID.SHA256, extraOffset);
392
+ data.writeUInt16LE(sha256Buf.length, extraOffset + 2);
393
+ sha256Buf.copy(data, extraOffset + 4);
394
+ extraOffset += sha256Buf.length + 4;
395
+ }
396
+ return data;
397
+ }
398
+ // ======================================
399
+ // Routines to handle the details of the Zip Entry
400
+ // ======================================
401
+ /**
402
+ * Sets the DOS date/time for this entry
403
+ * @param date - Date to convert to DOS format
404
+ * @returns number - DOS format date/time
405
+ */
406
+ setDateTime(date) {
407
+ if (!date)
408
+ return 0;
409
+ let time = (((date.getFullYear() - 1980) & 0x7f) << 25) | // b09-16 years from 1980
410
+ ((date.getMonth() + 1) << 21) | // b05-08 month
411
+ (date.getDate() << 16) | // b00-04 hour
412
+ // 2 bytes time
413
+ (date.getHours() << 11) | // b11-15 hour
414
+ (date.getMinutes() << 5) | // b05-10 minute
415
+ (date.getSeconds() >> 1); // b00-04 seconds divided by 2
416
+ return time;
417
+ }
418
+ /**
419
+ * Converts DOS date/time to JavaScript Date
420
+ * @param timeStamp - DOS format date/time
421
+ * @returns Date object or null if timestamp is 0
422
+ */
423
+ parseDateTime(timeStamp) {
424
+ if (timeStamp == 0)
425
+ return null;
426
+ return new Date(((timeStamp >> 25) & 0x3f) + 1980, // Year
427
+ ((timeStamp >> 21) & 0x0f) - 1, // Month
428
+ (timeStamp >> 16) & 0x1f, // Day
429
+ (timeStamp >> 11) & 0x1f, // Hour
430
+ (timeStamp >> 5) & 0x3f, // Min
431
+ (timeStamp & 0x1f) << 1); // Sec
432
+ }
433
+ /**
434
+ * Formats the entry's date in local format
435
+ * @returns String in MM/DD/YYYY format or "--/--/----" if no date
436
+ */
437
+ toLocalDateString() {
438
+ let _timeDate = this.parseDateTime(this.timeDateDOS);
439
+ if (_timeDate === null)
440
+ return "--/--/----";
441
+ return _timeDate.toLocaleDateString('en-US', {
442
+ year: 'numeric',
443
+ month: '2-digit',
444
+ day: '2-digit'
445
+ }).slice(0, 10);
446
+ }
447
+ /**
448
+ * Formats the entry's time
449
+ * @returns String in HH:MM format or "--:--" if no time
450
+ */
451
+ toTimeString() {
452
+ let _timeDate = this.parseDateTime(this.timeDateDOS);
453
+ if (_timeDate === null)
454
+ return "--:--";
455
+ return _timeDate.toTimeString().slice(0, 5);
456
+ }
457
+ /**
458
+ * Formats the entry's date and time in local format
459
+ * @returns String like "Jan 01, 2024 13:45:30" or "--/--/-- --:--" if no date/time
460
+ */
461
+ toFormattedDateString() {
462
+ let _timeDate = this.parseDateTime(this.timeDateDOS);
463
+ if (_timeDate == null)
464
+ return "--/--/-- --:--";
465
+ const datePart = _timeDate.toLocaleDateString('en-US', {
466
+ year: 'numeric',
467
+ month: 'short',
468
+ day: '2-digit'
469
+ });
470
+ const timePart = _timeDate.toLocaleTimeString('en-US', {
471
+ hour: '2-digit',
472
+ minute: '2-digit',
473
+ second: '2-digit',
474
+ hour12: false
475
+ });
476
+ return `${datePart} ${timePart}`;
477
+ }
478
+ /**
479
+ * Formats the entry's date and time in UTC
480
+ * @returns String like "Jan 01, 2024 13:45:30 UTC" or "--/--/-- --:--" if no date/time
481
+ */
482
+ toFormattedUTCDateString() {
483
+ let _timeDate = this.parseDateTime(this.timeDateDOS);
484
+ if (_timeDate == null)
485
+ return "--/--/-- --:--";
486
+ const datePart = _timeDate.toLocaleDateString('en-US', {
487
+ year: 'numeric',
488
+ month: 'short',
489
+ day: '2-digit',
490
+ timeZone: 'UTC'
491
+ });
492
+ const timePart = _timeDate.toLocaleTimeString('en-US', {
493
+ hour: '2-digit',
494
+ minute: '2-digit',
495
+ second: '2-digit',
496
+ hour12: false,
497
+ timeZone: 'UTC'
498
+ });
499
+ return `${datePart} ${timePart} UTC`;
500
+ }
501
+ /**
502
+ * Converts compression method code to human-readable string
503
+ * @returns String describing the compression method
504
+ */
505
+ cmpMethodToString() {
506
+ switch (this.cmpMethod) {
507
+ case Headers_1.CMP_METHOD.STORED: return 'Stored';
508
+ case Headers_1.CMP_METHOD.SHRUNK: return 'Shrunk';
509
+ case Headers_1.CMP_METHOD.REDUCED1: return 'Reduced-1';
510
+ case Headers_1.CMP_METHOD.REDUCED2: return 'Reduced-2';
511
+ case Headers_1.CMP_METHOD.REDUCED3: return 'Reduced-3';
512
+ case Headers_1.CMP_METHOD.REDUCED4: return 'Reduced-4';
513
+ case Headers_1.CMP_METHOD.IMPLODED: return 'Imploded';
514
+ case Headers_1.CMP_METHOD.DEFLATED:
515
+ switch (this.bitFlags & 0x6) {
516
+ case 0: return 'Deflate-N'; // Deflate Normal
517
+ case 2: return 'Deflate-M'; // Deflate Maximum
518
+ case 4: return 'Deflate-F'; // Deflate Fast
519
+ case 6: return 'Deflate-S'; // Deflate Super Fast
520
+ }
521
+ case Headers_1.CMP_METHOD.ENHANCED_DEFLATE: return 'Deflate-Enh';
522
+ case Headers_1.CMP_METHOD.IBM_TERSE: return 'PKDCL-LZ77';
523
+ case Headers_1.CMP_METHOD.ZSTD: return 'Zstandard';
524
+ default: return 'Unknown';
525
+ }
526
+ }
527
+ /**
528
+ * Converts file system code to human-readable string
529
+ * @returns String describing the file system
530
+ */
531
+ fileSystemToString() {
532
+ switch (this.verMadeBy >> 8) {
533
+ case Headers_1.FILE_SYSTEM.MSDOS: return 'MS-DOS';
534
+ case Headers_1.FILE_SYSTEM.AMIGA: return 'Amiga';
535
+ case Headers_1.FILE_SYSTEM.OPENVMS: return 'OpenVMS';
536
+ case Headers_1.FILE_SYSTEM.UNIX: return 'Unix';
537
+ case Headers_1.FILE_SYSTEM.VM_CMS: return 'VM/CMS';
538
+ case Headers_1.FILE_SYSTEM.ATARI: return 'Atari ST';
539
+ case Headers_1.FILE_SYSTEM.OS2: return 'OS/2 HPFS';
540
+ case Headers_1.FILE_SYSTEM.MAC: return 'Macintosh';
541
+ case Headers_1.FILE_SYSTEM.CP_M: return 'CP/M';
542
+ case Headers_1.FILE_SYSTEM.NTFS: return 'Windows NTFS';
543
+ case Headers_1.FILE_SYSTEM.MVS: return 'MVS (OS/390 - Z/OS)';
544
+ case Headers_1.FILE_SYSTEM.VSE: return 'VSE';
545
+ case Headers_1.FILE_SYSTEM.ACORN: return 'Acorn Risc';
546
+ case Headers_1.FILE_SYSTEM.ALTMVS: return 'Alternate MVS';
547
+ case Headers_1.FILE_SYSTEM.BEOS: return 'BeOS';
548
+ case Headers_1.FILE_SYSTEM.TANDEM: return 'Tandem';
549
+ case Headers_1.FILE_SYSTEM.OS400: return 'OS/400';
550
+ case Headers_1.FILE_SYSTEM.DARWIN: return 'Apple OS/X (Darwin)';
551
+ default: return 'Unknown';
552
+ }
553
+ }
554
+ /**
555
+ * Converts MS-DOS file attributes to string representation
556
+ * @returns String like "----" where R=readonly, H=hidden, S=system, A=archive
557
+ */
558
+ dosAttributesToString() {
559
+ let dosAttr = this.extFileAttr & 0xFFFF;
560
+ if (dosAttr === 0)
561
+ return 'none';
562
+ let attrs = '';
563
+ attrs += (dosAttr & Headers_1.DOS_FILE_ATTR.READONLY) ? 'r' : '-';
564
+ attrs += (dosAttr & Headers_1.DOS_FILE_ATTR.HIDDEN) ? 'h' : '-';
565
+ attrs += (dosAttr & Headers_1.DOS_FILE_ATTR.SYSTEM) ? 's' : '-';
566
+ // attrs += (dosAttr & DOS_FILE_ATTR.VOLUME) ? 'v' : '-';
567
+ attrs += (dosAttr & Headers_1.DOS_FILE_ATTR.DIRECTORY) ? 'd' : '-';
568
+ attrs += (dosAttr & Headers_1.DOS_FILE_ATTR.ARCHIVE) ? 'a' : '-';
569
+ return attrs;
570
+ }
571
+ /**
572
+ * Outputs detailed information about this entry for debugging
573
+ * Includes compression, encryption, timestamps, and extra fields
574
+ */
575
+ showVerboseInfo() {
576
+ console.log('=== Central Directory Entry (%s) ===', this.filename);
577
+ console.log('unzip from start of archive: ', this.localHdrOffset);
578
+ console.log('File system or operating system of origin: ', this.fileSystemToString());
579
+ console.log('Version of encoding software: ', this.verMadeBy);
580
+ console.log('Compression Method: ', this.cmpMethodToString());
581
+ console.log('File Security Status: ', this.bitFlags & Headers_1.GP_FLAG.ENCRYPTED ?
582
+ this.bitFlags & Headers_1.GP_FLAG.STRONG_ENCRYPT ? 'Strong Encrypt' : 'Encrypted' :
583
+ 'Not Encrypted');
584
+ console.log('File Modified (DOS date/time) ', this.toFormattedDateString());
585
+ console.log('File Modified (UTC) ', this.toFormattedUTCDateString());
586
+ console.log('Compressed Size: ', this.compressedSize);
587
+ console.log('UnCompressed Size: ', this.uncompressedSize);
588
+ console.log(`32-bit CRC value (hex): ${this.crc.toString(16).padStart(8, '0')}`);
589
+ console.log('Length of extra field: ', this.extraField?.length ?? 0);
590
+ console.log('Length of file comment: ', this.comment?.length ?? 0);
591
+ console.log('Unix File Attributes: ');
592
+ console.log('MS-DOS File Attributes: ', this.dosAttributesToString());
593
+ if (this.extraField) {
594
+ console.log('\nThe Central-Directory Extra Field contains:');
595
+ try {
596
+ for (let i = 0; i < this.extraField.length;) {
597
+ // Ensure we have at least 4 bytes (header ID + length)
598
+ if (i + 4 > this.extraField.length) {
599
+ console.log(` Warning: Truncated extra field at offset ${i}`);
600
+ break;
601
+ }
602
+ let _id = this.extraField.readUInt16LE(i);
603
+ let _idStr = _id.toString(16).padStart(4, '0');
604
+ let _len = this.extraField.readUInt16LE(i + 2);
605
+ // Validate the length to ensure it doesn't exceed buffer bounds
606
+ if (_len < 0 || i + 4 + _len > this.extraField.length) {
607
+ console.log(` Warning: Invalid extra field length (${_len}) at offset ${i} for ID ${_idStr}`);
608
+ break;
609
+ }
610
+ let _data = this.extraField.subarray(i + 4, i + 4 + _len);
611
+ try {
612
+ if (_id === Headers_1.HDR_ID.SHA256) {
613
+ console.log(` ID[0x${_idStr}] NeoZip-SHA256: ${_data.toString('hex')}`);
614
+ }
615
+ else if (_id === Headers_1.HDR_ID.UNV_TIME) {
616
+ if (_len >= 5) {
617
+ const flags = _data.readUInt8(0);
618
+ const timestamp = _data.readUInt32LE(1);
619
+ const date = new Date(timestamp * 1000);
620
+ console.log(` ID[0x${_idStr}] Universal Time: flags=${flags}, time=${date.toISOString()}`);
621
+ }
622
+ else {
623
+ console.log(` ID[0x${_idStr}] Universal Time: (invalid length ${_len})`);
624
+ }
625
+ }
626
+ else if (_id === Headers_1.HDR_ID.UID_GID) {
627
+ if (_len >= 5) {
628
+ const version = _data.readUInt8(0);
629
+ const uidSize = _data.readUInt8(1);
630
+ if (2 + uidSize > _len) {
631
+ console.log(` ID[0x${_idStr}] Unix UID/GID: (invalid UID size ${uidSize})`);
632
+ }
633
+ else {
634
+ const uid = _data.readUIntLE(2, Math.min(uidSize, 4));
635
+ let gid = 0;
636
+ if (2 + uidSize + 1 < _len) {
637
+ const gidSize = _data.readUInt8(2 + uidSize);
638
+ if (2 + uidSize + 1 + gidSize <= _len) {
639
+ gid = _data.readUIntLE(2 + uidSize + 1, Math.min(gidSize, 4));
640
+ }
641
+ }
642
+ console.log(` ID[0x${_idStr}] Unix UID/GID: version=${version}, uid=${uid}, gid=${gid}`);
643
+ }
644
+ }
645
+ else {
646
+ console.log(` ID[0x${_idStr}] Unix UID/GID: (invalid length ${_len})`);
647
+ }
648
+ }
649
+ else if (_id === Headers_1.HDR_ID.UNICODE_PATH) {
650
+ if (_len >= 5) {
651
+ const version = _data.readUInt8(0);
652
+ const nameCrc32 = _data.readUInt32LE(1);
653
+ const unicodeName = _data.subarray(5).toString('utf8');
654
+ // Calculate CRC32 of the original filename for verification
655
+ const fnameBuf = Buffer.from(this.filename);
656
+ const calculatedCrc = (0, Util_1.crc32)(fnameBuf);
657
+ const crcMatch = nameCrc32 === calculatedCrc ? 'MATCH' : 'MISMATCH';
658
+ console.log(` ID[0x${_idStr}] Unicode Path: version=${version}, CRC32=${nameCrc32.toString(16)} (${crcMatch})`);
659
+ console.log(` Path: "${unicodeName}"`);
660
+ }
661
+ else {
662
+ console.log(` ID[0x${_idStr}] Unicode Path: (invalid length ${_len})`);
663
+ }
664
+ }
665
+ else {
666
+ // For unknown fields, show a hex preview of the first few bytes
667
+ const preview = _len > 0
668
+ ? _data.slice(0, Math.min(16, _len)).toString('hex')
669
+ : '';
670
+ console.log(` ID[0x${_idStr}] Unknown field: length=${_len} bytes${preview ? ', data=' + preview + '...' : ''}`);
671
+ }
672
+ }
673
+ catch (error) {
674
+ console.log(` Error parsing extra field ID[0x${_idStr}]: ${error.message}`);
675
+ }
676
+ i += 4 + _len;
677
+ }
678
+ }
679
+ catch (error) {
680
+ console.log(` Error parsing extra fields: ${error.message}`);
681
+ }
682
+ }
683
+ console.log('\n');
684
+ }
685
+ }
686
+ exports.default = ZipEntry;