export const assetPathPattern = /^\/brands\/apps\/[a-z0-9-]+\.(svg|png)$/; export const normalizeBrandKey = (value) => value.trim().toLowerCase().replace(/&/g, "and").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); // Conservative structural rejection, not a general-purpose SVG sanitizer. // Original accepted artwork is copied byte-for-byte, never rewritten here. export function validateArtwork(bytes, filename) { if (filename.endsWith(".png")) { if (bytes.length < 45 || !bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) || bytes.toString("ascii", 12, 16) !== "IHDR" || bytes.readUInt32BE(16) === 0 || bytes.readUInt32BE(20) === 0 || bytes.toString("ascii", bytes.length - 8, bytes.length - 4) !== "IEND") { throw new Error(`${filename}: invalid PNG signature`); } return; } const svg = bytes.toString("utf8"); if (!filename.endsWith(".svg") || (!/]*\bviewBox\s*=\s*["'][^"']+["']/i.test(svg) && !(/]*\bwidth\s*=\s*["'][0-9.]+(?:px)?["']/i.test(svg) && /]*\bheight\s*=\s*["'][0-9.]+(?:px)?["']/i.test(svg)))) { throw new Error(`${filename}: SVG requires a viewBox or intrinsic width and height`); } if (/ typeof alias !== "string" || !alias.trim())) throw new Error(`${row.slug}: invalid aliases`); for (const name of [row.slug, row.provider, ...aliases]) { const key = normalizeBrandKey(name); if (keys.has(key) && keys.get(key) !== row.slug) throw new Error(`${row.slug}: ambiguous brand alias ${name}`); keys.set(key, row.slug); } for (const asset of new Set([row.localAsset, ...(row.darkAsset === undefined ? [] : [row.darkAsset])])) { if (typeof asset !== "string" || !assetPathPattern.test(asset)) throw new Error(`${row.slug}: invalid asset path`); validateArtwork(readAsset(asset), asset); } } return manifest.providers.length; }