UNPKG

meshcentral

Version:

Web based remote computer management server

107 lines (94 loc) 5.12 kB
// backup to zipfile module.exports.zipDirectory = async function (dirPath, outputZip, password = '', compression = 5) { try { const { ZipWriterStream } = require("@zip.js/zip.js"); const { readdir, stat } = require("fs/promises"); const path = require("path"); const { createReadStream, createWriteStream } = require("fs"); const { Readable } = require("stream"); const { pipeline } = require("stream/promises"); const zipStream = new ZipWriterStream({ ...(password ? { password, encryptionStrength: 3 } : {}), // 3 = AES-256 level: compression // msDosCompatible: true // to write EntryMetaData#externalFileAttributes in MS-DOS format for folder entries. Fixes Dir entries issue }); async function addFiles() { const entries = await readdir(dirPath, { recursive: true, withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(entry.parentPath, entry.name); const relPath = path.relative(dirPath, fullPath).replaceAll(path.sep, path.posix.sep); if (entry.isDirectory()) { await zipStream.zipWriter.add(relPath + '/', null); } else { await Readable.toWeb(createReadStream(fullPath)).pipeTo(zipStream.writable(relPath)); } // console.log ("zip addfile: " + entry); } } await Promise.all([ addFiles().then(() => zipStream.close()), pipeline(Readable.fromWeb(zipStream.readable), createWriteStream(outputZip)), ]); return { res: true, mes: 'Zip created succesfully' }; } catch (e) { // console.error(e); return { res: false, mes: e.message }; } } // restore zipfile // removePath: optional path to remove from destination path // First check archive on illegal paths (updirs and absolute paths), then extract module.exports.zipExtract = async function (zipPath, destPath, removePath = '', password = "") { try { const { ZipReader, Uint8ArrayReader } = require("@zip.js/zip.js"); const { createWriteStream } = require("fs"); const { readFile, mkdir } = require("fs/promises"); const path = require("path"); const { Readable } = require("stream"); const { pipeline } = require("stream/promises"); const zipReader = new ZipReader(new Uint8ArrayReader(await readFile(zipPath))); const entries = await zipReader.getEntries(); const resolveDestPath = path.resolve(destPath); // Archive validation: Abort the restore on any bad path before extraction const targets = []; // { entry, fullPath } cache the paths while we're at it for (const entry of entries) { // skip symlinks in unix-mode zips (upper 16 bits hold st_mode); not present in dos/win zips if (((entry.externalFileAttributes >>> 16) & 0xF000) === 0xA000) { continue; } // anchored prefix strip — must match what the extraction pass writes let name = entry.filename; if (removePath && name.startsWith(removePath)) name = name.slice(removePath.length); // reject any parent-dir token outright (split on both separators; segment match, not substring) if (name.split(/[/\\]/).includes('..')) { throw new Error('Aborting, nothing restored. Illegal path (parent traversal) in zip, entry: ' + entry.filename); } // reject absolute paths (posix root, or Windows drive / UNC) if (path.isAbsolute(name) || /^[a-zA-Z]:/.test(name) || name.startsWith('\\')) { throw new Error('Aborting, nothing restored. Illegal absolute path in zip, entry: ' + entry.filename); } // backstop: resolved target must stay inside destPath const fullPath = path.resolve(destPath, name); if (fullPath !== resolveDestPath && !fullPath.startsWith(resolveDestPath + path.sep)) { throw new Error('Illegal path in zip entry: ' + entry.filename); } targets.push({ entry, fullPath }); } // Validated, extract! for (const { entry, fullPath } of targets) { if (entry.directory) { await mkdir(fullPath, { recursive: true }); } else { await mkdir(path.dirname(fullPath), { recursive: true }); const { writable, readable } = new TransformStream(); await Promise.all([ entry.getData(writable, { password }), pipeline(Readable.fromWeb(readable), createWriteStream(fullPath)), ]); } } await zipReader.close(); return { res: true, mes: 'Extraction successful' }; } catch (e) { // console.error(e.message); return { res: false, mes: e.message }; } }