diff --git a/packages/amplify-cli-core/API.md b/packages/amplify-cli-core/API.md index 935143587ee..c5f833aab56 100644 --- a/packages/amplify-cli-core/API.md +++ b/packages/amplify-cli-core/API.md @@ -820,6 +820,9 @@ export class ExportPathValidationError extends AmplifyError { constructor(errMessage?: string); } +// @public (undocumented) +export const extract: any; + // @public (undocumented) export function fancy(message?: string): void; diff --git a/packages/amplify-cli-core/package.json b/packages/amplify-cli-core/package.json index 6b3272dd9ba..f13290394dd 100644 --- a/packages/amplify-cli-core/package.json +++ b/packages/amplify-cli-core/package.json @@ -57,7 +57,8 @@ "semver": "^7.5.4", "typescript-json-schema": "~0.52.0", "which": "^2.0.2", - "yaml": "^2.2.2" + "yaml": "^2.2.2", + "yauzl": "^3.1.3" }, "devDependencies": { "@aws-amplify/amplify-function-plugin-interface": "1.10.3", diff --git a/packages/amplify-cli-core/src/extractZip.js b/packages/amplify-cli-core/src/extractZip.js new file mode 100644 index 00000000000..46feb293fc3 --- /dev/null +++ b/packages/amplify-cli-core/src/extractZip.js @@ -0,0 +1,158 @@ +/* eslint-disable no-bitwise */ +/* eslint-disable spellcheck/spell-checker */ +/** + * This file is copied from https://github.com/max-mapper/extract-zip + * and turned off creating symlinks on the machine where archive is extracted + */ +const { createWriteStream, promises: fs } = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const stream = require('stream'); +const yauzl = require('yauzl'); + +const openZip = promisify(yauzl.open); +const pipeline = promisify(stream.pipeline); + +class Extractor { + constructor(zipPath, opts) { + this.zipPath = zipPath; + this.opts = opts; + } + + async extract() { + this.zipfile = await openZip(this.zipPath, { lazyEntries: true }); + this.canceled = false; + + return new Promise((resolve, reject) => { + this.zipfile.on('error', (err) => { + this.canceled = true; + reject(err); + }); + this.zipfile.readEntry(); + + this.zipfile.on('close', () => { + if (!this.canceled) { + resolve(); + } + }); + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.zipfile.on('entry', async (entry) => { + if (this.canceled) { + return; + } + + if (entry.fileName.startsWith('__MACOSX/')) { + this.zipfile.readEntry(); + return; + } + + const destDir = path.dirname(path.join(this.opts.dir, entry.fileName)); + + try { + await fs.mkdir(destDir, { recursive: true }); + + const canonicalDestDir = await fs.realpath(destDir); + const relativeDestDir = path.relative(this.opts.dir, canonicalDestDir); + + if (relativeDestDir.split(path.sep).includes('..')) { + throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`); + } + + await this.extractEntry(entry); + this.zipfile.readEntry(); + } catch (err) { + this.canceled = true; + this.zipfile.close(); + reject(err); + } + }); + }); + } + + async extractEntry(entry) { + /* istanbul ignore if */ + if (this.canceled) { + return; + } + + if (this.opts.onEntry) { + this.opts.onEntry(entry, this.zipfile); + } + + const dest = path.join(this.opts.dir, entry.fileName); + + // convert external file attr int into a fs stat mode int + const mode = (entry.externalFileAttributes >> 16) & 0xffff; + // check if it's a symlink or dir (using stat mode constants) + const IFMT = 61440; + const IFDIR = 16384; + const IFLNK = 40960; + const symlink = (mode & IFMT) === IFLNK; + let isDir = (mode & IFMT) === IFDIR; + + // Failsafe, borrowed from jsZip + if (!isDir && entry.fileName.endsWith('/')) { + isDir = true; + } + + // check for windows weird way of specifying a directory + // https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566 + const madeBy = entry.versionMadeBy >> 8; + if (!isDir) isDir = madeBy === 0 && entry.externalFileAttributes === 16; + + const procMode = this.getExtractedMode(mode, isDir) & 0o777; + + // always ensure folders are created + const destDir = isDir ? dest : path.dirname(dest); + + const mkdirOptions = { recursive: true }; + if (isDir) { + mkdirOptions.mode = procMode; + } + await fs.mkdir(destDir, mkdirOptions); + if (isDir) return; + + const readStream = await promisify(this.zipfile.openReadStream.bind(this.zipfile))(entry); + + if (!symlink) { + await pipeline(readStream, createWriteStream(dest, { mode: procMode })); + } + } + + getExtractedMode(entryMode, isDir) { + let mode = entryMode; + // Set defaults, if necessary + if (mode === 0) { + if (isDir) { + if (this.opts.defaultDirMode) { + mode = parseInt(this.opts.defaultDirMode, 10); + } + + if (!mode) { + mode = 0o755; + } + } else { + if (this.opts.defaultFileMode) { + mode = parseInt(this.opts.defaultFileMode, 10); + } + + if (!mode) { + mode = 0o644; + } + } + } + + return mode; + } +} + +module.exports = async function (zipPath, opts) { + if (!path.isAbsolute(opts.dir)) { + throw new Error('Target directory is expected to be absolute'); + } + + await fs.mkdir(opts.dir, { recursive: true }); + opts.dir = await fs.realpath(opts.dir); + return new Extractor(zipPath, opts).extract(); +}; diff --git a/packages/amplify-cli-core/src/index.ts b/packages/amplify-cli-core/src/index.ts index 19c1882230e..0fd92bdc425 100644 --- a/packages/amplify-cli-core/src/index.ts +++ b/packages/amplify-cli-core/src/index.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-cycle */ import * as ExeInfo from './exeInfo'; -export { ExeInfo }; +const extract = require('./extractZip'); +export { ExeInfo, extract }; export * from './banner-message'; export * from './category-interfaces'; export * from './cfnUtilities'; diff --git a/packages/amplify-cli-core/tsconfig.json b/packages/amplify-cli-core/tsconfig.json index 43fb5103aee..a8a2f58a00c 100644 --- a/packages/amplify-cli-core/tsconfig.json +++ b/packages/amplify-cli-core/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./lib", "rootDir": "./src", + "allowJs": true, "useUnknownInCatchVariables": false } } diff --git a/packages/amplify-e2e-tests/package.json b/packages/amplify-e2e-tests/package.json index 99c9c9829c4..00fbe441d87 100644 --- a/packages/amplify-e2e-tests/package.json +++ b/packages/amplify-e2e-tests/package.json @@ -48,7 +48,6 @@ "dotenv": "^8.2.0", "esm": "^3.2.25", "execa": "^5.1.1", - "extract-zip": "^2.0.1", "fs-extra": "^8.1.0", "get-port": "^5.1.1", "glob": "^8.0.3", diff --git a/packages/amplify-e2e-tests/src/__tests__/diagnose.test.ts b/packages/amplify-e2e-tests/src/__tests__/diagnose.test.ts index 08a256e4029..8b882cb61c8 100644 --- a/packages/amplify-e2e-tests/src/__tests__/diagnose.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/diagnose.test.ts @@ -10,10 +10,10 @@ import { diagnoseSendReport, diagnoseSendReport_ZipFailed, } from '@aws-amplify/amplify-e2e-core'; +import { extract } from '@aws-amplify/amplify-cli-core'; import * as fs from 'fs-extra'; import * as path from 'path'; import glob from 'glob'; -import extract from 'extract-zip'; const PARAMETERS_JSON = 'parameters.json'; const BUILD = 'build'; diff --git a/packages/amplify-provider-awscloudformation/package.json b/packages/amplify-provider-awscloudformation/package.json index d22174c1103..2d0a32670b7 100644 --- a/packages/amplify-provider-awscloudformation/package.json +++ b/packages/amplify-provider-awscloudformation/package.json @@ -48,7 +48,6 @@ "constructs": "^10.0.5", "cors": "^2.8.5", "deep-diff": "^1.0.2", - "extract-zip": "^2.0.1", "folder-hash": "^4.0.2", "fs-extra": "^8.1.0", "glob": "^7.2.0", diff --git a/packages/amplify-provider-awscloudformation/src/attach-backend.ts b/packages/amplify-provider-awscloudformation/src/attach-backend.ts index fd56ab3586d..bc89b22ff79 100644 --- a/packages/amplify-provider-awscloudformation/src/attach-backend.ts +++ b/packages/amplify-provider-awscloudformation/src/attach-backend.ts @@ -2,10 +2,9 @@ import aws from 'aws-sdk'; import fs from 'fs-extra'; import path from 'path'; import glob from 'glob'; -import extract from 'extract-zip'; import inquirer from 'inquirer'; import _ from 'lodash'; -import { exitOnNextTick, pathManager, PathConstants, AmplifyError } from '@aws-amplify/amplify-cli-core'; +import { exitOnNextTick, pathManager, PathConstants, AmplifyError, extract } from '@aws-amplify/amplify-cli-core'; import * as configurationManager from './configuration-manager'; import { getConfiguredAmplifyClient } from './aws-utils/aws-amplify'; import { checkAmplifyServiceIAMPermission } from './amplify-service-permission-check'; diff --git a/packages/amplify-provider-awscloudformation/src/download-api-models.ts b/packages/amplify-provider-awscloudformation/src/download-api-models.ts index 2d6e1f29831..aed23fc633e 100644 --- a/packages/amplify-provider-awscloudformation/src/download-api-models.ts +++ b/packages/amplify-provider-awscloudformation/src/download-api-models.ts @@ -1,6 +1,5 @@ -import { $TSAny, $TSContext, $TSObject, AmplifyError, AmplifyFrontend, pathManager } from '@aws-amplify/amplify-cli-core'; +import { $TSAny, $TSContext, $TSObject, AmplifyError, AmplifyFrontend, pathManager, extract } from '@aws-amplify/amplify-cli-core'; import { printer } from '@aws-amplify/amplify-prompts'; -import extract from 'extract-zip'; import * as fs from 'fs-extra'; import sequential from 'promise-sequential'; import { APIGateway } from './aws-utils/aws-apigw'; diff --git a/packages/amplify-provider-awscloudformation/src/zip-util.ts b/packages/amplify-provider-awscloudformation/src/zip-util.ts index 1e597a1dd3b..83611f580eb 100644 --- a/packages/amplify-provider-awscloudformation/src/zip-util.ts +++ b/packages/amplify-provider-awscloudformation/src/zip-util.ts @@ -1,5 +1,4 @@ -import { AmplifyFault } from '@aws-amplify/amplify-cli-core'; -import extract from 'extract-zip'; +import { AmplifyFault, extract } from '@aws-amplify/amplify-cli-core'; import fs from 'fs-extra'; import path from 'path'; import { S3 } from './aws-utils/aws-s3'; diff --git a/yarn.lock b/yarn.lock index 7c33234d005..e75440d920a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -440,6 +440,7 @@ __metadata: uuid: ^8.3.2 which: ^2.0.2 yaml: ^2.2.2 + yauzl: ^3.1.3 languageName: unknown linkType: soft @@ -824,7 +825,6 @@ __metadata: constructs: ^10.0.5 cors: ^2.8.5 deep-diff: ^1.0.2 - extract-zip: ^2.0.1 folder-hash: ^4.0.2 fs-extra: ^8.1.0 glob: ^7.2.0 @@ -12858,15 +12858,6 @@ __metadata: languageName: node linkType: hard -"@types/yauzl@npm:^2.9.1": - version: 2.9.2 - resolution: "@types/yauzl@npm:2.9.2" - dependencies: - "@types/node": "*" - checksum: 0b4a5db8b7b01e94d9c5f48b5043c22553313e9f31918a9755a4bc7875be92a99bf5f11aa260016f553410be517ce64f5a99b14226d878d65d6d1696869a08b1 - languageName: node - linkType: hard - "@types/zen-observable@npm:0.8.0": version: 0.8.0 resolution: "@types/zen-observable@npm:0.8.0" @@ -13752,7 +13743,6 @@ __metadata: dotenv: ^8.2.0 esm: ^3.2.25 execa: ^5.1.1 - extract-zip: ^2.0.1 fs-extra: ^8.1.0 get-port: ^5.1.1 glob: ^8.0.3 @@ -19213,23 +19203,6 @@ __metadata: languageName: node linkType: hard -"extract-zip@npm:^2.0.1": - version: 2.0.1 - resolution: "extract-zip@npm:2.0.1" - dependencies: - "@types/yauzl": ^2.9.1 - debug: ^4.1.1 - get-stream: ^5.1.0 - yauzl: ^2.10.0 - dependenciesMeta: - "@types/yauzl": - optional: true - bin: - extract-zip: cli.js - checksum: 9afbd46854aa15a857ae0341a63a92743a7b89c8779102c3b4ffc207516b2019337353962309f85c66ee3d9092202a83cdc26dbf449a11981272038443974aee - languageName: node - linkType: hard - "extsprintf@npm:1.3.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" @@ -19412,15 +19385,6 @@ __metadata: languageName: node linkType: hard -"fd-slicer@npm:~1.1.0": - version: 1.1.0 - resolution: "fd-slicer@npm:1.1.0" - dependencies: - pend: ~1.2.0 - checksum: 304dd70270298e3ffe3bcc05e6f7ade2511acc278bc52d025f8918b48b6aa3b77f10361bddfadfe2a28163f7af7adbdce96f4d22c31b2f648ba2901f0c5fc20e - languageName: node - linkType: hard - "fecha@npm:^4.2.0": version: 4.2.1 resolution: "fecha@npm:4.2.1" @@ -33040,13 +33004,13 @@ node-pty@beta: languageName: node linkType: hard -"yauzl@npm:^2.10.0": - version: 2.10.0 - resolution: "yauzl@npm:2.10.0" +"yauzl@npm:^3.1.3": + version: 3.1.3 + resolution: "yauzl@npm:3.1.3" dependencies: buffer-crc32: ~0.2.3 - fd-slicer: ~1.1.0 - checksum: f265002af7541b9ec3589a27f5fb8f11cf348b53cc15e2751272e3c062cd73f3e715bc72d43257de71bbaecae446c3f1b14af7559e8ab0261625375541816422 + pend: ~1.2.0 + checksum: e04a2567860e1337798cd2570d776b4040520b20660e7ec5dfcce24b8be2b134d6a5ae835804a0186b1a58cb8b1741b37eaa6a86f7546b6219b62a265dbaf3fc languageName: node linkType: hard