// Worker thread running 7-Zip (compiled to WASM) for ZIP/7Z/RAR archives. // 7-Zip calls are synchronous, so they run here to keep the server responsive. const { parentPort } = require('worker_threads'); const SevenZip = require('7z-wasm'); let sevenZip = null; const output = []; // The host filesystem is mounted at /host inside the WASM filesystem. const host = (p) => `/host${p}`; async function instance() { if (!sevenZip) { sevenZip = await SevenZip({ print: (l) => output.push(l), printErr: (l) => output.push(l) }); sevenZip.FS.mkdir('/host'); sevenZip.FS.mount(sevenZip.NODEFS, { root: '/' }, '/host'); } return sevenZip; } async function run(args) { const sz = await instance(); output.length = 0; let code; try { code = sz.callMain(args); } catch (err) { code = err && typeof err.status === 'number' ? err.status : 2; } return { code, lines: output.slice() }; } function errorText(lines) { const line = lines.find((l) => /error|wrong password|can ?not open|unsupported|headers/i.test(l)); return line ? line.trim() : null; } // Parses `7zz l -slt` into [{ path, size }] for files (folders are dropped). function parseListing(lines) { const start = lines.findIndex((l) => l.startsWith('----------')); if (start === -1) return null; const entries = []; let cur = {}; const flush = () => { const isFolder = cur.Folder === '+' || (cur.Attributes || '').startsWith('D'); if (cur.Path !== undefined && !isFolder) entries.push({ path: cur.Path, size: Number(cur.Size) || 0 }); cur = {}; }; for (const line of lines.slice(start + 1)) { if (!line.trim()) { flush(); continue; } const i = line.indexOf(' = '); if (i > 0) cur[line.slice(0, i)] = line.slice(i + 3); } flush(); return entries; } async function list(archive) { // A dummy password makes encrypted archives fail fast instead of prompting. const { code, lines } = await run(['l', '-slt', '-pprinthive', host(archive)]); const entries = parseListing(lines); if (code !== 0 || !entries) throw new Error(errorText(lines) || `7-Zip could not read the archive (exit ${code})`); return entries; } // Extracts the named entries (keeping their folders) under destDir. Callers // check which files actually arrived. async function extract(archive, entries, destDir) { const { code, lines } = await run( ['x', host(archive), `-o${host(destDir)}`, '-y', '-spd', '-bsp0', '-pprinthive', '--', ...entries]); return { code, error: code === 0 ? null : errorText(lines) }; } parentPort.on('message', async ({ id, op, archive, entries, destDir }) => { try { const result = op === 'list' ? await list(archive) : await extract(archive, entries, destDir); parentPort.postMessage({ id, result }); } catch (err) { parentPort.postMessage({ id, error: err.message }); } });