zip-lib
Version:
A zip and unzip library for Node.js.
287 lines (286 loc) • 9.39 kB
JavaScript
;
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;
}