安全修复: - 移除硬编码管理员凭据 admin/Admin@2026,改用环境变量注入 - 移除 forceSetAuth 全局 bridge 方法,减少攻击面 - sanitizeHtml 从黑名单正则升级为白名单方式 - secure-storage 实现 XOR+Base64 加密存储,不再明文 - 添加旧数据迁移逻辑 migrateLegacyStorage 功能修复: - 新增咨询创建页(consultation/create),修复"发起咨询"按钮导航失败 - 修复咨询详情页长轮询可能永远不启动(dataLoadedRef → useState) - 新增 createSession service API - 预约页面从主包移至分包,配置 commonChunks 优化主包体积 UX 修复: - 65 处硬编码字号 → var(--tk-font-*) token 替换 - AI 聊天页 13 处、咨询详情页 14 处、医生端核心页 38 处 - StatusTag 色值对齐设计系统色板 - Loading 文字从 --tk-font-h1(28px) 修正为 --tk-font-body-sm - EmptyState 文字从 --tk-font-num(30px)/--tk-font-h2(22px) 修正 - 医生端 5 处硬编码颜色 → SCSS 变量
53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
const ALLOWED_TAGS = new Set([
|
|
'p', 'br', 'hr', 'div', 'span',
|
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
'ul', 'ol', 'li',
|
|
'table', 'thead', 'tbody', 'tr', 'th', 'td',
|
|
'strong', 'em', 'b', 'i', 'u', 's', 'del', 'ins',
|
|
'blockquote', 'pre', 'code',
|
|
'a', 'img',
|
|
'dl', 'dt', 'dd',
|
|
'sup', 'sub',
|
|
]);
|
|
|
|
const ALLOWED_ATTRS: Record<string, Set<string>> = {
|
|
'*': new Set(['class']),
|
|
a: new Set(['href', 'title']),
|
|
img: new Set(['src', 'alt', 'width', 'height']),
|
|
td: new Set(['colspan', 'rowspan']),
|
|
th: new Set(['colspan', 'rowspan']),
|
|
};
|
|
|
|
const URL_ATTRS = new Set(['href', 'src']);
|
|
const SAFE_URL_RE = /^(?:https?|mailto|tel):|^$/i;
|
|
|
|
const TAG_RE = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g;
|
|
const ATTR_RE = /([a-zA-Z][a-zA-Z0-9-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
|
|
export function sanitizeHtml(html: string): string {
|
|
if (!html) return '';
|
|
|
|
return html.replace(TAG_RE, (fullMatch, tagName) => {
|
|
const tag = tagName.toLowerCase();
|
|
|
|
if (!ALLOWED_TAGS.has(tag)) return '';
|
|
|
|
const allowedForTag = ALLOWED_ATTRS[tag] || new Set();
|
|
const allowedGlobal = ALLOWED_ATTRS['*'];
|
|
const combined = new Set([...allowedForTag, ...allowedGlobal]);
|
|
|
|
const cleaned = fullMatch.replace(ATTR_RE, (_, attrName, dqVal, sqVal) => {
|
|
const attr = attrName.toLowerCase();
|
|
const val = dqVal ?? sqVal ?? '';
|
|
|
|
if (!combined.has(attr)) return '';
|
|
|
|
if (URL_ATTRS.has(attr) && !SAFE_URL_RE.test(val)) return '';
|
|
|
|
return ` ${attr}="${val}"`;
|
|
});
|
|
|
|
return cleaned;
|
|
});
|
|
}
|