zip-lib 0.7.2 → 1.0.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/unzip.js CHANGED
@@ -1,358 +1,358 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Unzip = void 0;
4
- const yauzl = require("yauzl");
5
- const exfs = require("./fs");
6
- const fs_1 = require("fs");
7
- const path = require("path");
8
- const util = require("./util");
9
- const cancelable_1 = require("./cancelable");
10
- class EntryEvent {
11
- /**
12
- *
13
- */
14
- constructor(_entryCount) {
15
- this._entryCount = _entryCount;
16
- this._isPrevented = false;
17
- }
18
- get entryName() {
19
- return this._entryName;
20
- }
21
- set entryName(name) {
22
- this._entryName = name;
23
- }
24
- get entryCount() {
25
- return this._entryCount;
26
- }
27
- get isPrevented() {
28
- return this._isPrevented;
29
- }
30
- preventDefault() {
31
- this._isPrevented = true;
32
- }
33
- reset() {
34
- this._isPrevented = false;
35
- }
36
- }
37
- class EntryContext {
38
- constructor(_targetFolder, _realTargetFolder, symlinkAsFileOnWindows) {
39
- this._targetFolder = _targetFolder;
40
- this._realTargetFolder = _realTargetFolder;
41
- this.symlinkAsFileOnWindows = symlinkAsFileOnWindows;
42
- this._symlinkFileNames = [];
43
- }
44
- get decodeEntryFileName() {
45
- return this._decodeEntryFileName;
46
- }
47
- set decodeEntryFileName(name) {
48
- this._decodeEntryFileName = name;
49
- }
50
- get targetFolder() {
51
- return this._targetFolder;
52
- }
53
- get realTargetFolder() {
54
- return this._realTargetFolder;
55
- }
56
- get symlinkFileNames() {
57
- return this._symlinkFileNames;
58
- }
59
- getFilePath() {
60
- return path.join(this.targetFolder, this.decodeEntryFileName);
61
- }
62
- async isOutsideTargetFolder(tpath) {
63
- if (this.symlinkFileNames.length === 0) {
64
- return false;
65
- }
66
- if (process.platform === "win32" &&
67
- this.symlinkAsFileOnWindows) {
68
- return false;
69
- }
70
- for (const fileName of this.symlinkFileNames) {
71
- if (tpath.includes(fileName)) {
72
- const realFilePath = await util.realpath(tpath);
73
- if (realFilePath.indexOf(this.realTargetFolder) !== 0) {
74
- return true;
75
- }
76
- }
77
- }
78
- return false;
79
- }
80
- }
81
- /**
82
- * Extract the zip file.
83
- */
84
- class Unzip extends cancelable_1.Cancelable {
85
- /**
86
- *
87
- */
88
- constructor(options) {
89
- super();
90
- this.options = options;
91
- }
92
- /**
93
- * Extract the zip file to the specified location.
94
- * @param zipFile
95
- * @param targetFolder
96
- * @param options
97
- */
98
- async extract(zipFile, targetFolder) {
99
- let extractedEntriesCount = 0;
100
- const token = new cancelable_1.CancellationToken();
101
- this.token = token;
102
- if (this.isOverwrite()) {
103
- await exfs.rimraf(targetFolder);
104
- }
105
- if (token.isCancelled) {
106
- return Promise.reject(this.canceledError());
107
- }
108
- await exfs.ensureFolder(targetFolder);
109
- const realTargetFolder = await util.realpath(targetFolder);
110
- const zfile = await this.openZip(zipFile, token);
111
- this.zipFile = zfile;
112
- zfile.readEntry();
113
- return new Promise((c, e) => {
114
- let anyError = null;
115
- const total = zfile.entryCount;
116
- zfile.once("error", (err) => {
117
- this.closeZip();
118
- e(this.wrapError(err, token.isCancelled));
119
- });
120
- zfile.once("close", () => {
121
- this.zipFile = null;
122
- if (anyError) {
123
- e(this.wrapError(anyError, token.isCancelled));
124
- }
125
- else {
126
- if (token.isCancelled) {
127
- e(this.canceledError());
128
- }
129
- // If the zip content is empty, it will not receive the `zfile.on("entry")` event.
130
- else if (total === 0) {
131
- c(void 0);
132
- }
133
- }
134
- });
135
- // Because openZip is an asynchronous method, openZip may not be completed when calling cancel,
136
- // so we need to check if it has been canceled after the openZip method returns.
137
- if (token.isCancelled) {
138
- this.closeZip();
139
- return;
140
- }
141
- const entryContext = new EntryContext(targetFolder, realTargetFolder, this.symlinkToFile());
142
- const entryEvent = new EntryEvent(total);
143
- zfile.on("entry", async (entry) => {
144
- // use UTF-8 in all situations
145
- // see https://github.com/thejoshwolfe/yauzl/issues/84
146
- const rawName = entry.fileName.toString("utf8");
147
- // allow backslash
148
- const fileName = rawName.replace(/\\/g, "/");
149
- // Because `decodeStrings` is `false`, we need to manually verify the entryname
150
- // see https://github.com/thejoshwolfe/yauzl#validatefilenamefilename
151
- const errorMessage = yauzl.validateFileName(fileName);
152
- if (errorMessage != null) {
153
- anyError = new Error(errorMessage);
154
- this.closeZip();
155
- e(anyError);
156
- return;
157
- }
158
- entryEvent.entryName = fileName;
159
- this.onEntryCallback(entryEvent);
160
- entryContext.decodeEntryFileName = fileName;
161
- try {
162
- if (entryEvent.isPrevented) {
163
- entryEvent.reset();
164
- zfile.readEntry();
165
- }
166
- else {
167
- await this.handleEntry(zfile, entry, entryContext, token);
168
- }
169
- extractedEntriesCount++;
170
- if (extractedEntriesCount === total) {
171
- c();
172
- }
173
- }
174
- catch (error) {
175
- anyError = this.wrapError(error, token.isCancelled);
176
- this.closeZip();
177
- e(anyError);
178
- }
179
- });
180
- });
181
- }
182
- /**
183
- * Cancel decompression.
184
- * If the cancel method is called after the extract is complete, nothing will happen.
185
- */
186
- cancel() {
187
- if (this.token) {
188
- this.token.cancel();
189
- this.token = null;
190
- }
191
- this.closeZip();
192
- }
193
- closeZip() {
194
- if (this.zipFile) {
195
- this.zipFile.close();
196
- this.zipFile = null;
197
- }
198
- }
199
- openZip(zipFile, token) {
200
- return new Promise((c, e) => {
201
- yauzl.open(zipFile, {
202
- lazyEntries: true,
203
- // see https://github.com/thejoshwolfe/yauzl/issues/84
204
- decodeStrings: false
205
- }, (err, zfile) => {
206
- if (err) {
207
- e(this.wrapError(err, token.isCancelled));
208
- }
209
- else {
210
- c(zfile);
211
- }
212
- });
213
- });
214
- }
215
- async handleEntry(zfile, entry, entryContext, token) {
216
- if (/\/$/.test(entryContext.decodeEntryFileName)) {
217
- // Directory file names end with '/'.
218
- // Note that entires for directories themselves are optional.
219
- // An entry's fileName implicitly requires its parent directories to exist.
220
- await exfs.ensureFolder(entryContext.getFilePath());
221
- zfile.readEntry();
222
- }
223
- else {
224
- // file entry
225
- await this.extractEntry(zfile, entry, entryContext, token);
226
- }
227
- }
228
- openZipFileStream(zfile, entry, token) {
229
- return new Promise((c, e) => {
230
- zfile.openReadStream(entry, (err, readStream) => {
231
- if (err) {
232
- e(this.wrapError(err, token.isCancelled));
233
- }
234
- else {
235
- c(readStream);
236
- }
237
- });
238
- });
239
- }
240
- async extractEntry(zfile, entry, entryContext, token) {
241
- const filePath = entryContext.getFilePath();
242
- const fileDir = path.dirname(filePath);
243
- await exfs.ensureFolder(fileDir);
244
- const outside = await entryContext.isOutsideTargetFolder(fileDir);
245
- if (outside) {
246
- const error = new Error(`Refuse to write file outside "${entryContext.targetFolder}", file: "${filePath}"`);
247
- error.name = "AFWRITE";
248
- return Promise.reject(error);
249
- }
250
- const readStream = await this.openZipFileStream(zfile, entry, token);
251
- await this.writeEntryToFile(readStream, entry, entryContext, token);
252
- zfile.readEntry();
253
- }
254
- async writeEntryToFile(readStream, entry, entryContext, token) {
255
- let fileStream;
256
- token.onCancelled(() => {
257
- if (fileStream) {
258
- readStream.unpipe(fileStream);
259
- fileStream.destroy(this.canceledError());
260
- }
261
- });
262
- return new Promise(async (c, e) => {
263
- try {
264
- const filePath = entryContext.getFilePath();
265
- const mode = this.modeFromEntry(entry);
266
- // see https://unix.stackexchange.com/questions/193465/what-file-mode-is-a-symlink
267
- const isSymlink = ((mode & 0o170000) === 0o120000);
268
- readStream.once("error", (err) => {
269
- e(this.wrapError(err, token.isCancelled));
270
- });
271
- if (isSymlink) {
272
- entryContext.symlinkFileNames.push(entryContext.decodeEntryFileName);
273
- }
274
- if (isSymlink && !this.symlinkToFile()) {
275
- let linkContent = "";
276
- readStream.on("data", (chunk) => {
277
- if (chunk instanceof String) {
278
- linkContent += chunk;
279
- }
280
- else {
281
- linkContent += chunk.toString();
282
- }
283
- });
284
- readStream.once("end", () => {
285
- this.createSymlink(linkContent, filePath).then(c, e);
286
- });
287
- }
288
- else {
289
- fileStream = fs_1.createWriteStream(filePath, { mode });
290
- fileStream.once("close", () => c());
291
- fileStream.once("error", (err) => {
292
- e(this.wrapError(err, token.isCancelled));
293
- });
294
- readStream.pipe(fileStream);
295
- }
296
- }
297
- catch (error) {
298
- e(this.wrapError(error, token.isCancelled));
299
- }
300
- });
301
- }
302
- modeFromEntry(entry) {
303
- const attr = entry.externalFileAttributes >> 16 || 33188;
304
- return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
305
- .map(mask => attr & mask)
306
- .reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
307
- }
308
- async createSymlink(linkContent, des) {
309
- let linkType = "file";
310
- if (process.platform === 'win32') {
311
- if (/\/$/.test(linkContent)) {
312
- linkType = "dir";
313
- }
314
- else {
315
- let targetPath = linkContent;
316
- if (!path.isAbsolute(linkContent)) {
317
- targetPath = path.join(path.dirname(des), linkContent);
318
- }
319
- try {
320
- const stat = await util.stat(targetPath);
321
- if (stat.isDirectory()) {
322
- linkType = "dir";
323
- }
324
- }
325
- catch (error) {
326
- // ignore
327
- }
328
- }
329
- }
330
- await util.symlink(linkContent, des, linkType);
331
- }
332
- isOverwrite() {
333
- if (this.options &&
334
- this.options.overwrite) {
335
- return true;
336
- }
337
- return false;
338
- }
339
- onEntryCallback(event) {
340
- if (this.options && this.options.onEntry) {
341
- this.options.onEntry(event);
342
- }
343
- }
344
- symlinkToFile() {
345
- let symlinkToFile = false;
346
- if (process.platform === "win32") {
347
- if (this.options &&
348
- this.options.symlinkAsFileOnWindows === false) {
349
- symlinkToFile = false;
350
- }
351
- else {
352
- symlinkToFile = true;
353
- }
354
- }
355
- return symlinkToFile;
356
- }
357
- }
358
- exports.Unzip = Unzip;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Unzip = void 0;
4
+ const yauzl = require("yauzl");
5
+ const exfs = require("./fs");
6
+ const fs_1 = require("fs");
7
+ const path = require("path");
8
+ const util = require("./util");
9
+ const cancelable_1 = require("./cancelable");
10
+ class EntryEvent {
11
+ /**
12
+ *
13
+ */
14
+ constructor(_entryCount) {
15
+ this._entryCount = _entryCount;
16
+ this._isPrevented = false;
17
+ }
18
+ get entryName() {
19
+ return this._entryName;
20
+ }
21
+ set entryName(name) {
22
+ this._entryName = name;
23
+ }
24
+ get entryCount() {
25
+ return this._entryCount;
26
+ }
27
+ get isPrevented() {
28
+ return this._isPrevented;
29
+ }
30
+ preventDefault() {
31
+ this._isPrevented = true;
32
+ }
33
+ reset() {
34
+ this._isPrevented = false;
35
+ }
36
+ }
37
+ class EntryContext {
38
+ constructor(_targetFolder, _realTargetFolder, symlinkAsFileOnWindows) {
39
+ this._targetFolder = _targetFolder;
40
+ this._realTargetFolder = _realTargetFolder;
41
+ this.symlinkAsFileOnWindows = symlinkAsFileOnWindows;
42
+ this._symlinkFileNames = [];
43
+ }
44
+ get decodeEntryFileName() {
45
+ return this._decodeEntryFileName;
46
+ }
47
+ set decodeEntryFileName(name) {
48
+ this._decodeEntryFileName = name;
49
+ }
50
+ get targetFolder() {
51
+ return this._targetFolder;
52
+ }
53
+ get realTargetFolder() {
54
+ return this._realTargetFolder;
55
+ }
56
+ get symlinkFileNames() {
57
+ return this._symlinkFileNames;
58
+ }
59
+ getFilePath() {
60
+ return path.join(this.targetFolder, this.decodeEntryFileName);
61
+ }
62
+ async isOutsideTargetFolder(tpath) {
63
+ if (this.symlinkFileNames.length === 0) {
64
+ return false;
65
+ }
66
+ if (process.platform === "win32" &&
67
+ this.symlinkAsFileOnWindows) {
68
+ return false;
69
+ }
70
+ for (const fileName of this.symlinkFileNames) {
71
+ if (tpath.includes(fileName)) {
72
+ const realFilePath = await util.realpath(tpath);
73
+ if (realFilePath.indexOf(this.realTargetFolder) !== 0) {
74
+ return true;
75
+ }
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Extract the zip file.
83
+ */
84
+ class Unzip extends cancelable_1.Cancelable {
85
+ /**
86
+ *
87
+ */
88
+ constructor(options) {
89
+ super();
90
+ this.options = options;
91
+ }
92
+ /**
93
+ * Extract the zip file to the specified location.
94
+ * @param zipFile
95
+ * @param targetFolder
96
+ * @param options
97
+ */
98
+ async extract(zipFile, targetFolder) {
99
+ let extractedEntriesCount = 0;
100
+ const token = new cancelable_1.CancellationToken();
101
+ this.token = token;
102
+ if (this.isOverwrite()) {
103
+ await exfs.rimraf(targetFolder);
104
+ }
105
+ if (token.isCancelled) {
106
+ return Promise.reject(this.canceledError());
107
+ }
108
+ await exfs.ensureFolder(targetFolder);
109
+ const realTargetFolder = await util.realpath(targetFolder);
110
+ const zfile = await this.openZip(zipFile, token);
111
+ this.zipFile = zfile;
112
+ zfile.readEntry();
113
+ return new Promise((c, e) => {
114
+ let anyError = null;
115
+ const total = zfile.entryCount;
116
+ zfile.once("error", (err) => {
117
+ this.closeZip();
118
+ e(this.wrapError(err, token.isCancelled));
119
+ });
120
+ zfile.once("close", () => {
121
+ this.zipFile = null;
122
+ if (anyError) {
123
+ e(this.wrapError(anyError, token.isCancelled));
124
+ }
125
+ else {
126
+ if (token.isCancelled) {
127
+ e(this.canceledError());
128
+ }
129
+ // If the zip content is empty, it will not receive the `zfile.on("entry")` event.
130
+ else if (total === 0) {
131
+ c(void 0);
132
+ }
133
+ }
134
+ });
135
+ // Because openZip is an asynchronous method, openZip may not be completed when calling cancel,
136
+ // so we need to check if it has been canceled after the openZip method returns.
137
+ if (token.isCancelled) {
138
+ this.closeZip();
139
+ return;
140
+ }
141
+ const entryContext = new EntryContext(targetFolder, realTargetFolder, this.symlinkToFile());
142
+ const entryEvent = new EntryEvent(total);
143
+ zfile.on("entry", async (entry) => {
144
+ // use UTF-8 in all situations
145
+ // see https://github.com/thejoshwolfe/yauzl/issues/84
146
+ const rawName = entry.fileName.toString("utf8");
147
+ // allow backslash
148
+ const fileName = rawName.replace(/\\/g, "/");
149
+ // Because `decodeStrings` is `false`, we need to manually verify the entryname
150
+ // see https://github.com/thejoshwolfe/yauzl#validatefilenamefilename
151
+ const errorMessage = yauzl.validateFileName(fileName);
152
+ if (errorMessage != null) {
153
+ anyError = new Error(errorMessage);
154
+ this.closeZip();
155
+ e(anyError);
156
+ return;
157
+ }
158
+ entryEvent.entryName = fileName;
159
+ this.onEntryCallback(entryEvent);
160
+ entryContext.decodeEntryFileName = fileName;
161
+ try {
162
+ if (entryEvent.isPrevented) {
163
+ entryEvent.reset();
164
+ zfile.readEntry();
165
+ }
166
+ else {
167
+ await this.handleEntry(zfile, entry, entryContext, token);
168
+ }
169
+ extractedEntriesCount++;
170
+ if (extractedEntriesCount === total) {
171
+ c();
172
+ }
173
+ }
174
+ catch (error) {
175
+ anyError = this.wrapError(error, token.isCancelled);
176
+ this.closeZip();
177
+ e(anyError);
178
+ }
179
+ });
180
+ });
181
+ }
182
+ /**
183
+ * Cancel decompression.
184
+ * If the cancel method is called after the extract is complete, nothing will happen.
185
+ */
186
+ cancel() {
187
+ if (this.token) {
188
+ this.token.cancel();
189
+ this.token = null;
190
+ }
191
+ this.closeZip();
192
+ }
193
+ closeZip() {
194
+ if (this.zipFile) {
195
+ this.zipFile.close();
196
+ this.zipFile = null;
197
+ }
198
+ }
199
+ openZip(zipFile, token) {
200
+ return new Promise((c, e) => {
201
+ yauzl.open(zipFile, {
202
+ lazyEntries: true,
203
+ // see https://github.com/thejoshwolfe/yauzl/issues/84
204
+ decodeStrings: false
205
+ }, (err, zfile) => {
206
+ if (err) {
207
+ e(this.wrapError(err, token.isCancelled));
208
+ }
209
+ else {
210
+ c(zfile);
211
+ }
212
+ });
213
+ });
214
+ }
215
+ async handleEntry(zfile, entry, entryContext, token) {
216
+ if (/\/$/.test(entryContext.decodeEntryFileName)) {
217
+ // Directory file names end with '/'.
218
+ // Note that entires for directories themselves are optional.
219
+ // An entry's fileName implicitly requires its parent directories to exist.
220
+ await exfs.ensureFolder(entryContext.getFilePath());
221
+ zfile.readEntry();
222
+ }
223
+ else {
224
+ // file entry
225
+ await this.extractEntry(zfile, entry, entryContext, token);
226
+ }
227
+ }
228
+ openZipFileStream(zfile, entry, token) {
229
+ return new Promise((c, e) => {
230
+ zfile.openReadStream(entry, (err, readStream) => {
231
+ if (err) {
232
+ e(this.wrapError(err, token.isCancelled));
233
+ }
234
+ else {
235
+ c(readStream);
236
+ }
237
+ });
238
+ });
239
+ }
240
+ async extractEntry(zfile, entry, entryContext, token) {
241
+ const filePath = entryContext.getFilePath();
242
+ const fileDir = path.dirname(filePath);
243
+ await exfs.ensureFolder(fileDir);
244
+ const outside = await entryContext.isOutsideTargetFolder(fileDir);
245
+ if (outside) {
246
+ const error = new Error(`Refuse to write file outside "${entryContext.targetFolder}", file: "${filePath}"`);
247
+ error.name = "AFWRITE";
248
+ return Promise.reject(error);
249
+ }
250
+ const readStream = await this.openZipFileStream(zfile, entry, token);
251
+ await this.writeEntryToFile(readStream, entry, entryContext, token);
252
+ zfile.readEntry();
253
+ }
254
+ async writeEntryToFile(readStream, entry, entryContext, token) {
255
+ let fileStream;
256
+ token.onCancelled(() => {
257
+ if (fileStream) {
258
+ readStream.unpipe(fileStream);
259
+ fileStream.destroy(this.canceledError());
260
+ }
261
+ });
262
+ return new Promise(async (c, e) => {
263
+ try {
264
+ const filePath = entryContext.getFilePath();
265
+ const mode = this.modeFromEntry(entry);
266
+ // see https://unix.stackexchange.com/questions/193465/what-file-mode-is-a-symlink
267
+ const isSymlink = ((mode & 0o170000) === 0o120000);
268
+ readStream.once("error", (err) => {
269
+ e(this.wrapError(err, token.isCancelled));
270
+ });
271
+ if (isSymlink) {
272
+ entryContext.symlinkFileNames.push(entryContext.decodeEntryFileName);
273
+ }
274
+ if (isSymlink && !this.symlinkToFile()) {
275
+ let linkContent = "";
276
+ readStream.on("data", (chunk) => {
277
+ if (chunk instanceof String) {
278
+ linkContent += chunk;
279
+ }
280
+ else {
281
+ linkContent += chunk.toString();
282
+ }
283
+ });
284
+ readStream.once("end", () => {
285
+ this.createSymlink(linkContent, filePath).then(c, e);
286
+ });
287
+ }
288
+ else {
289
+ fileStream = (0, fs_1.createWriteStream)(filePath, { mode });
290
+ fileStream.once("close", () => c());
291
+ fileStream.once("error", (err) => {
292
+ e(this.wrapError(err, token.isCancelled));
293
+ });
294
+ readStream.pipe(fileStream);
295
+ }
296
+ }
297
+ catch (error) {
298
+ e(this.wrapError(error, token.isCancelled));
299
+ }
300
+ });
301
+ }
302
+ modeFromEntry(entry) {
303
+ const attr = entry.externalFileAttributes >> 16 || 33188;
304
+ return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
305
+ .map(mask => attr & mask)
306
+ .reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
307
+ }
308
+ async createSymlink(linkContent, des) {
309
+ let linkType = "file";
310
+ if (process.platform === 'win32') {
311
+ if (/\/$/.test(linkContent)) {
312
+ linkType = "dir";
313
+ }
314
+ else {
315
+ let targetPath = linkContent;
316
+ if (!path.isAbsolute(linkContent)) {
317
+ targetPath = path.join(path.dirname(des), linkContent);
318
+ }
319
+ try {
320
+ const stat = await util.stat(targetPath);
321
+ if (stat.isDirectory()) {
322
+ linkType = "dir";
323
+ }
324
+ }
325
+ catch (error) {
326
+ // ignore
327
+ }
328
+ }
329
+ }
330
+ await util.symlink(linkContent, des, linkType);
331
+ }
332
+ isOverwrite() {
333
+ if (this.options &&
334
+ this.options.overwrite) {
335
+ return true;
336
+ }
337
+ return false;
338
+ }
339
+ onEntryCallback(event) {
340
+ if (this.options && this.options.onEntry) {
341
+ this.options.onEntry(event);
342
+ }
343
+ }
344
+ symlinkToFile() {
345
+ let symlinkToFile = false;
346
+ if (process.platform === "win32") {
347
+ if (this.options &&
348
+ this.options.symlinkAsFileOnWindows === false) {
349
+ symlinkToFile = false;
350
+ }
351
+ else {
352
+ symlinkToFile = true;
353
+ }
354
+ }
355
+ return symlinkToFile;
356
+ }
357
+ }
358
+ exports.Unzip = Unzip;