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/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 files = await fs.readdir(folder);
22
- for (const item of files) {
23
- const file = path.join(folder, item);
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(file);
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
- fileType = "dir";
44
- }
45
- else {
46
- if (stat.isSymbolicLink()) {
47
- isSymbolicLink = true;
48
- // If the path is a link, we must instead use fs.stat() to find out if the
49
- // link is a directory or not because lstat will always return the stat of
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: fileType,
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 Promise.resolve({
79
+ return {
69
80
  isDirectory: true,
70
81
  isSymbolicLink: false,
71
- });
82
+ };
72
83
  }
73
84
  try {
74
- const result = await mkdir(folder);
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
- return Promise.reject(error);
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
- return Promise.reject(new Error(`Refuse to recursively delete root, path: "${target}"`));
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
- else {
114
- // chmod as needed to allow for unlink
115
- const mode = stat.mode;
116
- if (!(mode & 128)) {
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
- return Promise.reject(error);
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
- const fileStat = await fs.lstat(folder);
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 zip.
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 zip.
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
- const zip = new zip_1.Zip(options);
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
- return zip.archive(zipFile);
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
- const zip = new zip_1.Zip(options);
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
- return zip.archive(zipFile);
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
- return unzip.extract(zipFile, targetFolder);
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 extract.
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
- * Extract symbolic links as files on Windows. This value is only available on Windows and ignored on other platforms.
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 symlink in the zip will be extracted as a symlink correctly on Windows, but an `EPERM` error will be thrown under non-administrators.
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, the default security policy allows only administrators to create symbolic links.
17
- * If you set `symlinkAsFileOnWindows` to `false` and the zip contains symlink,
18
- * be sure to run the code under the administrator, otherwise an `EPERM` error will be thrown.
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
- * The IEntryEvent interface represents an event that an entry is about to be extracted.
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
- * Prevent extracting current entry.
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 decompression.
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;