Skip to content

Commit e70b7d2

Browse files
authored
fix: resolve bug in unarchive method (#144)
1 parent 336f5b7 commit e70b7d2

File tree

5 files changed

+144
-80
lines changed

5 files changed

+144
-80
lines changed

package-lock.json

Lines changed: 114 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"connect-livereload": "^0.6.1",
6262
"execa": "^9.6.0",
6363
"express": "^4.17.1",
64+
"extract-zip": "^2.0.1",
6465
"file-type": "^15.0.0",
6566
"form-data": "^4.0.2",
6667
"fs-extra": "^11.3.0",
@@ -74,7 +75,6 @@
7475
"tmp-promise": "^3.0.3",
7576
"treeify": "^1.1.0",
7677
"tslib": "^2.8.1",
77-
"unzipper": "^0.12.3",
7878
"uuid": "^11.1.0",
7979
"yaml": "^2.8.0"
8080
},

src/controllers/portal/quickstart.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { SDKClient } from "../../client-utils/sdk-client.js";
1212
import {
1313
createTempDirectory,
1414
isValidUrl,
15-
unzipFile,
1615
getMessageInRedColor,
1716
clearDirectory,
1817
deleteFile
@@ -22,8 +21,13 @@ import { AuthorizationError, GetValidationParams } from "../../types/api/validat
2221
import { metadataFileContent, staticPortalRepoUrl } from "../../config/env.js";
2322
import { PortalQuickstartPrompts } from "../../prompts/portal/quickstart.js";
2423
import { AuthenticationError } from "../../types/utils.js";
24+
import { ZipService } from "../../infrastructure/zip-service.js";
25+
import { FilePath } from "../../types/file/filePath.js";
26+
import { DirectoryPath } from "../../types/file/directoryPath.js";
27+
import { FileName } from "../../types/file/fileName.js";
2528

2629
export class PortalQuickstartController {
30+
private readonly zipService = new ZipService();
2731
private readonly specUrl =
2832
"https://github.com/apimatic/static-portal-workflow/blob/master/spec/openapi.json";
2933

@@ -110,10 +114,10 @@ export class PortalQuickstartController {
110114
}
111115
} else {
112116
specPath = path.normalize(specPath);
113-
const fileType = await filetype.fromFile(specPath);
117+
const fileType = await filetype.fromFile(specPath);
114118

115119
if (fileType?.ext === "zip") {
116-
await unzipFile(fs.createReadStream(specPath), tempSpecDir);
120+
await this.zipService.unArchive(new FilePath(new DirectoryPath(path.dirname(specPath)), new FileName(path.basename(specPath))), new DirectoryPath(tempSpecDir));
117121
} else {
118122
const destinationPath = path.join(tempSpecDir, path.basename(specPath));
119123
await fsExtra.copy(specPath, destinationPath);

src/infrastructure/zip-service.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from "fs";
22
import archiver from "archiver";
3-
import unzipper from "unzipper";
3+
import extract from "extract-zip";
44
import { DirectoryPath } from "../types/file/directoryPath.js";
55
import { FilePath } from "../types/file/filePath.js";
66

@@ -19,12 +19,27 @@ export class ZipService {
1919
});
2020
}
2121

22-
async unArchive(sourceFile: FilePath, destinationDirectory: DirectoryPath): Promise<void> {
23-
return new Promise((resolve, reject) => {
24-
fs.createReadStream(sourceFile.toString())
25-
.pipe(unzipper.Extract({ path: destinationDirectory.toString() }))
26-
.on("close", () => resolve())
27-
.on("error", (err) => reject(err));
22+
public async unArchive(sourceFile: FilePath, destinationDirectory: DirectoryPath): Promise<void> {
23+
const MAX_FILES = 100_000;
24+
const MAX_SIZE = 1_000_000_000; // 1 GB
25+
let fileCount = 0;
26+
let totalSize = 0;
27+
28+
await extract(sourceFile.toString(), {
29+
dir: destinationDirectory.toString(),
30+
onEntry: function (entry) {
31+
fileCount++;
32+
if (fileCount > MAX_FILES) {
33+
throw new Error("Reached max. file count");
34+
}
35+
// The uncompressedSize comes from the zip headers, so it might not be trustworthy.
36+
// Alternatively, calculate the size from the readStream.
37+
let entrySize = entry.uncompressedSize;
38+
totalSize += entrySize;
39+
if (totalSize > MAX_SIZE) {
40+
throw new Error("Reached max. size");
41+
}
42+
}
2843
});
2944
}
3045
}

src/utils/utils.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,11 @@ import fs from "fs";
33
import fsExtra from "fs-extra";
44
import os from "os";
55
import archiver from "archiver";
6-
import unzipper from "unzipper";
76
import stripTags from "striptags";
87
import colors from "picocolors";
98

109
import { loggers, ValidationMessages } from "../types/utils.js";
1110

12-
export const unzipFile = (stream: NodeJS.ReadableStream, destination: string) => {
13-
return new Promise((resolve, reject) => {
14-
const extractStream = unzipper.Extract({ path: destination });
15-
16-
stream
17-
.pipe(extractStream)
18-
.on("error", (error: Error) => reject(new Error("Error during extraction: " + error.message)));
19-
20-
extractStream.on("close", () => resolve("Extracted"));
21-
extractStream.on("error", (error: Error) => reject(new Error("Error during extraction: " + error.message)));
22-
});
23-
};
24-
2511
export const createTempDirectory = async () => {
2612
return fs.mkdtempSync(path.join(os.tmpdir(), "apimatic-cli-"));
2713
};

0 commit comments

Comments
 (0)