diff --git a/scripts/export-media-map.mjs b/scripts/export-media-map.mjs index fbe8a3f..02557d9 100644 --- a/scripts/export-media-map.mjs +++ b/scripts/export-media-map.mjs @@ -37,7 +37,14 @@ const DEFAULT_CONTAINER = 'infrastructure-postgres-1' const DEFAULT_USER = 'cms' const DEFAULT_DB = 'cms' -/** 唯一一条对 cms 执行的 SQL(只读 SELECT;source_url IS NOT NULL——867 中 8 行 NULL 不带出) */ +/** site-id 白名单校验:UUID 形态(评审加固)——SQL 里 site_id 以字面量内插,非 UUID 输入在拼 SQL 前拒绝 */ +function assertValidSiteId(siteId) { + if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(siteId)) { + throw new Error(`--site-id 不是合法 UUID(36 位形态):${siteId}`) + } +} + +/** 唯一一条对 cms 执行的 SQL(只读 SELECT;site_id 已过 UUID 校验;source_url IS NOT NULL——867 中 8 行 NULL 不带出) */ function exportSql(siteId) { return ( `SELECT source_url, file_path FROM media_assets ` + @@ -90,9 +97,10 @@ async function auditAgainstWxr(mapPath, wxrPath) { const sidecarByExact = new Set(map.map((e) => e.sourceUrl)) const sidecarByNormalized = new Set(map.map((e) => normalizeUrl(e.sourceUrl))) const wxr = await readFile(wxrPath, 'utf8') - const attachmentUrls = [...wxr.matchAll(/<\/wp:attachment_url>/g)].map( - (m) => m[1], - ) + // CDATA 与裸文本两种形态都匹配(评审加固:WXR 导出器未必总包 CDATA);分支互斥,m[1] ?? m[2] + const attachmentUrls = [ + ...wxr.matchAll(/(?:|([^<]*))<\/wp:attachment_url>/g), + ].map((m) => m[1] ?? m[2] ?? '') const seen = new Set() const hitsExact = [] // 归一前即完全相等 const hitsNormalized = [] // 仅归一后相等(大小写/尾斜杠救回) @@ -156,6 +164,7 @@ function parseArgs(argv) { async function main() { const args = parseArgs(process.argv.slice(2)) + assertValidSiteId(args.siteId) const sql = exportSql(args.siteId) process.stderr.write(`[export-media-map] 只读查询:docker exec ${args.container} psql -U ${args.user} -d ${args.db}\n`)