test(archive): share zip/tar fixture generation
This commit is contained in:
@@ -27,6 +27,26 @@ async function withArchiveCase(
|
||||
await run({ workDir, archivePath, extractDir });
|
||||
}
|
||||
|
||||
async function writePackageArchive(params: {
|
||||
ext: "zip" | "tar";
|
||||
workDir: string;
|
||||
archivePath: string;
|
||||
fileName: string;
|
||||
content: string;
|
||||
}) {
|
||||
if (params.ext === "zip") {
|
||||
const zip = new JSZip();
|
||||
zip.file(`package/${params.fileName}`, params.content);
|
||||
await fs.writeFile(params.archivePath, await zip.generateAsync({ type: "nodebuffer" }));
|
||||
return;
|
||||
}
|
||||
|
||||
const packageDir = path.join(params.workDir, "package");
|
||||
await fs.mkdir(packageDir, { recursive: true });
|
||||
await fs.writeFile(path.join(packageDir, params.fileName), params.content);
|
||||
await tar.c({ cwd: params.workDir, file: params.archivePath }, ["package"]);
|
||||
}
|
||||
|
||||
async function expectExtractedSizeBudgetExceeded(params: {
|
||||
archivePath: string;
|
||||
destDir: string;
|
||||
@@ -53,25 +73,36 @@ afterAll(async () => {
|
||||
|
||||
describe("archive utils", () => {
|
||||
it("detects archive kinds", () => {
|
||||
expect(resolveArchiveKind("/tmp/file.zip")).toBe("zip");
|
||||
expect(resolveArchiveKind("/tmp/file.tgz")).toBe("tar");
|
||||
expect(resolveArchiveKind("/tmp/file.tar.gz")).toBe("tar");
|
||||
expect(resolveArchiveKind("/tmp/file.tar")).toBe("tar");
|
||||
expect(resolveArchiveKind("/tmp/file.txt")).toBeNull();
|
||||
const cases = [
|
||||
{ input: "/tmp/file.zip", expected: "zip" },
|
||||
{ input: "/tmp/file.tgz", expected: "tar" },
|
||||
{ input: "/tmp/file.tar.gz", expected: "tar" },
|
||||
{ input: "/tmp/file.tar", expected: "tar" },
|
||||
{ input: "/tmp/file.txt", expected: null },
|
||||
] as const;
|
||||
for (const testCase of cases) {
|
||||
expect(resolveArchiveKind(testCase.input), testCase.input).toBe(testCase.expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("extracts zip archives", async () => {
|
||||
await withArchiveCase("zip", async ({ archivePath, extractDir }) => {
|
||||
const zip = new JSZip();
|
||||
zip.file("package/hello.txt", "hi");
|
||||
await fs.writeFile(archivePath, await zip.generateAsync({ type: "nodebuffer" }));
|
||||
|
||||
await extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 });
|
||||
const rootDir = await resolvePackedRootDir(extractDir);
|
||||
const content = await fs.readFile(path.join(rootDir, "hello.txt"), "utf-8");
|
||||
expect(content).toBe("hi");
|
||||
});
|
||||
});
|
||||
it.each([{ ext: "zip" as const }, { ext: "tar" as const }])(
|
||||
"extracts $ext archives",
|
||||
async ({ ext }) => {
|
||||
await withArchiveCase(ext, async ({ workDir, archivePath, extractDir }) => {
|
||||
await writePackageArchive({
|
||||
ext,
|
||||
workDir,
|
||||
archivePath,
|
||||
fileName: "hello.txt",
|
||||
content: "hi",
|
||||
});
|
||||
await extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 });
|
||||
const rootDir = await resolvePackedRootDir(extractDir);
|
||||
const content = await fs.readFile(path.join(rootDir, "hello.txt"), "utf-8");
|
||||
expect(content).toBe("hi");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects zip path traversal (zip slip)", async () => {
|
||||
await withArchiveCase("zip", async ({ archivePath, extractDir }) => {
|
||||
@@ -110,20 +141,6 @@ describe("archive utils", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("extracts tar archives", async () => {
|
||||
await withArchiveCase("tar", async ({ workDir, archivePath, extractDir }) => {
|
||||
const packageDir = path.join(workDir, "package");
|
||||
await fs.mkdir(packageDir, { recursive: true });
|
||||
await fs.writeFile(path.join(packageDir, "hello.txt"), "yo");
|
||||
await tar.c({ cwd: workDir, file: archivePath }, ["package"]);
|
||||
|
||||
await extractArchive({ archivePath, destDir: extractDir, timeoutMs: 5_000 });
|
||||
const rootDir = await resolvePackedRootDir(extractDir);
|
||||
const content = await fs.readFile(path.join(rootDir, "hello.txt"), "utf-8");
|
||||
expect(content).toBe("yo");
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects tar path traversal (zip slip)", async () => {
|
||||
await withArchiveCase("tar", async ({ workDir, archivePath, extractDir }) => {
|
||||
const insideDir = path.join(workDir, "inside");
|
||||
@@ -138,19 +155,26 @@ describe("archive utils", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects zip archives that exceed extracted size budget", async () => {
|
||||
await withArchiveCase("zip", async ({ archivePath, extractDir }) => {
|
||||
const zip = new JSZip();
|
||||
zip.file("package/big.txt", "x".repeat(64));
|
||||
await fs.writeFile(archivePath, await zip.generateAsync({ type: "nodebuffer" }));
|
||||
it.each([{ ext: "zip" as const }, { ext: "tar" as const }])(
|
||||
"rejects $ext archives that exceed extracted size budget",
|
||||
async ({ ext }) => {
|
||||
await withArchiveCase(ext, async ({ workDir, archivePath, extractDir }) => {
|
||||
await writePackageArchive({
|
||||
ext,
|
||||
workDir,
|
||||
archivePath,
|
||||
fileName: "big.txt",
|
||||
content: "x".repeat(64),
|
||||
});
|
||||
|
||||
await expectExtractedSizeBudgetExceeded({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
maxExtractedBytes: 32,
|
||||
await expectExtractedSizeBudgetExceeded({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
maxExtractedBytes: 32,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects archives that exceed archive size budget", async () => {
|
||||
await withArchiveCase("zip", async ({ archivePath, extractDir }) => {
|
||||
@@ -178,21 +202,6 @@ describe("archive utils", () => {
|
||||
await expect(resolvePackedRootDir(extractDir)).rejects.toThrow(/unexpected archive layout/i);
|
||||
});
|
||||
|
||||
it("rejects tar archives that exceed extracted size budget", async () => {
|
||||
await withArchiveCase("tar", async ({ workDir, archivePath, extractDir }) => {
|
||||
const packageDir = path.join(workDir, "package");
|
||||
await fs.mkdir(packageDir, { recursive: true });
|
||||
await fs.writeFile(path.join(packageDir, "big.txt"), "x".repeat(64));
|
||||
await tar.c({ cwd: workDir, file: archivePath }, ["package"]);
|
||||
|
||||
await expectExtractedSizeBudgetExceeded({
|
||||
archivePath,
|
||||
destDir: extractDir,
|
||||
maxExtractedBytes: 32,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects tar entries with absolute extraction paths", async () => {
|
||||
await withArchiveCase("tar", async ({ workDir, archivePath, extractDir }) => {
|
||||
const inputDir = path.join(workDir, "input");
|
||||
|
||||
Reference in New Issue
Block a user