Release Archive Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 09:34:59 +02:00
parent a49ec6b17a
commit fe578f46bd
47 changed files with 3337 additions and 253 deletions
+108 -1
View File
@@ -42,7 +42,7 @@ test("serves the release identity and hardened headers", async ({
const manifest = await request.get("/deep/nested/archive/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.archive-tools",
version: "0.1.0",
version: "0.2.0",
entry: "./",
requirements: { workers: false },
});
@@ -77,6 +77,51 @@ test("inspects, previews and safely repackages a ZIP locally", async ({
expect(external).toEqual([]);
});
test("shows validated RAR5 structural evidence without offering extraction", async ({
page,
}) => {
const external = await localOnly(page);
await page.goto("/deep/nested/archive/");
const name = new TextEncoder().encode("evidence.txt");
const archive = concatBytes(
new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00]),
rar5FixtureBlock(new Uint8Array([0x01, 0x00, 0x00])),
rar5FixtureBlock(
concatBytes(
new Uint8Array([
0x02,
0x02,
0x03,
0x00,
0x03,
0x00,
0x00,
0x00,
name.length,
]),
name,
),
new Uint8Array([1, 2, 3]),
),
rar5FixtureBlock(new Uint8Array([0x05, 0x00, 0x00])),
);
await page.getByLabel("Choose archive").setInputFiles({
name: "evidence.rar",
mimeType: "application/vnd.rar",
buffer: Buffer.from(archive),
});
await expect(page.getByText("Inspected 1 entries locally.")).toBeVisible();
await expect(
page.getByRole("row", { name: /evidence\.txt/iu }),
).toContainText("RAR entries are inventory-only");
await page.getByText("Structural inspection evidence").click();
await expect(page.getByText("3 verified")).toBeVisible();
await expect(
page.getByRole("checkbox", { name: "Select evidence.txt" }),
).toBeDisabled();
expect(external).toEqual([]);
});
test("blocks traversal and case-colliding ZIP paths", async ({ page }) => {
await page.goto("/deep/nested/archive/");
const archive = zipSync({
@@ -156,3 +201,65 @@ test("creates a TAR and compares archive inventories", async ({ page }) => {
page.getByRole("row", { name: /same\.txt same/u }),
).toBeVisible();
});
test("creates and opens an AES-256 ZIP with a memory-only password", async ({
page,
}) => {
await page.goto("/deep/nested/archive/");
await page.getByRole("button", { name: "Create" }).click();
await page.getByLabel("Files").setInputFiles({
name: "secret.txt",
mimeType: "text/plain",
buffer: Buffer.from("local secret"),
});
await page.getByLabel("Encryption").selectOption("aes-256");
await page.getByLabel("Password (memory only)").fill("correct horse");
const encryptedPromise = page.waitForEvent("download");
await page.getByRole("button", { name: "Create & download" }).click();
const encrypted = await encryptedPromise;
await page.getByRole("button", { name: "Inspect & extract" }).click();
await page.getByLabel("Choose archive").setInputFiles(await encrypted.path());
await expect(
page.getByRole("row", {
name: /secret\.txt.*AES-256.*password required/iu,
}),
).toBeVisible();
await page.getByLabel("ZIP password (memory only)").fill("correct horse");
await page.getByRole("button", { name: "secret.txt" }).click();
await expect(page.getByText("local secret")).toBeVisible();
});
function rar5FixtureBlock(
body: Uint8Array,
data = new Uint8Array(),
): Uint8Array {
const size = new Uint8Array([body.length]);
const crcInput = concatBytes(size, body);
const header = new Uint8Array(4 + crcInput.length);
new DataView(header.buffer).setUint32(0, fixtureCrc32(crcInput), true);
header.set(crcInput, 4);
return concatBytes(header, data);
}
function fixtureCrc32(input: Uint8Array): number {
let value = 0xffffffff;
for (const byte of input) {
value ^= byte;
for (let bit = 0; bit < 8; bit += 1)
value = (value >>> 1) ^ (value & 1 ? 0xedb88320 : 0);
}
return (value ^ 0xffffffff) >>> 0;
}
function concatBytes(...parts: Uint8Array[]): Uint8Array {
const output = new Uint8Array(
parts.reduce((total, part) => total + part.length, 0),
);
let offset = 0;
for (const part of parts) {
output.set(part, offset);
offset += part.length;
}
return output;
}
+18
View File
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";
test("keeps the primary workspace inside a narrow viewport", async ({
page,
}) => {
await page.goto("/deep/nested/archive/");
await expect(page.locator("main").first()).toBeVisible();
await expect(
page.locator("main .loading, main .workbench-loading"),
).toHaveCount(0);
const widths = await page.evaluate(() => ({
content: document.documentElement.scrollWidth,
viewport: document.documentElement.clientWidth,
}));
expect(widths.viewport).toBeLessThanOrEqual(430);
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
});