fix(web): reject superscript Windows device names in archives

This commit is contained in:
MiniRML committed 2026-10-13 11:13:17 -07:00
1 parent 18c438b109
commit 3397e873bc
3 files changed
+33 -1

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ class BoundedBlobReader extends BlobReader {
export function validPath(name) {
if (!name || name.includes("\\") || /[\x00-\x1f<>:"|?*]/u.test(name)) throw new Error(`Unsafe ZIP path: ${name}`);
const parts = name.replace(/\/$/u, "").split("/");
if (parts.some(part => !part || part === "." || part === ".." || /[. ]$/u.test(part) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(part))) throw new Error(`Unsafe ZIP path: ${name}`);
if (parts.some(part => !part || part === "." || part === ".." || /[. ]$/u.test(part) || /^(con|prn|aux|nul|com[1-9\u00b9\u00b2\u00b3]|lpt[1-9\u00b9\u00b2\u00b3])(?:\.|$)/iu.test(part))) throw new Error(`Unsafe ZIP path: ${name}`);
return name;
}
+31
View File
@@ -0,0 +1,31 @@
import assert from "node:assert/strict";
import { register } from "node:module";
import { pathToFileURL } from "node:url";
import path from "node:path";
register("./cdn-loader.mjs", import.meta.url, { data: { url: process.env.ANYPS5_TEST_ZIP_URL, path: process.env.ANYPS5_TEST_ZIP_PATH } });
const { Archive, validPath } = await import(pathToFileURL(path.join(process.argv[2], "archive.mjs")));
const { BlobWriter, Uint8ArrayReader, ZipWriter } = await import(process.env.ANYPS5_TEST_ZIP_URL);
const header = new Uint8Array([127, 69, 76, 70]);
for (const prefix of ["COM", "lpt"]) {
for (const digit of ["\u00b9", "\u00b2", "\u00b3"]) {
for (const name of [prefix + digit, prefix + digit + ".txt", "content/" + prefix + digit + "/data.bin"]) {
assert.throws(() => validPath(name), /Unsafe ZIP path/);
const folder = new Archive([{ path: "main.elf", file: new Blob([header]) }, { path: name, file: new Blob(["resource"]) }]);
try { await assert.rejects(folder.inspect(() => {}), /Unsafe ZIP path/); }
finally { await folder.close(); }
}
}
}
const writer = new ZipWriter(new BlobWriter(), { useWebWorkers: false, useCompressionStream: false });
await writer.add("main.elf", new Uint8ArrayReader(header));
await writer.add("content/COM\u00b9.txt", new Uint8ArrayReader(new Uint8Array([1])));
const archive = new Archive(await writer.close());
try { await assert.rejects(archive.inspect(() => {}), /Unsafe ZIP path/); }
finally { await archive.close(); }
for (const name of ["COM10", "COM\u2074.txt", "COM\u00b9-extra", "content/ordinary.txt"]) {
assert.equal(validPath(name), name);
}
+1
View File
@@ -9,6 +9,7 @@ register("./cdn-loader.mjs", import.meta.url, { data: { url: process.env.ANYPS5_
const site = name => pathToFileURL(path.join(process.argv[2], name)).href;
const { Archive, memoryEstimate, MEMORY_LIMIT, packageArchive } = await import(site("archive.mjs"));
const { BlobReader, BlobWriter, Uint8ArrayReader, Uint8ArrayWriter, ZipReader, ZipWriter } = await import(process.env.ANYPS5_TEST_ZIP_URL);
await import("./archive-reserved.test.mjs");
const { default: createRelinker } = await import(site("native/relinker_wasm.mjs"));
const native = await createRelinker();
const executable = new Uint8Array(await readFile(path.join(process.argv[3], "main.elf")));