const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs'); const http = require('node:http'); const os = require('node:os'); const path = require('node:path'); const { spawn } = require('node:child_process'); const html = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8').replace(/\r\n?/g, '\n'); const sha256 = text => crypto.createHash('sha256').update(text).digest('base64'); for (const tag of ['script', 'style']) { const content = html.match(new RegExp(`<${tag}>([\\s\\S]*?)${tag}>`))[1]; assert.ok(html.includes(`'sha256-${sha256(content)}'`), `${tag} CSP hash must match`); } const browser = process.env.SECURITY_TEST_BROWSER || [ 'C:/Program Files/Google/Chrome/Application/chrome.exe', 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', ].find(candidate => fs.existsSync(candidate)); assert.ok(browser, 'Set SECURITY_TEST_BROWSER to a Chromium browser executable'); async function main() { let probes = 0; const harness = ` (async () => { let checks = 0; const check = (condition, message) => { if (!condition) throw new Error(message); checks++; }; try { for (const value of ['=1+1', '+1', '-1', '@SUM(1)', ' =1', '\\t=1', '\\r=1', '\\n=1', '\\u0000=1', '=1', '+1', '-1', '@SUM(1)']) { check(toCsvField(value).startsWith('"文本:'), 'Formula/control prefix: ' + JSON.stringify(value)); } check(toCsvField('普通标题') === '"普通标题"', 'Normal title preserved'); const opaqueLink = document.createElement('a'); opaqueLink.setAttribute('href', 'httpx:payload","=1+1'); check(opaqueLink.href.startsWith('http') && opaqueLink.href.includes('"'), 'Original prefix check could admit unescaped CSV quotes'); check(toCsvField('a,"b"\\nc') === '"a,""b""\\nc"', 'CSV quoting'); const ordinary = bookmarkHtmlToCsv('
');
check(ordinary.count === 2, 'Netscape bookmark format');
check(ordinary.csvContent.startsWith('\\uFEFFTitle,URL\\r\\n'), 'BOM and CRLF');
check(ordinary.csvContent.includes('"中文 & 标题","https://example.com/?q=%22x%22"'), 'Entities and URL encoding');
const hostile = bookmarkHtmlToCsv('