// What the report routes serve: rangeResponse (the segment and video routes' // byte ranges) and deckPreviewFile (the one route that takes a path from the // client, confined to the deck's preview directory). // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; import { decodeProjectSegment, deckPreviewDir, feedPreviewDir, feedPreviewSrc, deckPreviewFile, deckPreviewSrc, encodeProjectSegment, postsPreviewDir, postsPreviewSrc, previewDirFor, rangeResponse, stampPreviewDir, stampPreviewSrc, } from "./serve.mjs"; const req = (range) => new Request("http://x/", range ? { headers: { range } } : {}); const bytes = async (res) => Buffer.from(await res.arrayBuffer()); test("rangeResponse: whole file, explicit, open-ended, suffix and clamped ranges", async () => { const dir = await mkdtemp(path.join(tmpdir(), "umtool-range-")); try { const data = Buffer.from(Array.from({ length: 1000 }, (_, i) => i % 251)); const abs = path.join(dir, "a.mp4"); await writeFile(abs, data); const file = { abs, size: data.length, headers: { "content-type": "video/mp4", "x-k": "v" } }; let res = rangeResponse(req(null), file); assert.equal(res.status, 200); assert.equal(res.headers.get("content-length"), "1000"); assert.equal(res.headers.get("content-type"), "video/mp4"); assert.equal(res.headers.get("x-k"), "v"); assert.deepEqual(await bytes(res), data); res = rangeResponse(req("bytes=0-99"), file); assert.equal(res.status, 206); assert.equal(res.headers.get("content-range"), "bytes 0-99/1000"); assert.equal(res.headers.get("content-length"), "100"); assert.equal(res.headers.get("x-k"), "v"); assert.deepEqual(await bytes(res), data.subarray(0, 100)); res = rangeResponse(req("bytes=900-"), file); assert.equal(res.status, 206); assert.equal(res.headers.get("content-range"), "bytes 900-999/1000"); assert.deepEqual(await bytes(res), data.subarray(900)); // A suffix range is the LAST n bytes, not an offset. res = rangeResponse(req("bytes=-100"), file); assert.equal(res.status, 206); assert.equal(res.headers.get("content-range"), "bytes 900-999/1000"); assert.deepEqual(await bytes(res), data.subarray(900)); res = rangeResponse(req("bytes=-5000"), file); assert.equal(res.headers.get("content-range"), "bytes 0-999/1000"); // An end past the file is clamped. res = rangeResponse(req("bytes=990-5000"), file); assert.equal(res.headers.get("content-range"), "bytes 990-999/1000"); assert.equal((await bytes(res)).length, 10); } finally { await rm(dir, { recursive: true, force: true }); } }); test("rangeResponse: unsatisfiable is a bare 416; unparseable is the whole file", async () => { const dir = await mkdtemp(path.join(tmpdir(), "umtool-range-")); try { const abs = path.join(dir, "a.mp4"); await writeFile(abs, Buffer.alloc(1000, 1)); const file = { abs, size: 1000, headers: { "content-type": "video/mp4" } }; for (const r of ["bytes=1000-", "bytes=5-2", "bytes=2000-3000"]) { const res = rangeResponse(req(r), file); assert.equal(res.status, 416, r); assert.equal(res.headers.get("content-range"), "bytes */1000"); assert.equal(res.headers.get("content-type"), null, "the 416 carries content-range only"); } for (const r of ["items=0-1", "bytes=0-1,5-6", "nonsense"]) { const res = rangeResponse(req(r), file); assert.equal(res.status, 200, r); assert.equal(res.headers.get("content-length"), "1000"); await res.arrayBuffer(); } } finally { await rm(dir, { recursive: true, force: true }); } }); test("project ids ride as one url segment and come back exactly", () => { for (const id of ["ferret-rescue", "folder/name", "a b/รง"]) { const seg = encodeProjectSegment(id); assert.match(seg, /^[A-Za-z0-9_-]+$/); assert.equal(decodeProjectSegment(seg), id); } for (const bad of ["", "..", "a/b", "a.b", null, undefined]) assert.equal(decodeProjectSegment(bad), null); assert.equal( deckPreviewSrc("folder/name", "sourced"), `/api/report/chrome/files/${encodeProjectSegment("folder/name")}/sourced/index.html`, ); assert.equal(deckPreviewDir("/p", "full"), path.join("/p", "out", "full", "chrome", "deck-preview")); }); test("deckPreviewFile: files inside the preview directory, and nothing else", async () => { const root = await mkdtemp(path.join(tmpdir(), "umtool-deckfiles-")); try { const project = path.join(root, "proj"); const dir = deckPreviewDir(project, "sourced"); await mkdir(path.join(dir, "assets"), { recursive: true }); await writeFile(path.join(dir, "index.html"), ""); await writeFile(path.join(dir, "assets", "gsap.min.js"), "x"); // Beside the preview: a build's own project, the manifest, and a secret // outside the project altogether. await mkdir(path.join(project, "out", "sourced", "chrome", "deck"), { recursive: true }); await writeFile(path.join(project, "out", "sourced", "chrome", "deck", "index.html"), "build"); await writeFile(path.join(project, "video.manifest.json"), "{}"); await writeFile(path.join(root, "secret.txt"), "secret"); // Links inside the directory: one out, one to a directory out, one in. await symlink(path.join(root, "secret.txt"), path.join(dir, "out-link.txt")); await symlink(root, path.join(dir, "out-dir")); await symlink(path.join(dir, "index.html"), path.join(dir, "assets", "in-link.html")); const ok = async (segs) => { const f = await deckPreviewFile(dir, segs); assert.ok(f, JSON.stringify(segs)); return f; }; const refused = async (segs) => assert.equal(await deckPreviewFile(dir, segs), null, JSON.stringify(segs)); assert.equal((await ok(["index.html"])).size, "".length); await ok(["assets", "gsap.min.js"]); // A link that stays inside is fine; it resolves to the real file. assert.equal((await ok(["assets", "in-link.html"])).abs, path.join(await realDir(dir), "index.html")); await refused([]); await refused([".."]); await refused(["..", "deck", "index.html"]); await refused(["assets", "..", "..", "deck", "index.html"]); await refused(["..", "..", "..", "video.manifest.json"]); await refused(["..", "..", "..", "..", "secret.txt"]); await refused(["."]); await refused(["", "index.html"]); // A segment the router decoded from %2F or %5C is still one segment. await refused(["assets/gsap.min.js"]); await refused(["../../../../secret.txt"]); await refused(["assets\\gsap.min.js"]); await refused(["index.html\0.png"]); // Absolute paths, however they arrive. await refused([path.join(root, "secret.txt")]); await refused(["/etc/passwd"]); // Symlinks out of the directory. await refused(["out-link.txt"]); await refused(["out-dir", "secret.txt"]); // A directory is not a file; a missing file is not one either. await refused(["assets"]); await refused(["nope.html"]); await refused("index.html"); } finally { await rm(root, { recursive: true, force: true }); } }); test("deckPreviewFile: an out/ that is itself a symlink (media on another drive) still serves", async () => { const root = await mkdtemp(path.join(tmpdir(), "umtool-deckfiles-")); try { const real = path.join(root, "elsewhere", "out"); await mkdir(path.join(real, "sourced", "chrome", "deck-preview"), { recursive: true }); await writeFile(path.join(real, "sourced", "chrome", "deck-preview", "index.html"), "x"); const project = path.join(root, "proj"); await mkdir(project); await symlink(real, path.join(project, "out")); const f = await deckPreviewFile(deckPreviewDir(project, "sourced"), ["index.html"]); assert.ok(f); assert.equal(await deckPreviewFile(deckPreviewDir(project, "sourced"), ["..", "..", "..", "..", "proj"]), null); } finally { await rm(root, { recursive: true, force: true }); } }); async function realDir(p) { const { realpath } = await import("node:fs/promises"); return realpath(p); } test("previewDirFor: feed-preview/ is the posts feed's project", () => { assert.deepEqual(previewDirFor("/p", "sourced", ["feed-preview", "assets", "qr00.png"], []), { dir: feedPreviewDir("/p", "sourced"), rest: ["assets", "qr00.png"], }); assert.equal(feedPreviewDir("/p", "full"), path.join("/p", "out", "full", "chrome", "feed-preview")); assert.match(feedPreviewSrc("reports/x", "sourced"), /\/sourced\/feed-preview\/index\.html$/); }); test("previewDirFor: stamp-preview/ is the fact-check stamps' project", () => { assert.deepEqual(previewDirFor("/p", "sourced", ["stamp-preview", "assets", "DeckSans.ttf"], []), { dir: stampPreviewDir("/p", "sourced"), rest: ["assets", "DeckSans.ttf"], }); assert.equal(stampPreviewDir("/p", "full"), path.join("/p", "out", "full", "chrome", "stamp-preview")); assert.match(stampPreviewSrc("reports/x", "sourced"), /\/sourced\/stamp-preview\/index\.html$/); }); test("previewDirFor: posts-preview- is a window's project only for an entry of the cut", () => { const ids = ["c01", "c02", "k1"]; assert.deepEqual(previewDirFor("/p", "sourced", ["posts-preview-c02", "index.html"], ids), { dir: postsPreviewDir("/p", "sourced", "c02"), rest: ["index.html"], }); assert.equal(postsPreviewDir("/p", "sourced", "c02"), path.join("/p", "out", "sourced", "chrome", "posts-preview-c02")); // Everything else is the deck's preview, as before. assert.deepEqual(previewDirFor("/p", "sourced", ["assets", "gsap.min.js"], ids), { dir: deckPreviewDir("/p", "sourced"), rest: ["assets", "gsap.min.js"], }); // A name the client made up, or one that walks, is no directory at all. for (const head of ["posts-preview-nope", "posts-preview-", "posts-preview-..", "posts-preview-.", "posts-preview-a/b"]) { assert.equal(previewDirFor("/p", "sourced", [head, "index.html"], [...ids, "..", ".", "a/b"]), null, head); } assert.equal(previewDirFor("/p", "sourced", [], ids), null); // The src the preview route hands out names the same directory. assert.match(postsPreviewSrc("reports/x", "sourced", "c02"), /\/sourced\/posts-preview-c02\/index\.html$/); }); test("deckPreviewFile under a posts window: confined to THAT window's directory", async () => { const root = await mkdtemp(path.join(tmpdir(), "umtool-posts-preview-")); try { const win = postsPreviewDir(root, "sourced", "c02"); await mkdir(path.join(win, "assets"), { recursive: true }); await writeFile(path.join(win, "index.html"), "posts"); await writeFile(path.join(win, "assets", "qr.png"), "png"); await mkdir(deckPreviewDir(root, "sourced"), { recursive: true }); await writeFile(path.join(deckPreviewDir(root, "sourced"), "index.html"), "deck"); const at = (rest) => { const w = previewDirFor(root, "sourced", rest, ["c02"]); return w ? deckPreviewFile(w.dir, w.rest) : null; }; assert.equal((await at(["posts-preview-c02", "index.html"]))?.abs, path.join(await realpathOf(win), "index.html")); assert.ok(await at(["posts-preview-c02", "assets", "qr.png"])); // The window's dir is no way up to the deck's, or out. assert.equal(await at(["posts-preview-c02", "..", "deck-preview", "index.html"]), null); assert.equal(await at(["posts-preview-c02"]), null); } finally { await rm(root, { recursive: true, force: true }); } }); async function realpathOf(p) { const { realpath } = await import("node:fs/promises"); return realpath(p); }