UNPKG

zip-lib

Version:

A zip and unzip library for Node.js.

287 lines (286 loc) 9.39 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.isOutside = isOutside; exports.realpath = realpath; exports.readdirp = readdirp; exports.getFileEntry = getFileEntry; exports.ensureFolder = ensureFolder; exports.pathExists = pathExists; exports.statFolder = statFolder; exports.rimraf = rimraf; exports.isRootPath = isRootPath; const fsSync = __importStar(require("node:fs")); const fs = __importStar(require("node:fs/promises")); const path = __importStar(require("node:path")); const util = __importStar(require("node:util")); /** * Checks if the target path is outside the specified base directory. * * @param basePath - The reference directory. * @param targetPath - The path to evaluate against the base. * @returns `true` if targetPath is outside of basePath. */ function isOutside(basePath, targetPath) { const absoluteBase = path.resolve(basePath); const absoluteTarget = path.resolve(targetPath); const relative = path.relative(absoluteBase, absoluteTarget); return relative.startsWith("..") || path.isAbsolute(relative); } async function realpath(target) { // fs.promises.realpath has a bug with long path on Windows. // https://github.com/nodejs/node/issues/51031 return util.promisify(fsSync.realpath)(target); } async function readdirp(folder) { const result = []; const filePaths = (await fs.readdir(folder)).map((item) => path.join(folder, item)); const entries = await Promise.all(filePaths.map((filePath) => getFileEntry(filePath))); for (const entry of entries) { if (!entry.isSymbolicLink && entry.type === "dir") { const subFiles = await readdirp(entry.path); if (subFiles.length > 0) { result.push(...subFiles); // If the folder is not empty, don't need to add the folder itself. continue; } } result.push(entry); } return result; } async function getFileEntry(target) { const stat = await fs.lstat(target); if (stat.isDirectory()) { return { path: target, isSymbolicLink: false, type: "dir", mtime: stat.mtime, mode: stat.mode, }; } if (!stat.isSymbolicLink()) { return { path: target, isSymbolicLink: false, type: "file", mtime: stat.mtime, mode: stat.mode, }; } // If the path is a link, we must instead use fs.stat() to find out if the // link is a directory or not because lstat will always return the stat of // the link which is always a file. try { const actualStat = await fs.stat(target); return { path: target, isSymbolicLink: true, type: actualStat.isDirectory() ? "dir" : "file", mtime: stat.mtime, mode: stat.mode, }; } catch (_error) { // link is broken, ignore error } // If the symlink is broken (points to a non-existent target), fs.stat() fails. // We fall back to fs.readlink() to inspect the literal target path string. const linkTarget = await fs.readlink(target); // Heuristic check: If the target path ends with a slash, we assume it's a directory. // Note: This is an approximation since many directory symlinks don't include a trailing slash. const fileType = linkTarget.endsWith("/") || linkTarget.endsWith("\\") ? "dir" : "file"; return { path: target, isSymbolicLink: true, type: fileType, mtime: stat.mtime, mode: stat.mode, }; } async function ensureFolder(folder) { // stop at root if (folder === path.dirname(folder)) { return { isDirectory: true, isSymbolicLink: false, }; } try { return await mkdir(folder); } catch (error) { // ENOENT: a parent folder does not exist yet, continue // to create the parent folder and then try again. if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") { await ensureFolder(path.dirname(folder)); return await mkdir(folder); } // Any other error throw error; } } async function pathExists(target) { try { await fs.access(target); return true; } catch (_error) { return false; } } async function statFolder(folder) { try { return await statExistingFolder(folder); } catch (error) { if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") { return undefined; } throw error; } } async function rimraf(target) { if (isRootPath(target)) { // refuse to recursively delete root throw new Error(`Refuse to recursively delete root, path: "${target}"`); } try { const stat = await fs.lstat(target); // Folder delete (recursive) - NOT for symbolic links though! if (stat.isDirectory() && !stat.isSymbolicLink()) { // Children const children = await fs.readdir(target); await Promise.all(children.map((child) => rimraf(path.join(target, child)))); // Folder await fs.rmdir(target); return; } // Single file delete const mode = stat.mode; if (!(mode & 128)) { // 128 === 0200 await fs.chmod(target, mode | 128); } await fs.unlink(target); } catch (error) { if (typeof error === "object" && error !== null && "code" in error && error.code !== "ENOENT") { throw error; } } } async function mkdir(folder) { try { await fs.mkdir(folder, 0o777); return { isDirectory: true, isSymbolicLink: false, }; } catch (error) { // ENOENT: a parent folder does not exist yet or folder name is invalid. if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") { throw error; } // Any other error: check if folder exists and // return normally in that case if its a folder try { return await statExistingFolder(folder); } catch (_statError) { throw error; // rethrow original error } } } async function statExistingFolder(folder) { const fileStat = await fs.lstat(folder); if (!fileStat.isSymbolicLink()) { if (!fileStat.isDirectory()) { throw new Error(`"${folder}" exists and is not a directory.`); } return { isDirectory: true, isSymbolicLink: false, }; } const realFilePath = await realpath(folder); const realFileStat = await fs.lstat(realFilePath); if (!realFileStat.isDirectory()) { throw new Error(`"${folder}" exists and is not a directory.`); } return { isDirectory: false, isSymbolicLink: true, realpath: realFilePath, }; } // "A" const charA = 65; // "Z" const charZ = 90; // "a" const chara = 97; // "z" const charz = 122; // ":" const charColon = 58; // "\" const charWinSep = 92; // "/" const charUnixSep = 47; function isDriveLetter(char0) { return (char0 >= charA && char0 <= charZ) || (char0 >= chara && char0 <= charz); } const winSep = "\\"; const unixSep = "/"; function isRootPath(target) { if (!target) { return false; } if (target === winSep || target === unixSep) { return true; } if (process.platform === "win32") { if (target.length > 3) { return false; } return (isDriveLetter(target.charCodeAt(0)) && target.charCodeAt(1) === charColon && (target.length === 2 || target.charCodeAt(2) === charWinSep || target.charCodeAt(2) === charUnixSep)); } return false; }