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