From 865871f45c5f61da917da02d19ca97fef73d099f Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 2 Jul 2026 13:22:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(markdown):=20=E4=BF=AE=E5=A4=8D=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=88=97=E8=A1=A8=20checkbox=20=E5=9C=A8=E5=89=8D?= =?UTF-8?q?=E5=8F=B0=E8=A2=AB=20sanitizer=20=E5=89=A5=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 编辑器端已挂 TaskList/TaskItem,但前台渲染走 pulldown-cmark,其输出的 不在 sanitizer 白名单内,被 remove_and_keep_content 剥离,导致任务列表在前台只剩裸
  • 文本。 - sanitizer: 白名单放开 input,但强制 type="checkbox" 属性值校验 - attrs_to_remove 增加 input.type 值校验,非 checkbox 一律删除属性 - element_handler 末尾兜底:缺 type 或非 checkbox 的 input 整标签移除 (void 元素无内容,remove() 不丢正文,封堵 type=image/text 等 XSS 滥用) - 仅 clean_html 放开,clean_comment_html 保持不放开(评论无需任务列表) - input.css 补 .md-content 任务列表样式(pulldown-cmark 裸 li>input 结构, 用 :has() 识别,不支持 :has() 的浏览器降级为普通列表仍可读) - 新增 sanitizer 5 个测试(白名单/XSS 边界)+ markdown 1 个端到端测试 --- input.css | 20 +++++++++ src/api/markdown.rs | 22 ++++++++++ src/api/sanitizer.rs | 101 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/input.css b/input.css index 994aa15..f1eb8a6 100644 --- a/input.css +++ b/input.css @@ -300,6 +300,26 @@ margin-bottom: 0; } + /* 任务列表:pulldown-cmark 把 - [ ] / - [x] 渲染成裸
  • … + * (无
  • 文本
  • "#; + let result = clean_html(input); + assert!( + !result.contains("input"), + "type=image 的 input 必须被整体移除, got: {result}" + ); + assert!( + !result.contains("evil.example"), + "残留的 src 也应随 input 一并移除, got: {result}" + ); + // 文本内容保留 + assert!(result.contains("文本")); + } + + #[test] + fn clean_html_input_rejects_type_text() { + let result = clean_html(r#""#); + assert!( + !result.contains("input"), + "type=text 的 input 应被移除, got: {result}" + ); + } + + #[test] + fn clean_html_input_without_type_removed() { + // 缺省 type 属性的 input(如 经属性过滤后 type 缺省)必须整体移除 + let result = clean_html(""); + assert!( + !result.contains("input"), + "无 type 属性的 input 应被整体移除, got: {result}" + ); + } + + #[test] + fn clean_comment_html_input_stripped() { + // 评论白名单本就不含 input,任务列表 checkbox 在评论侧不放开 + let result = clean_comment_html(r#""#); + assert!( + !result.contains("input"), + "评论侧 input 应被剥离, got: {result}" + ); + } }