From fdc36ad36b4ce9b95a0cb8bce14224005915261f Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Tue, 19 Dec 2023 13:07:31 +0100 Subject: [PATCH] Switch to `yauzl` for CodeQL CLI This switches the CodeQL CLI download to `yauzl` instead of `unzipper`. There should be no changes in behavior. I tested this manually on Insiders by removing the distribution directory and this successfully downloaded and extracted the CLI. --- .../ql-vscode/src/codeql-cli/distribution.ts | 4 +- .../ql-vscode/src/common/distribution.ts | 28 --------- extensions/ql-vscode/src/common/unzip.ts | 63 +++++++++++++++++++ .../ql-vscode/test/vscode-tests/ensureCli.ts | 4 +- 4 files changed, 67 insertions(+), 32 deletions(-) diff --git a/extensions/ql-vscode/src/codeql-cli/distribution.ts b/extensions/ql-vscode/src/codeql-cli/distribution.ts index eade0850402..da1d61b5460 100644 --- a/extensions/ql-vscode/src/codeql-cli/distribution.ts +++ b/extensions/ql-vscode/src/codeql-cli/distribution.ts @@ -15,7 +15,6 @@ import { import { codeQlLauncherName, deprecatedCodeQlLauncherName, - extractZipArchive, getRequiredAssetName, } from "../common/distribution"; import { @@ -26,6 +25,7 @@ import { showAndLogErrorMessage, showAndLogWarningMessage, } from "../common/logging"; +import { unzipToDirectory } from "../common/unzip"; /** * distribution.ts @@ -420,7 +420,7 @@ class ExtensionSpecificDistributionManager { void extLogger.log( `Extracting CodeQL CLI to ${this.getDistributionStoragePath()}`, ); - await extractZipArchive(archivePath, this.getDistributionStoragePath()); + await unzipToDirectory(archivePath, this.getDistributionStoragePath()); } finally { await remove(tmpDirectory); } diff --git a/extensions/ql-vscode/src/common/distribution.ts b/extensions/ql-vscode/src/common/distribution.ts index 94a19c195f9..0e5d9212c74 100644 --- a/extensions/ql-vscode/src/common/distribution.ts +++ b/extensions/ql-vscode/src/common/distribution.ts @@ -1,7 +1,4 @@ import { platform } from "os"; -import { Open } from "unzipper"; -import { join } from "path"; -import { pathExists, chmod } from "fs-extra"; /** * Get the name of the codeql cli installation we prefer to install, based on our current platform. @@ -19,31 +16,6 @@ export function getRequiredAssetName(): string { } } -export async function extractZipArchive( - archivePath: string, - outPath: string, -): Promise { - const archive = await Open.file(archivePath); - await archive.extract({ - concurrency: 4, - path: outPath, - }); - // Set file permissions for extracted files - await Promise.all( - archive.files.map(async (file) => { - // Only change file permissions if within outPath (path.join normalises the path) - const extractedPath = join(outPath, file.path); - if ( - extractedPath.indexOf(outPath) !== 0 || - !(await pathExists(extractedPath)) - ) { - return Promise.resolve(); - } - return chmod(extractedPath, file.externalFileAttributes >>> 16); - }), - ); -} - export function codeQlLauncherName(): string { return platform() === "win32" ? "codeql.exe" : "codeql"; } diff --git a/extensions/ql-vscode/src/common/unzip.ts b/extensions/ql-vscode/src/common/unzip.ts index acd24510fe7..64760922840 100644 --- a/extensions/ql-vscode/src/common/unzip.ts +++ b/extensions/ql-vscode/src/common/unzip.ts @@ -1,5 +1,8 @@ import { Entry as ZipEntry, open, Options as ZipOptions, ZipFile } from "yauzl"; import { Readable } from "stream"; +import { dirname, join } from "path"; +import { WriteStream } from "fs"; +import { createWriteStream, ensureDir } from "fs-extra"; // We can't use promisify because it picks up the wrong overload. export function openZip( @@ -82,3 +85,63 @@ export async function openZipBuffer( }); }); } + +async function copyStream( + readable: Readable, + writeStream: WriteStream, +): Promise { + return new Promise((resolve, reject) => { + readable.on("error", (err) => { + reject(err); + }); + readable.on("end", () => { + resolve(); + }); + + readable.pipe(writeStream); + }); +} + +export async function unzipToDirectory( + archivePath: string, + destinationPath: string, +): Promise { + const zipFile = await openZip(archivePath, { + autoClose: false, + strictFileNames: true, + lazyEntries: true, + }); + + try { + const entries = await readZipEntries(zipFile); + + for (const entry of entries) { + const path = join(destinationPath, entry.fileName); + + if (/\/$/.test(entry.fileName)) { + // Directory file names end with '/' + + await ensureDir(path); + } else { + // Ensure the directory exists + await ensureDir(dirname(path)); + + const readable = await openZipReadStream(zipFile, entry); + + let mode: number | undefined = entry.externalFileAttributes >>> 16; + if (mode <= 0) { + mode = undefined; + } + + const writeStream = createWriteStream(path, { + autoClose: true, + mode, + }); + + await copyStream(readable, writeStream); + } + } + } finally { + zipFile.close(); + } +} diff --git a/extensions/ql-vscode/test/vscode-tests/ensureCli.ts b/extensions/ql-vscode/test/vscode-tests/ensureCli.ts index b5c1f857dab..46c8ebada41 100644 --- a/extensions/ql-vscode/test/vscode-tests/ensureCli.ts +++ b/extensions/ql-vscode/test/vscode-tests/ensureCli.ts @@ -2,9 +2,9 @@ import { existsSync, createWriteStream, mkdirpSync } from "fs-extra"; import { normalize, join } from "path"; import { getRequiredAssetName, - extractZipArchive, codeQlLauncherName, } from "../../src/common/distribution"; +import { unzipToDirectory } from "../../src/common/unzip"; import fetch from "node-fetch"; import supportedCliVersions from "../../supported_cli_versions.json"; @@ -126,7 +126,7 @@ export async function ensureCli(useCli: boolean) { console.log(`Unzipping into '${unzipDir}'`); mkdirpSync(unzipDir); - await extractZipArchive(downloadedFilePath, unzipDir); + await unzipToDirectory(downloadedFilePath, unzipDir); console.log("Done."); } catch (e) { console.error("Failed to download CLI.");