diff --git a/src/common/utils/escapeRegExp.ts b/src/common/utils/escapeRegExp.ts new file mode 100644 index 0000000000..dbf10e4bfb --- /dev/null +++ b/src/common/utils/escapeRegExp.ts @@ -0,0 +1,5 @@ +// Helper to sanitize / escape special chars for passing to RegExp-constructor + +export function escapeRegExp(str: string) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index b9b9bbb7ad..e9fa3b2772 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -16,3 +16,4 @@ export * from "./singleton"; export * from "./openExternal"; export * from "./rectify-array"; export * from "./downloadFile"; +export * from "./escapeRegExp"; diff --git a/src/extensions/extension-discovery.ts b/src/extensions/extension-discovery.ts index bb1d1db420..33d8f2833d 100644 --- a/src/extensions/extension-discovery.ts +++ b/src/extensions/extension-discovery.ts @@ -16,7 +16,7 @@ export interface InstalledExtension { } const logModule = "[EXTENSION-DISCOVERY]"; -const manifestFilename = "package.json"; +export const manifestFilename = "package.json"; /** * Returns true if the lstat is for a directory-like file (e.g. isDirectory or symbolic link) diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 2d260652ec..1514425de5 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -17,10 +17,10 @@ import { PageLayout } from "../layout/page-layout"; import { Clipboard } from "../clipboard"; import logger from "../../../main/logger"; import { extensionLoader } from "../../../extensions/extension-loader"; -import { extensionDiscovery } from "../../../extensions/extension-discovery"; +import { extensionDiscovery, manifestFilename } from "../../../extensions/extension-discovery"; import { LensExtensionManifest, sanitizeExtensionName } from "../../../extensions/lens-extension"; import { Notifications } from "../notifications"; -import { downloadFile } from "../../../common/utils"; +import { downloadFile, escapeRegExp } from "../../../common/utils"; import { extractTar, readFileFromTar } from "../../../common/utils/tar"; import { docsUrl } from "../../../common/vars"; @@ -142,14 +142,16 @@ export class Extensions extends React.Component { } async validatePackage(filePath: string): Promise { + const manifestMatcher = RegExp(String.raw`^(\w+\/)?${escapeRegExp(manifestFilename)}$`); + const packageJson: Buffer = await readFileFromTar(filePath, { + notFoundMessage: `Invalid extension package, ${manifestFilename} not found`, // tarball from npm contains single root folder "package/*" - fileMatcher: (path: string) => !!path.match(/(\w+\/)?package\.json$/), - notFoundMessage: "Invalid extension, package.json not found", + fileMatcher: (path: string) => !!path.match(manifestMatcher), }); const manifest: LensExtensionManifest = JSON.parse(packageJson.toString("utf8")); if (!manifest.lens && !manifest.renderer) { - throw `package.json must specify "main" and/or "renderer" fields`; + throw `${manifestFilename} must specify "main" and/or "renderer" fields`; } return manifest; }