zip-lib 1.2.2 → 1.3.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 +295 -203
- package/lib/fs.d.ts +2 -0
- package/lib/fs.js +81 -61
- package/lib/index.d.ts +21 -2
- package/lib/index.js +27 -24
- package/lib/unzip.d.ts +33 -10
- package/lib/unzip.js +195 -128
- package/lib/zip.d.ts +25 -10
- package/lib/zip.js +143 -60
- package/package.json +3 -3
package/lib/fs.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isOutside = isOutside;
|
|
3
4
|
exports.realpath = realpath;
|
|
4
5
|
exports.readdirp = readdirp;
|
|
5
6
|
exports.getFileEntry = getFileEntry;
|
|
6
7
|
exports.ensureFolder = ensureFolder;
|
|
7
8
|
exports.pathExists = pathExists;
|
|
9
|
+
exports.statFolder = statFolder;
|
|
8
10
|
exports.rimraf = rimraf;
|
|
9
11
|
exports.isRootPath = isRootPath;
|
|
10
12
|
const fsSync = require("node:fs");
|
|
11
13
|
const fs = require("node:fs/promises");
|
|
12
14
|
const path = require("node:path");
|
|
13
15
|
const util = require("node:util");
|
|
16
|
+
function isOutside(baseDir, targetPath) {
|
|
17
|
+
const absoluteBase = path.resolve(baseDir);
|
|
18
|
+
const absoluteTarget = path.resolve(targetPath);
|
|
19
|
+
const relative = path.relative(absoluteBase, absoluteTarget);
|
|
20
|
+
return relative.startsWith("..") || path.isAbsolute(relative);
|
|
21
|
+
}
|
|
14
22
|
async function realpath(target) {
|
|
15
23
|
// fs.promises.realpath has a bug with long path on Windows.
|
|
16
24
|
// https://github.com/nodejs/node/issues/51031
|
|
@@ -18,16 +26,14 @@ async function realpath(target) {
|
|
|
18
26
|
}
|
|
19
27
|
async function readdirp(folder) {
|
|
20
28
|
const result = [];
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const entry = await getFileEntry(file);
|
|
29
|
+
const filePaths = (await fs.readdir(folder)).map((item) => path.join(folder, item));
|
|
30
|
+
const entries = await Promise.all(filePaths.map((filePath) => getFileEntry(filePath)));
|
|
31
|
+
for (const entry of entries) {
|
|
25
32
|
if (!entry.isSymbolicLink && entry.type === "dir") {
|
|
26
|
-
const subFiles = await readdirp(
|
|
33
|
+
const subFiles = await readdirp(entry.path);
|
|
27
34
|
if (subFiles.length > 0) {
|
|
28
35
|
result.push(...subFiles);
|
|
29
36
|
// If the folder is not empty, don't need to add the folder itself.
|
|
30
|
-
// continue and skip the code below
|
|
31
37
|
continue;
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -37,27 +43,32 @@ async function readdirp(folder) {
|
|
|
37
43
|
}
|
|
38
44
|
async function getFileEntry(target) {
|
|
39
45
|
const stat = await fs.lstat(target);
|
|
40
|
-
let isSymbolicLink = false;
|
|
41
|
-
let fileType = "file";
|
|
42
46
|
if (stat.isDirectory()) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// the link which is always a file.
|
|
51
|
-
const actualStat = await fs.stat(target);
|
|
52
|
-
if (actualStat.isDirectory()) {
|
|
53
|
-
fileType = "dir";
|
|
54
|
-
}
|
|
55
|
-
}
|
|
47
|
+
return {
|
|
48
|
+
path: target,
|
|
49
|
+
isSymbolicLink: false,
|
|
50
|
+
type: "dir",
|
|
51
|
+
mtime: stat.mtime,
|
|
52
|
+
mode: stat.mode,
|
|
53
|
+
};
|
|
56
54
|
}
|
|
55
|
+
if (!stat.isSymbolicLink()) {
|
|
56
|
+
return {
|
|
57
|
+
path: target,
|
|
58
|
+
isSymbolicLink: false,
|
|
59
|
+
type: "file",
|
|
60
|
+
mtime: stat.mtime,
|
|
61
|
+
mode: stat.mode,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// If the path is a link, we must instead use fs.stat() to find out if the
|
|
65
|
+
// link is a directory or not because lstat will always return the stat of
|
|
66
|
+
// the link which is always a file.
|
|
67
|
+
const actualStat = await fs.stat(target);
|
|
57
68
|
return {
|
|
58
69
|
path: target,
|
|
59
|
-
isSymbolicLink,
|
|
60
|
-
type:
|
|
70
|
+
isSymbolicLink: true,
|
|
71
|
+
type: actualStat.isDirectory() ? "dir" : "file",
|
|
61
72
|
mtime: stat.mtime,
|
|
62
73
|
mode: stat.mode,
|
|
63
74
|
};
|
|
@@ -65,24 +76,23 @@ async function getFileEntry(target) {
|
|
|
65
76
|
async function ensureFolder(folder) {
|
|
66
77
|
// stop at root
|
|
67
78
|
if (folder === path.dirname(folder)) {
|
|
68
|
-
return
|
|
79
|
+
return {
|
|
69
80
|
isDirectory: true,
|
|
70
81
|
isSymbolicLink: false,
|
|
71
|
-
}
|
|
82
|
+
};
|
|
72
83
|
}
|
|
73
84
|
try {
|
|
74
|
-
|
|
75
|
-
return result;
|
|
85
|
+
return await mkdir(folder);
|
|
76
86
|
}
|
|
77
87
|
catch (error) {
|
|
78
88
|
// ENOENT: a parent folder does not exist yet, continue
|
|
79
89
|
// to create the parent folder and then try again.
|
|
80
90
|
if (error.code === "ENOENT") {
|
|
81
91
|
await ensureFolder(path.dirname(folder));
|
|
82
|
-
return mkdir(folder);
|
|
92
|
+
return await mkdir(folder);
|
|
83
93
|
}
|
|
84
94
|
// Any other error
|
|
85
|
-
|
|
95
|
+
throw error;
|
|
86
96
|
}
|
|
87
97
|
}
|
|
88
98
|
async function pathExists(target) {
|
|
@@ -94,10 +104,21 @@ async function pathExists(target) {
|
|
|
94
104
|
return false;
|
|
95
105
|
}
|
|
96
106
|
}
|
|
107
|
+
async function statFolder(folder) {
|
|
108
|
+
try {
|
|
109
|
+
return await statExistingFolder(folder);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code === "ENOENT") {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
97
118
|
async function rimraf(target) {
|
|
98
119
|
if (isRootPath(target)) {
|
|
99
120
|
// refuse to recursively delete root
|
|
100
|
-
|
|
121
|
+
throw new Error(`Refuse to recursively delete root, path: "${target}"`);
|
|
101
122
|
}
|
|
102
123
|
try {
|
|
103
124
|
const stat = await fs.lstat(target);
|
|
@@ -108,17 +129,15 @@ async function rimraf(target) {
|
|
|
108
129
|
await Promise.all(children.map((child) => rimraf(path.join(target, child))));
|
|
109
130
|
// Folder
|
|
110
131
|
await fs.rmdir(target);
|
|
132
|
+
return;
|
|
111
133
|
}
|
|
112
134
|
// Single file delete
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// 128 === 0200
|
|
118
|
-
await fs.chmod(target, mode | 128);
|
|
119
|
-
}
|
|
120
|
-
return fs.unlink(target);
|
|
135
|
+
const mode = stat.mode;
|
|
136
|
+
if (!(mode & 128)) {
|
|
137
|
+
// 128 === 0200
|
|
138
|
+
await fs.chmod(target, mode | 128);
|
|
121
139
|
}
|
|
140
|
+
await fs.unlink(target);
|
|
122
141
|
}
|
|
123
142
|
catch (error) {
|
|
124
143
|
if (error.code !== "ENOENT") {
|
|
@@ -137,39 +156,40 @@ async function mkdir(folder) {
|
|
|
137
156
|
catch (error) {
|
|
138
157
|
// ENOENT: a parent folder does not exist yet or folder name is invalid.
|
|
139
158
|
if (error.code === "ENOENT") {
|
|
140
|
-
|
|
159
|
+
throw error;
|
|
141
160
|
}
|
|
142
161
|
// Any other error: check if folder exists and
|
|
143
162
|
// return normally in that case if its a folder
|
|
144
163
|
try {
|
|
145
|
-
|
|
146
|
-
if (fileStat.isSymbolicLink()) {
|
|
147
|
-
const realFilePath = await realpath(folder);
|
|
148
|
-
const realFileStat = await fs.lstat(realFilePath);
|
|
149
|
-
if (!realFileStat.isDirectory()) {
|
|
150
|
-
return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
isDirectory: false,
|
|
154
|
-
isSymbolicLink: true,
|
|
155
|
-
realpath: realFilePath,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
if (!fileStat.isDirectory()) {
|
|
160
|
-
return Promise.reject(new Error(`"${folder}" exists and is not a directory.`));
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
isDirectory: true,
|
|
164
|
-
isSymbolicLink: false,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
164
|
+
return await statExistingFolder(folder);
|
|
167
165
|
}
|
|
168
166
|
catch (_statError) {
|
|
169
167
|
throw error; // rethrow original error
|
|
170
168
|
}
|
|
171
169
|
}
|
|
172
170
|
}
|
|
171
|
+
async function statExistingFolder(folder) {
|
|
172
|
+
const fileStat = await fs.lstat(folder);
|
|
173
|
+
if (!fileStat.isSymbolicLink()) {
|
|
174
|
+
if (!fileStat.isDirectory()) {
|
|
175
|
+
throw new Error(`"${folder}" exists and is not a directory.`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
isDirectory: true,
|
|
179
|
+
isSymbolicLink: false,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const realFilePath = await realpath(folder);
|
|
183
|
+
const realFileStat = await fs.lstat(realFilePath);
|
|
184
|
+
if (!realFileStat.isDirectory()) {
|
|
185
|
+
throw new Error(`"${folder}" exists and is not a directory.`);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
isDirectory: false,
|
|
189
|
+
isSymbolicLink: true,
|
|
190
|
+
realpath: realFilePath,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
173
193
|
// "A"
|
|
174
194
|
const charA = 65;
|
|
175
195
|
// "Z"
|
package/lib/index.d.ts
CHANGED
|
@@ -3,19 +3,38 @@ import { type IZipOptions } from "./zip";
|
|
|
3
3
|
export * from "./unzip";
|
|
4
4
|
export * from "./zip";
|
|
5
5
|
/**
|
|
6
|
-
* Compress a single file to
|
|
6
|
+
* Compress a single file to a buffer.
|
|
7
|
+
* @param file
|
|
8
|
+
* @param options
|
|
9
|
+
*/
|
|
10
|
+
export declare function archiveFile(file: string, options?: IZipOptions): Promise<Buffer>;
|
|
11
|
+
/**
|
|
12
|
+
* Compress a single file to the specified zip file path.
|
|
7
13
|
* @param file
|
|
8
14
|
* @param zipFile the zip file path.
|
|
9
15
|
* @param options
|
|
10
16
|
*/
|
|
11
17
|
export declare function archiveFile(file: string, zipFile: string, options?: IZipOptions): Promise<void>;
|
|
12
18
|
/**
|
|
13
|
-
* Compress all the contents of the specified folder to
|
|
19
|
+
* Compress all the contents of the specified folder to a buffer.
|
|
20
|
+
* @param folder
|
|
21
|
+
* @param options
|
|
22
|
+
*/
|
|
23
|
+
export declare function archiveFolder(folder: string, options?: IZipOptions): Promise<Buffer>;
|
|
24
|
+
/**
|
|
25
|
+
* Compress all the contents of the specified folder to the specified zip file path.
|
|
14
26
|
* @param folder
|
|
15
27
|
* @param zipFile the zip file path.
|
|
16
28
|
* @param options
|
|
17
29
|
*/
|
|
18
30
|
export declare function archiveFolder(folder: string, zipFile: string, options?: IZipOptions): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Extract the zip buffer to the specified location.
|
|
33
|
+
* @param zipBuffer
|
|
34
|
+
* @param targetFolder
|
|
35
|
+
* @param options
|
|
36
|
+
*/
|
|
37
|
+
export declare function extract(zipBuffer: Buffer, targetFolder: string, options?: IExtractOptions): Promise<void>;
|
|
19
38
|
/**
|
|
20
39
|
* Extract the zip file to the specified location.
|
|
21
40
|
* @param zipFile
|
package/lib/index.js
CHANGED
|
@@ -21,35 +21,38 @@ const unzip_1 = require("./unzip");
|
|
|
21
21
|
const zip_1 = require("./zip");
|
|
22
22
|
__exportStar(require("./unzip"), exports);
|
|
23
23
|
__exportStar(require("./zip"), exports);
|
|
24
|
-
/**
|
|
25
|
-
* Compress a single file to zip.
|
|
26
|
-
* @param file
|
|
27
|
-
* @param zipFile the zip file path.
|
|
28
|
-
* @param options
|
|
29
|
-
*/
|
|
30
24
|
function archiveFile(file, zipFile, options) {
|
|
31
|
-
|
|
25
|
+
let optionsParameter;
|
|
26
|
+
if (typeof zipFile === "string") {
|
|
27
|
+
optionsParameter = options;
|
|
28
|
+
}
|
|
29
|
+
else if (typeof zipFile === "object") {
|
|
30
|
+
optionsParameter = zipFile;
|
|
31
|
+
}
|
|
32
|
+
const zip = new zip_1.Zip(optionsParameter);
|
|
32
33
|
zip.addFile(file);
|
|
33
|
-
|
|
34
|
+
if (typeof zipFile === "string") {
|
|
35
|
+
return zip.archive(zipFile);
|
|
36
|
+
}
|
|
37
|
+
return zip.archive();
|
|
34
38
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Compress all the contents of the specified folder to zip.
|
|
37
|
-
* @param folder
|
|
38
|
-
* @param zipFile the zip file path.
|
|
39
|
-
* @param options
|
|
40
|
-
*/
|
|
41
39
|
function archiveFolder(folder, zipFile, options) {
|
|
42
|
-
|
|
40
|
+
let optionsParameter;
|
|
41
|
+
if (typeof zipFile === "string") {
|
|
42
|
+
optionsParameter = options;
|
|
43
|
+
}
|
|
44
|
+
else if (typeof zipFile === "object") {
|
|
45
|
+
optionsParameter = zipFile;
|
|
46
|
+
}
|
|
47
|
+
const zip = new zip_1.Zip(optionsParameter);
|
|
43
48
|
zip.addFolder(folder);
|
|
44
|
-
|
|
49
|
+
if (typeof zipFile === "string") {
|
|
50
|
+
return zip.archive(zipFile);
|
|
51
|
+
}
|
|
52
|
+
return zip.archive();
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
* Extract the zip file to the specified location.
|
|
48
|
-
* @param zipFile
|
|
49
|
-
* @param targetFolder
|
|
50
|
-
* @param options
|
|
51
|
-
*/
|
|
52
|
-
function extract(zipFile, targetFolder, options) {
|
|
54
|
+
function extract(zipFileOrBuffer, targetFolder, options) {
|
|
53
55
|
const unzip = new unzip_1.Unzip(options);
|
|
54
|
-
|
|
56
|
+
// biome-ignore lint/suspicious/noExplicitAny: <>
|
|
57
|
+
return unzip.extract(zipFileOrBuffer, targetFolder);
|
|
55
58
|
}
|
package/lib/unzip.d.ts
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import { Cancelable } from "./cancelable";
|
|
2
2
|
export interface IExtractOptions {
|
|
3
3
|
/**
|
|
4
|
-
* If it is `true`, the target directory will be deleted before
|
|
4
|
+
* If it is `true`, the target directory will be deleted before extraction.
|
|
5
5
|
* The default value is `false`.
|
|
6
6
|
*/
|
|
7
7
|
overwrite?: boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Extracts symbolic links as files on Windows. This value is only available on Windows and is ignored on other platforms.
|
|
10
10
|
* The default value is `true`.
|
|
11
11
|
*
|
|
12
12
|
* If `true`, the symlink in the zip will be extracted as a normal file on Windows.
|
|
13
13
|
*
|
|
14
|
-
* If `false`, the
|
|
14
|
+
* If `false`, the library will try to extract symlinks as real symlinks on Windows.
|
|
15
|
+
* This may fail with an `EPERM` error when the current process is not allowed to create symlinks.
|
|
15
16
|
*
|
|
16
|
-
* > ⚠**WARNING:** On Windows,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* > ⚠**WARNING:** On Windows, creating symbolic links may require administrator privileges,
|
|
18
|
+
* depending on system policy. If Windows Developer Mode is enabled, non-administrator
|
|
19
|
+
* processes can usually create symlinks as well.
|
|
19
20
|
*/
|
|
20
21
|
symlinkAsFileOnWindows?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Controls the creation phase of symlinks.
|
|
24
|
+
*
|
|
25
|
+
* `true`: Refuses to create any symlink whose target is outside the extraction root.
|
|
26
|
+
*
|
|
27
|
+
* `false`: Allows creating external symlinks. **Note:** Subsequent write operations to these
|
|
28
|
+
* links will still be intercepted by the separate AFWRITE security layer.
|
|
29
|
+
*
|
|
30
|
+
* The default value is `false`.
|
|
31
|
+
*/
|
|
32
|
+
safeSymlinksOnly?: boolean;
|
|
21
33
|
/**
|
|
22
34
|
* Called before an item is extracted.
|
|
23
35
|
* @param event
|
|
@@ -25,7 +37,7 @@ export interface IExtractOptions {
|
|
|
25
37
|
onEntry?: (event: IEntryEvent) => void;
|
|
26
38
|
}
|
|
27
39
|
/**
|
|
28
|
-
*
|
|
40
|
+
* Represents an event fired before an entry is extracted.
|
|
29
41
|
*/
|
|
30
42
|
export interface IEntryEvent {
|
|
31
43
|
/**
|
|
@@ -37,7 +49,7 @@ export interface IEntryEvent {
|
|
|
37
49
|
*/
|
|
38
50
|
readonly entryCount: number;
|
|
39
51
|
/**
|
|
40
|
-
*
|
|
52
|
+
* Prevents the current entry from being extracted.
|
|
41
53
|
*/
|
|
42
54
|
preventDefault(): void;
|
|
43
55
|
}
|
|
@@ -52,15 +64,25 @@ export declare class Unzip extends Cancelable {
|
|
|
52
64
|
constructor(options?: IExtractOptions | undefined);
|
|
53
65
|
private zipFile;
|
|
54
66
|
private token;
|
|
67
|
+
/**
|
|
68
|
+
* Extract the zip buffer to the specified location.
|
|
69
|
+
* @param zipBuffer
|
|
70
|
+
* @param targetFolder
|
|
71
|
+
*/
|
|
72
|
+
extract(zipBuffer: Buffer, targetFolder: string): Promise<void>;
|
|
55
73
|
/**
|
|
56
74
|
* Extract the zip file to the specified location.
|
|
57
75
|
* @param zipFile
|
|
58
76
|
* @param targetFolder
|
|
59
|
-
* @param options
|
|
60
77
|
*/
|
|
61
78
|
extract(zipFile: string, targetFolder: string): Promise<void>;
|
|
79
|
+
private prepareExtraction;
|
|
80
|
+
private processEntries;
|
|
81
|
+
private readNextEntry;
|
|
82
|
+
private createPromiseSettler;
|
|
83
|
+
private handleZipEntry;
|
|
62
84
|
/**
|
|
63
|
-
* Cancel
|
|
85
|
+
* Cancel extraction.
|
|
64
86
|
* If the cancel method is called after the extract is complete, nothing will happen.
|
|
65
87
|
*/
|
|
66
88
|
cancel(): void;
|
|
@@ -70,6 +92,7 @@ export declare class Unzip extends Cancelable {
|
|
|
70
92
|
private openZipFileStream;
|
|
71
93
|
private extractEntry;
|
|
72
94
|
private writeEntryToFile;
|
|
95
|
+
private readStreamContent;
|
|
73
96
|
private modeFromEntry;
|
|
74
97
|
private createSymlink;
|
|
75
98
|
private isOverwrite;
|