import { strToU8, zipSync } from "fflate"; import { describe, expect, it } from "vitest"; import { analyzePackage } from "../../src/package/analyze"; import { parseStaticXml } from "../../src/package/xml"; function archive(name: string, entries: Record): File { const bytes = zipSync(entries, { level: 0 }); return new File([new Uint8Array(bytes).buffer], name, { type: "application/zip", }); } describe("compound package analysis", () => { it("detects OOXML and resolves local and external relationships", async () => { const file = archive("sample.docx", { "[Content_Types].xml": strToU8( '', ), "_rels/.rels": strToU8( '', ), "word/document.xml": strToU8(""), }); const result = await analyzePackage(file); expect(result.kind).toBe("docx"); expect(result.relationships).toHaveLength(2); expect(result.relationships[0]).toMatchObject({ mode: "internal", resolvedPath: "word/document.xml", exists: true, }); expect(result.relationships[1]).toMatchObject({ mode: "external" }); expect( result.diagnostics.some( (item) => item.code === "MISSING_RELATIONSHIP_TARGET", ), ).toBe(false); }); it("reports missing relationship targets and path collisions", async () => { const file = archive("unsafe.docx", { "[Content_Types].xml": strToU8(""), "_rels/.rels": strToU8( '', ), "Report.txt": strToU8("a"), "report.TXT": strToU8("b"), }); const result = await analyzePackage(file); expect( result.diagnostics.some( (item) => item.code === "MISSING_RELATIONSHIP_TARGET", ), ).toBe(true); expect( result.diagnostics.filter((item) => item.code === "DUPLICATE_PATH"), ).toHaveLength(2); }); it("detects EPUB metadata and missing manifest parts", async () => { const file = archive("book.epub", { mimetype: strToU8("application/epub+zip"), "META-INF/container.xml": strToU8( '', ), "EPUB/package.opf": strToU8( 'Local bookAda', ), "EPUB/chapter.xhtml": strToU8("hello"), }); const result = await analyzePackage(file); expect(result.kind).toBe("epub"); expect(result.metadata).toContainEqual({ section: "EPUB", name: "Title", value: "Local book", }); expect(result.diagnostics).toContainEqual( expect.objectContaining({ code: "MISSING_MANIFEST_ITEM", path: "EPUB/lost.png", }), ); }); it("adapts ODF and JAR manifests without executing their contents", async () => { const odf = await analyzePackage( archive("sheet.ods", { mimetype: strToU8("application/vnd.oasis.opendocument.spreadsheet"), "META-INF/manifest.xml": strToU8( '', ), "content.xml": strToU8(""), }), ); expect(odf.kind).toBe("ods"); expect(odf.diagnostics).toContainEqual( expect.objectContaining({ code: "MISSING_ODF_ENTRY", path: "Pictures/missing.png", }), ); const jar = await analyzePackage( archive("tool.jar", { "META-INF/MANIFEST.MF": strToU8( "Manifest-Version: 1.0\r\nMain-Class: example.Main\r\nClass-Path: lib/a.jar lib/b.jar\r\n", ), "META-INF/LOCAL.SF": strToU8( "Signature-Version: 1.0\r\nSHA-256-Digest-Manifest: abc\r\n", ), "META-INF/LOCAL.RSA": new Uint8Array([1, 2, 3]), "META-INF/LICENSE.txt": strToU8("Example licence evidence"), "package.json": strToU8( JSON.stringify({ dependencies: { alpha: "^1.2.0" }, devDependencies: { beta: "2.0.0" }, license: "MIT", }), ), "example/Main.class": new Uint8Array([0xca, 0xfe, 0xba, 0xbe]), }), ); expect(jar.kind).toBe("jar"); expect(jar.metadata).toContainEqual({ section: "JAR manifest", name: "Main-Class", value: "example.Main", }); expect(jar.dependencies).toEqual( expect.arrayContaining([ expect.objectContaining({ name: "alpha", scope: "runtime" }), expect.objectContaining({ name: "lib/a.jar", scope: "JAR Class-Path declaration", }), ]), ); expect(jar.licenses).toEqual( expect.arrayContaining([ expect.objectContaining({ name: "MIT", source: "package.json" }), expect.objectContaining({ source: "META-INF/LICENSE.txt" }), ]), ); expect(jar.signatures).toEqual( expect.arrayContaining([ expect.objectContaining({ path: "META-INF/LOCAL.SF", state: "complete-pair", declaredDigests: ["SHA-256"], }), ]), ); }); it("adapts APK and both WebExtension identities with bounded inventories", async () => { const apk = await analyzePackage( archive("app.apk", { "AndroidManifest.xml": new Uint8Array([3, 0, 8, 0]), "classes.dex": strToU8("dex\n035\0"), "lib/arm64-v8a/liblocal.so": new Uint8Array([0x7f, 0x45, 0x4c, 0x46]), "META-INF/LOCAL.RSA": new Uint8Array([1, 2, 3]), }), ); expect(apk.kind).toBe("apk"); expect(apk.signatures[0]?.format).toMatch(/APK\/JAR v1/iu); const firefox = await analyzePackage( archive("addon.xpi", { "manifest.json": strToU8( JSON.stringify({ manifest_version: 3, name: "Local add-on", version: "1.0.0", browser_specific_settings: { gecko: { id: "local@example.test" } }, icons: { 48: "icon.png" }, }), ), }), ); expect(firefox.kind).toBe("firefox-extension"); expect(firefox.diagnostics).toContainEqual( expect.objectContaining({ code: "MISSING_EXTENSION_ASSET", path: "icon.png", }), ); const chrome = await analyzePackage( archive("addon.zip", { "manifest.json": strToU8( JSON.stringify({ manifest_version: 3, name: "Local extension", version: "1.0.0", }), ), }), ); expect(chrome.kind).toBe("chrome-extension"); }); it("rejects DTD/entity-bearing metadata", () => { expect(() => parseStaticXml(']>&y;'), ).toThrow(/DTD and entity/iu); }); });