diff --git a/src/extract.ts b/src/extract.ts index 07c4472..1fd4814 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -41,6 +41,7 @@ export function extractZip(bytes: Buffer, targetDir: string): string[] { // A lemezre írás csak a teljes szűrés után történik, így ha valamelyik // korlát hibát dob, semmi nem kerül a célmappába. let total = 0; + const normalizedPaths: string[] = []; let entryCount = 0; const entries = unzipSync(new Uint8Array(bytes), { filter(info) { @@ -58,9 +59,22 @@ export function extractZip(bytes: Buffer, targetDir: string): string[] { if (total > MAX_TOTAL_BYTES) { throw new Error("A zip kibontott mérete meghaladja a 200 MB-os korlátot."); } + normalizedPaths.push( + posix.normalize(info.name.replace(/\\/g, "/")).normalize("NFC").toLowerCase() + ); return true; } }); + const targets = new Set(normalizedPaths); + const nestedConflict = normalizedPaths.some((name) => { + for (let parent = posix.dirname(name); parent !== "."; parent = posix.dirname(parent)) { + if (targets.has(parent)) return true; + } + return false; + }); + if (targets.size !== normalizedPaths.length || targets.has(".") || nestedConflict) { + throw new Error("Ütköző útvonalak a zipben, a kibontás leáll."); + } const written: string[] = []; for (const [name, data] of Object.entries(entries)) { diff --git a/test/extract.test.ts b/test/extract.test.ts index 52d9af8..13a29ff 100644 --- a/test/extract.test.ts +++ b/test/extract.test.ts @@ -74,6 +74,32 @@ function buildManyEntryZip(entryCount: number): Buffer { } describe("extractZip", () => { + it.each( + ( + [ + ["a", "a/b"], + ["A", "a"], + ["a/b", "a\\b"], + ["a/./b", "a/b"], + ["é", "e\u0301"] + ] as const + ).flatMap<[string, string]>(([a, b]) => [ + [a, b], + [b, a] + ]) + )("rejects conflicting %s and %s before writing", (a, b) => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "tarhely-conflict-")); + try { + const zip = Buffer.from( + zipSync({ "safe.txt": Buffer.from("ok"), [a]: Buffer.from("a"), [b]: Buffer.from("b") }) + ); + expect(() => extractZip(zip, tmpDir)).toThrow("Ütköző útvonalak"); + expect(fs.readdirSync(tmpDir)).toEqual([]); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }); + it("extracts zip entries to target directory and returns relative paths", () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "tarhely-test-")); const zipData = Buffer.from(