// dsh-trusted-page —— 纯工具函数(无任何依赖,供宿主侧与测试复用)。 /** * 规范化一个用户输入的「受信域名」为裸权威(bare authority:host 或 host:port)。 * * 容错输入:协议前缀(https://)、路径、查询、锚点、首尾空白、大写。 * 拒绝:空串、含空白/用户信息(@)/非法字符、端口非数字、IPv6 未加方括号。 * 与 DSH 服务端 fence(assertTrustedAuthority)同口径:IDN 需以 punycode 书写。 * * @param {unknown} input 任意来源(设置文档/UI 输入)的值 * @returns {string | undefined} 规范化后的权威;非法返回 undefined */ const AUTHORITY_PATTERN = /^(\[[0-9a-f:.]+\]|[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*)(?::(\d{1,5}))?$/ export function normalizeAuthority(input) { if (typeof input !== 'string') return undefined let value = input.trim().toLowerCase() value = value.replace(/^[a-z][a-z0-9+.-]*:\/\//, '') value = value.split(/[/?#]/)[0] if (value === '' || value.length > 253) return undefined if (/[\s@<>"'`\\]/.test(value)) return undefined if (!AUTHORITY_PATTERN.test(value)) return undefined return value } /** * 合并 UI 管理的受信域名与静态基线(CLI --trusted-host / cordis.patch.yml)。 * @param {string[]} raw UI 侧原始输入(未规范化) * @param {readonly string[]} base 静态基线(已在 fence 里,保持不动) * @returns {string[]} 规范化、去重、剔除基线已有项之后的增量列表 */ export function extraAuthorities(raw, base) { const seen = new Set(base) const out = [] for (const entry of raw) { const normalized = normalizeAuthority(entry) if (normalized === undefined || seen.has(normalized)) continue seen.add(normalized) out.push(normalized) } return out } /** * 生成注入页面的「页面受信判定」内联脚本。 * * 在浏览器里先于一切插件模块执行:若 location 命中受信列表,则声明 * window.__DSH_TRANSPORT__ = { ownsHost: true }(dsh-client-connection 预留 * 扩展点),使设置持久化可用。匹配语义与服务端 fence 一致: * 无端口条目匹配任意端口;带端口条目要求端口精确相等。 * * @param {readonly string[]} hosts 已规范化的受信权威列表 * @returns {string} 可直接作为 index-injection `script` 行的脚本文本 */ export function buildClassificationScript(hosts) { const json = JSON.stringify(hosts) return `/* dsh-trusted-page: page trust classification */ (() => { try { const declared = ${json}; if (!Array.isArray(declared) || declared.length === 0) return; const hostname = location.hostname; const port = location.port; const trusted = declared.some((entry) => { const bracketEnd = entry.lastIndexOf(']'); const colon = entry.lastIndexOf(':'); if (colon <= bracketEnd) return entry === hostname; return entry.slice(0, colon) === hostname && entry.slice(colon + 1) === port; }); if (trusted && globalThis.__DSH_TRANSPORT__ === undefined) { globalThis.__DSH_TRANSPORT__ = { ownsHost: true }; } } catch {} })(); ` }