/**
* @file js/chatbot/utils/safe-markdown-render.js
* @description 安全的 Markdown 渲染工具 - 防止 XSS 攻击
*
* 设计理念:
* 1. 移除真正危险的东西(\n```")
* // => "
<script>alert()</script>
"
*
* @example
* // 恶意代码(被移除)
* safeRenderMarkdown('
')
* // => "
" // onerror 被移除
*/
function safeRenderMarkdown(markdown) {
// 检查依赖
if (typeof marked === 'undefined') {
console.error('safeRenderMarkdown: marked is not loaded');
return escapeHtml(markdown).replace(/\n/g, '
');
}
if (typeof DOMPurify === 'undefined') {
console.warn('safeRenderMarkdown: DOMPurify is not loaded, falling back to unsafe rendering');
return marked.parse(markdown);
}
// 1. 使用 marked 解析 Markdown
// 代码块会被自动转义为 < >,不会执行
const rawHtml = marked.parse(markdown);
// 2. 使用 DOMPurify 清理 - 宽松配置
const cleanHtml = DOMPurify.sanitize(rawHtml, {
// 允许的标签(宽松配置,支持教学示例)
ALLOWED_TAGS: [
// === Markdown 标准标签 ===
'p', 'br', 'hr',
'strong', 'em', 'b', 'i', 'u', 's', 'del', 'ins',
'code', 'pre', 'kbd', 'samp', 'var',
'ul', 'ol', 'li', 'dl', 'dt', 'dd',
'blockquote',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'a', 'img',
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
// === 扩展标签(用于 KaTeX 和复杂布局) ===
'span', 'div', 'section', 'article',
'sup', 'sub', 'small', 'mark',
// === 教学示例可能用到的标签 ===
// AI 可能在教学中返回这些 HTML 标签作为示例
// DOMPurify 会移除事件属性,所以这些标签是安全的
'button', 'input', 'form', 'label', 'select', 'textarea', 'fieldset', 'legend',
'iframe', 'video', 'audio', 'source', 'track',
'details', 'summary',
// 注意: