// 压缩包解压(沙箱内):zip 用 fflate,rar 用 node-unrar-js(WASM),7z 用 7z-wasm。 // WASM 库要读自己包里的 .wasm 文件:prepareArchives() 在撤销权限前预热(载入内存),之后只在内存里解压。 import * as SevenZipModule from "7z-wasm"; import { unzipSync } from "fflate"; import { createExtractorFromData } from "node-unrar-js"; /** 用到的 7z-wasm 接口(Emscripten 实例:内存文件系统 + callMain) */ interface Seven { FS: { writeFile(path: string, data: Uint8Array): void; readFile(path: string): Uint8Array; readdir(path: string): string[]; stat(path: string): { mode: number }; isDir(mode: number): boolean; }; callMain(args: string[]): unknown; } // 7z-wasm 是 CJS 包:tsc 与 deno check 对默认导出的理解不同,运行时取 default 或模块本身 const SevenZip = ((SevenZipModule as unknown as { default?: unknown }).default ?? SevenZipModule) as unknown as (opts: { print: (s: string) => void; printErr: (s: string) => void; }) => Promise; let seven: Seven | null = null; let warmed = false; export async function prepareArchives(): Promise { if (warmed) return; warmed = true; try { await createExtractorFromData({ data: new Uint8Array([0x52, 0x61, 0x72, 0x21]).buffer }); } catch { // 只为载入 WASM,内容无效报错是预期的 } seven = await SevenZip({ print: () => {}, printErr: () => {} }); } export type AcceptEntry = (name: string, size: number) => boolean; export function archiveKindOf(name: string): "zip" | "rar" | "7z" | null { const m = /\.(zip|rar|7z)$/i.exec(name); return m ? (m[1]!.toLowerCase() as "zip" | "rar" | "7z") : null; } /** 解出通过 accept 的条目;压缩包损坏 / 加密抛错 */ export async function extractArchive( kind: "zip" | "rar" | "7z", bytes: Uint8Array, accept: AcceptEntry, ): Promise> { if (kind === "zip") return unzipSync(bytes, { filter: (e) => !e.name.endsWith("/") && accept(e.name, e.originalSize) }); if (kind === "rar") { const ex = await createExtractorFromData({ data: bytes.slice().buffer }); const out: Record = {}; for (const f of ex.extract({ files: (h) => !h.flags.directory && accept(h.name, h.unpSize) }).files) if (f.extraction) out[f.fileHeader.name] = f.extraction; return out; } await prepareArchives(); const z = seven!; const dir = `/x${crypto.randomUUID().slice(0, 8)}`; z.FS.writeFile(`${dir}.7z`, bytes); // callMain 出错时抛 ExitStatus 或静默不写文件:两种都当成损坏处理 z.callMain(["x", `${dir}.7z`, `-o${dir}`, "-y", "-p-"]); try { z.FS.stat(dir); } catch { throw new Error("7z 解压失败"); } const out: Record = {}; const walk = (d: string, rel: string) => { for (const n of z.FS.readdir(d)) { if (n === "." || n === "..") continue; const p = `${d}/${n}`; const r = rel ? `${rel}/${n}` : n; if (z.FS.isDir(z.FS.stat(p).mode)) walk(p, r); else { const data = z.FS.readFile(p); if (accept(r, data.length)) out[r] = data; } } }; walk(dir, ""); return out; }