7zip-native 0.0.12 → 0.0.15
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 +2 -1
- package/binding.gyp +8 -8
- package/dist/index.cjs +11 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +12 -2
- package/native/addon.cpp +108 -30
- package/package.json +13 -2
- package/prebuilds/darwin-arm64/7zip_native.node +0 -0
- package/prebuilds/darwin-x64/7zip_native.node +0 -0
- package/prebuilds/linux-arm/7zip_native.node +0 -0
- package/prebuilds/linux-arm64/7zip_native.node +0 -0
- package/prebuilds/linux-ia32/7zip_native.node +0 -0
- package/prebuilds/linux-x64/7zip_native.node +0 -0
- package/prebuilds/win32-arm64/7z.dll +0 -0
- package/prebuilds/win32-arm64/7zip_native.node +0 -0
- package/prebuilds/win32-x64/7z.dll +0 -0
- package/prebuilds/win32-x64/7zip_native.node +0 -0
package/README.md
CHANGED
|
@@ -90,6 +90,7 @@ Each entry includes:
|
|
|
90
90
|
| --- | --- | --- |
|
|
91
91
|
| `id` | `number` | Stable ID within this opened archive |
|
|
92
92
|
| `path` | `string` | Entry path, using `/` separators |
|
|
93
|
+
| `extractedPath` | `string` | Relative output path after platform-specific filename normalization; Windows-invalid characters are replaced |
|
|
93
94
|
| `size` | `bigint` | Uncompressed size |
|
|
94
95
|
| `compressedSize` | `bigint` | Compressed size |
|
|
95
96
|
| `isDirectory` | `boolean` | Whether the entry is a directory |
|
|
@@ -293,7 +294,7 @@ Windows x86 (ia32) is not supported by the published prebuilt binaries.
|
|
|
293
294
|
|
|
294
295
|
## Electron
|
|
295
296
|
|
|
296
|
-
The addon uses Node-API, but the addon and 7-Zip engine still need to match the Electron application's operating system and architecture. Use a matching package prebuild when available, or rebuild for the Electron version and target architecture using your Electron build tooling.
|
|
297
|
+
The addon uses Node-API, but the addon and 7-Zip engine still need to match the Electron application's operating system and architecture. Use a matching package prebuild when available, or rebuild for the Electron version and target architecture using your Electron build tooling. The engine is loaded at runtime rather than linked into the addon, so ensure the matching engine library is included alongside the addon.
|
|
297
298
|
|
|
298
299
|
When packaging with ASAR, ensure both `7zip_native.node` and the matching engine library (`7z.dll`, `7z.so`, or `lib7z.dylib`) are physically unpacked and that the runtime paths resolve to those unpacked files. For `electron-builder`, an `asarUnpack` configuration can include:
|
|
299
300
|
|
package/binding.gyp
CHANGED
|
@@ -15,20 +15,20 @@
|
|
|
15
15
|
"cflags_cc!": ["-fno-exceptions"],
|
|
16
16
|
"conditions": [
|
|
17
17
|
["OS=='linux'", {
|
|
18
|
+
"sources": [
|
|
19
|
+
"vendor/7zip/CPP/Common/MyWindows.cpp",
|
|
20
|
+
"vendor/7zip/CPP/Windows/PropVariant.cpp"
|
|
21
|
+
],
|
|
18
22
|
"cflags_cc": ["-pthread"],
|
|
19
23
|
"libraries": [
|
|
20
24
|
"-pthread",
|
|
21
|
-
"-ldl"
|
|
22
|
-
"-L<(module_root_dir)/vendor/7zip/CPP/7zip/Bundles/Format7zF/_o",
|
|
23
|
-
"-l:7z.so",
|
|
24
|
-
"-Wl,-rpath,'$$ORIGIN'"
|
|
25
|
+
"-ldl"
|
|
25
26
|
]
|
|
26
27
|
}],
|
|
27
28
|
["OS=='mac'", {
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"-Wl,-rpath,@loader_path"
|
|
29
|
+
"sources": [
|
|
30
|
+
"vendor/7zip/CPP/Common/MyWindows.cpp",
|
|
31
|
+
"vendor/7zip/CPP/Windows/PropVariant.cpp"
|
|
32
32
|
],
|
|
33
33
|
"xcode_settings": {
|
|
34
34
|
"CLANG_CXX_LANGUAGE_STANDARD": "c++17",
|
package/dist/index.cjs
CHANGED
|
@@ -99,6 +99,11 @@ function sanitizeOutputPath(entryPath) {
|
|
|
99
99
|
}
|
|
100
100
|
return entryPath.replace(/\\/g, '/').split('/').map(sanitizeWindowsSegment).join('/');
|
|
101
101
|
}
|
|
102
|
+
function getExtractedPath(entryPath) {
|
|
103
|
+
const normalized = sanitizeOutputPath(entryPath).replace(/\\/g, '/');
|
|
104
|
+
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
105
|
+
return segments.join(node_path.sep);
|
|
106
|
+
}
|
|
102
107
|
function assertSafeRelativeArchivePath(entryPath) {
|
|
103
108
|
const normalized = entryPath.replace(/\\/g, '/');
|
|
104
109
|
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
@@ -481,7 +486,12 @@ class Archive {
|
|
|
481
486
|
}
|
|
482
487
|
async entries() {
|
|
483
488
|
this.assertOpen();
|
|
484
|
-
this.entriesPromise ??= native.entries(this.handle)
|
|
489
|
+
this.entriesPromise ??= native.entries(this.handle)
|
|
490
|
+
.then(entries => entries.map(entry => ({
|
|
491
|
+
...entry,
|
|
492
|
+
extractedPath: getExtractedPath(entry.path),
|
|
493
|
+
})))
|
|
494
|
+
.catch((cause) => {
|
|
485
495
|
throw new ArchiveError('ERR_ARCHIVE_OPEN', errorMessage(cause), { cause });
|
|
486
496
|
});
|
|
487
497
|
return this.entriesPromise;
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { rename, link, rm, lstat, mkdir } from 'node:fs/promises';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { availableParallelism } from 'node:os';
|
|
6
|
-
import { dirname, resolve, join, extname,
|
|
6
|
+
import { dirname, resolve, join, extname, sep, relative } from 'node:path';
|
|
7
7
|
import { Readable } from 'node:stream';
|
|
8
8
|
import { pipeline } from 'node:stream/promises';
|
|
9
9
|
|
|
@@ -96,6 +96,11 @@ function sanitizeOutputPath(entryPath) {
|
|
|
96
96
|
}
|
|
97
97
|
return entryPath.replace(/\\/g, '/').split('/').map(sanitizeWindowsSegment).join('/');
|
|
98
98
|
}
|
|
99
|
+
function getExtractedPath(entryPath) {
|
|
100
|
+
const normalized = sanitizeOutputPath(entryPath).replace(/\\/g, '/');
|
|
101
|
+
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
102
|
+
return segments.join(sep);
|
|
103
|
+
}
|
|
99
104
|
function assertSafeRelativeArchivePath(entryPath) {
|
|
100
105
|
const normalized = entryPath.replace(/\\/g, '/');
|
|
101
106
|
const segments = normalized.split('/').filter(segment => segment !== '' && segment !== '.');
|
|
@@ -478,7 +483,12 @@ class Archive {
|
|
|
478
483
|
}
|
|
479
484
|
async entries() {
|
|
480
485
|
this.assertOpen();
|
|
481
|
-
this.entriesPromise ??= native.entries(this.handle)
|
|
486
|
+
this.entriesPromise ??= native.entries(this.handle)
|
|
487
|
+
.then(entries => entries.map(entry => ({
|
|
488
|
+
...entry,
|
|
489
|
+
extractedPath: getExtractedPath(entry.path),
|
|
490
|
+
})))
|
|
491
|
+
.catch((cause) => {
|
|
482
492
|
throw new ArchiveError('ERR_ARCHIVE_OPEN', errorMessage(cause), { cause });
|
|
483
493
|
});
|
|
484
494
|
return this.entriesPromise;
|
package/native/addon.cpp
CHANGED
|
@@ -2160,12 +2160,12 @@ public:
|
|
|
2160
2160
|
GUID classId;
|
|
2161
2161
|
std::string name;
|
|
2162
2162
|
bool extensionMatch;
|
|
2163
|
-
bool
|
|
2164
|
-
|
|
2163
|
+
bool contentMatch;
|
|
2164
|
+
unsigned probePriority;
|
|
2165
2165
|
};
|
|
2166
2166
|
std::vector<Format> formats;
|
|
2167
2167
|
formats.reserve(formatCount);
|
|
2168
|
-
std::vector<Byte> header(
|
|
2168
|
+
std::vector<Byte> header(1 << 20);
|
|
2169
2169
|
auto *probeFile = new FileInStream();
|
|
2170
2170
|
CMyComPtr<IInStream> probe = probeFile;
|
|
2171
2171
|
if (!probeFile->Open(path)) {
|
|
@@ -2178,6 +2178,8 @@ public:
|
|
|
2178
2178
|
if (headerSize == 0) {
|
|
2179
2179
|
throw std::runtime_error("Archive file is empty");
|
|
2180
2180
|
}
|
|
2181
|
+
const bool endOfFile = headerSize < header.size()
|
|
2182
|
+
|| GetFileStamp(path).size <= headerSize;
|
|
2181
2183
|
for (UInt32 i = 0; i < formatCount; ++i) {
|
|
2182
2184
|
NWindows::NCOM::CPropVariant classIdValue;
|
|
2183
2185
|
if (g_getHandlerProperty(i, NArchive::NHandlerPropID::kClassID, &classIdValue) != S_OK
|
|
@@ -2207,38 +2209,95 @@ public:
|
|
|
2207
2209
|
}
|
|
2208
2210
|
Func_IsArc isArchive = nullptr;
|
|
2209
2211
|
const bool hasSignatureFunction = g_getIsArc(i, &isArchive) == S_OK && isArchive != nullptr;
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2212
|
+
constexpr UInt32 kStartOpenFlags =
|
|
2213
|
+
NArcInfoFlags::kStartOpen
|
|
2214
|
+
| NArcInfoFlags::kPureStartOpen
|
|
2215
|
+
| NArcInfoFlags::kBackwardOpen;
|
|
2216
|
+
NWindows::NCOM::CPropVariant flagsValue;
|
|
2217
|
+
const bool hasFlags = g_getHandlerProperty(
|
|
2218
|
+
i, NArchive::NHandlerPropID::kFlags, &flagsValue) == S_OK
|
|
2219
|
+
&& flagsValue.vt == VT_UI4;
|
|
2220
|
+
const UInt32 flags = hasFlags ? flagsValue.ulVal : 0;
|
|
2221
|
+
if (!match && (flags & NArcInfoFlags::kByExtOnlyOpen) != 0) {
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
bool contentMatch = false;
|
|
2226
|
+
unsigned probePriority = match ? 0 : 2;
|
|
2227
|
+
if (hasSignatureFunction) {
|
|
2228
|
+
const auto result = isArchive(header.data(), headerSize);
|
|
2229
|
+
contentMatch = result == k_IsArc_Res_YES
|
|
2230
|
+
|| (result == k_IsArc_Res_NEED_MORE && !endOfFile);
|
|
2231
|
+
if (!match && contentMatch) {
|
|
2232
|
+
probePriority = 1;
|
|
2233
|
+
} else if (!match && !contentMatch) {
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
} else {
|
|
2237
|
+
NWindows::NCOM::CPropVariant signatureOffsetValue;
|
|
2238
|
+
UInt32 signatureOffset = 0;
|
|
2239
|
+
if (g_getHandlerProperty(
|
|
2240
|
+
i, NArchive::NHandlerPropID::kSignatureOffset, &signatureOffsetValue) == S_OK
|
|
2241
|
+
&& signatureOffsetValue.vt == VT_UI4) {
|
|
2242
|
+
signatureOffset = signatureOffsetValue.ulVal;
|
|
2243
|
+
}
|
|
2244
|
+
std::vector<std::vector<Byte>> signatures;
|
|
2245
|
+
NWindows::NCOM::CPropVariant signatureValue;
|
|
2246
|
+
if (g_getHandlerProperty(i, NArchive::NHandlerPropID::kSignature, &signatureValue) == S_OK
|
|
2247
|
+
&& signatureValue.vt == VT_BSTR
|
|
2248
|
+
&& ::SysStringByteLen(signatureValue.bstrVal) != 0) {
|
|
2249
|
+
const auto *data = reinterpret_cast<const Byte *>(signatureValue.bstrVal);
|
|
2250
|
+
const auto size = ::SysStringByteLen(signatureValue.bstrVal);
|
|
2251
|
+
signatures.emplace_back(data, data + size);
|
|
2252
|
+
} else {
|
|
2253
|
+
NWindows::NCOM::CPropVariant multiSignatureValue;
|
|
2254
|
+
if (g_getHandlerProperty(
|
|
2255
|
+
i, NArchive::NHandlerPropID::kMultiSignature, &multiSignatureValue) == S_OK
|
|
2256
|
+
&& multiSignatureValue.vt == VT_BSTR) {
|
|
2257
|
+
const auto *data = reinterpret_cast<const Byte *>(multiSignatureValue.bstrVal);
|
|
2258
|
+
const auto size = ::SysStringByteLen(multiSignatureValue.bstrVal);
|
|
2259
|
+
for (UINT offset = 0; offset < size;) {
|
|
2260
|
+
const auto signatureSize = data[offset++];
|
|
2261
|
+
if (signatureSize > size - offset) {
|
|
2262
|
+
signatures.clear();
|
|
2263
|
+
break;
|
|
2264
|
+
}
|
|
2265
|
+
signatures.emplace_back(data + offset, data + offset + signatureSize);
|
|
2266
|
+
offset += signatureSize;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
bool signatureMatch = false;
|
|
2272
|
+
bool needCheck = !hasFlags || signatures.empty()
|
|
2273
|
+
|| (flags & kStartOpenFlags) != 0 || signatureOffset != 0;
|
|
2274
|
+
const auto signatureOffsetSize = static_cast<std::size_t>(signatureOffset);
|
|
2275
|
+
for (const auto& signature : signatures) {
|
|
2276
|
+
if (signatureOffsetSize > headerSize
|
|
2277
|
+
|| signature.size() > headerSize - signatureOffsetSize) {
|
|
2278
|
+
if (!endOfFile) {
|
|
2279
|
+
needCheck = true;
|
|
2280
|
+
}
|
|
2281
|
+
} else if (std::memcmp(
|
|
2282
|
+
header.data() + signatureOffsetSize, signature.data(), signature.size()) == 0) {
|
|
2283
|
+
signatureMatch = true;
|
|
2284
|
+
break;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
contentMatch = signatureMatch;
|
|
2288
|
+
if (!match && signatureMatch) {
|
|
2289
|
+
probePriority = 1;
|
|
2290
|
+
} else if (!match && !needCheck) {
|
|
2291
|
+
continue;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
formats.push_back({i, classId, std::move(name), match, contentMatch, probePriority});
|
|
2236
2295
|
}
|
|
2237
2296
|
if (formats.empty()) {
|
|
2238
2297
|
throw std::runtime_error("No registered 7-Zip handler recognizes this archive");
|
|
2239
2298
|
}
|
|
2240
2299
|
std::stable_sort(formats.begin(), formats.end(), [](const Format& left, const Format& right) {
|
|
2241
|
-
return left.
|
|
2300
|
+
return left.probePriority < right.probePriority;
|
|
2242
2301
|
});
|
|
2243
2302
|
|
|
2244
2303
|
bool passwordRequested = false;
|
|
@@ -2269,6 +2328,11 @@ public:
|
|
|
2269
2328
|
candidate->Close();
|
|
2270
2329
|
continue;
|
|
2271
2330
|
}
|
|
2331
|
+
if (!HasValidEntryPaths(candidate) && !format.contentMatch
|
|
2332
|
+
&& LowerAscii(format.name) != "gzip") {
|
|
2333
|
+
candidate->Close();
|
|
2334
|
+
continue;
|
|
2335
|
+
}
|
|
2272
2336
|
auto context = std::make_unique<ArchiveContext>();
|
|
2273
2337
|
context->archive = candidate;
|
|
2274
2338
|
context->stream = input;
|
|
@@ -2636,6 +2700,20 @@ public:
|
|
|
2636
2700
|
}
|
|
2637
2701
|
|
|
2638
2702
|
private:
|
|
2703
|
+
bool HasValidEntryPaths(IInArchive *archive) const
|
|
2704
|
+
{
|
|
2705
|
+
UInt32 count = 0;
|
|
2706
|
+
if (archive->GetNumberOfItems(&count) != S_OK || count > kMaxEntries) {
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
for (UInt32 i = 0; i < count; ++i) {
|
|
2710
|
+
if (GetPropString(archive, i, kpidPath).empty()) {
|
|
2711
|
+
return false;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
return true;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2639
2717
|
ContextLease AcquireContext()
|
|
2640
2718
|
{
|
|
2641
2719
|
std::unique_lock<std::mutex> lock(contextMutex_);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "7zip-native",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Native 7zip bindings for Node.js",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -65,9 +65,20 @@
|
|
|
65
65
|
},
|
|
66
66
|
"keywords": [
|
|
67
67
|
"7zip",
|
|
68
|
+
"7-zip",
|
|
69
|
+
"archive",
|
|
68
70
|
"nodejs",
|
|
69
71
|
"bindings",
|
|
70
|
-
"native"
|
|
72
|
+
"native",
|
|
73
|
+
"node-api",
|
|
74
|
+
"napi",
|
|
75
|
+
"zip",
|
|
76
|
+
"rar",
|
|
77
|
+
"tar",
|
|
78
|
+
"wim",
|
|
79
|
+
"compression",
|
|
80
|
+
"decompression",
|
|
81
|
+
"extraction"
|
|
71
82
|
],
|
|
72
83
|
"devDependencies": {
|
|
73
84
|
"@eslint/js": "^10.0.1",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|