如何编写自定义净化规则:Ammonia attribute_filter 回调与高级 API 完全参考
【免费下载链接】ammoniaRepair and secure untrusted HTML项目地址: https://gitcode.com/gh_mirrors/am/ammonia
🧹Ammonia是 Rust 生态中最主流的 HTML 净化(sanitization)库,基于白名单机制防止 XSS、布局破坏和点击劫持。而真正发挥它威力的,是它的高级 API——尤其是attribute_filter回调:它让你对每一个属性做"放行、删除、改写"三级精细控制。本指南带你从零理解回调时机、返回值语义,并串联起url_relative、set_tag_attribute_value等配套高级 API,给出一套可直接套用的自定义净化规则配方。
一、先理解 Ammonia 的净化流程
在使用attribute_filter之前,先搞清楚它在整个流程中的位置,这是避免踩坑的关键:
| 步骤 | 发生了什么 | 你能干预的 API |
|---|---|---|
| 1️⃣ 解析 | 按 HTML5 规范解析为 DOM(与浏览器一致) | — |
| 2️⃣ 标签过滤 | 移除白名单外的标签 | tags/clean_content_tags |
| 3️⃣ 属性过滤 | 移除白名单外的属性 | generic_attributes/tag_attributes |
| 4️⃣回调改写 | attribute_filter对每个存活的属性依次调用 | attribute_filter⭐ |
| 5️⃣ URL 处理 | 校验 scheme、处理相对 URL | url_schemes/url_relative |
| 6️⃣ 序列化 | 输出安全的 HTML 字符串 | — |
💡 核心结论:
attribute_filter只在属性已通过白名单之后才会被调用。它管不了被白名单挡掉的属性,但对通过的属性拥有最终决定权。
实现源码位于 src/lib.rs。
二、attribute_filter 回调签名解析
pub fn attribute_filter<'cb, CallbackFn>(&mut self, callback: CallbackFn) -> &mut Self where CallbackFn: for<'u> Fn(&str, &str, &'u str) -> Option<Cow<'u, str>> + Send + Sync + 'static,回调接收三个参数,返回值决定属性的命运:
element(&str):元素名,如"img"、"a"attribute(&str):属性名,如"src"、"href"value(&str):属性当前值- 返回
Some(value):放行(可原样返回,也可返回改写后的新值) - 返回
None:删除该属性
两个必须知道的细节
- 只能设置一次。重复调用会触发 panic,源码中写得很直白:
"attribute_filter can be set only once"(见 src/lib.rs)。 - 回调执行时机早于
url_relative()。官方文档明确说明:"Rewriting of attributes with URLs is done beforeurl_relative()"。也就是说,你在回调里改写的 URL 之后还会经过相对 URL 解析。官方测试url_filter_absolute/url_filter_relative就验证了这一点:回调把src="imgtest"改写为相对路径后,RewriteWithBase再把它拼接成绝对地址(见 src/lib.rs)。
三、三个经典实战模式
模式 1:按元素+属性删除(最常用)
场景:允许图片,但想强制禁止外链图片,删除所有<img>的src:
use ammonia::Builder; let clean = Builder::new() .attribute_filter(|element, attribute, value| { match (element, attribute) { ("img", "src") => None, // 删除 _ => Some(value.into()), // 其余放行 } }) .clean("<a href=/><img alt=Home src=foo></a>"); // 输出:<a href="/"><img alt="Home"></a>这是官方文档中的标准示例,完整代码见 src/lib.rs。
模式 2:改写属性值(URL 补全/代理化)
场景:把用户上传的相对图片路径统一改写为 CDN 地址:
let clean = Builder::new() .attribute_filter(|elem, attr, value| { match (elem, attr) { ("img", "src") => { Some(format!("https://cdn.example.com/{}", value).into()) } ("a", "href") => { // 统一加前缀走站内路由 Some(format!("/posts/{}", value).into()) } _ => Some(value.into()), } }) .clean("<img src=a.png>"); // 输出:<img src="https://cdn.example.com/a.png">模式 3:内容级校验(白名单 + 回调双层防御)
白名单控制"哪些属性能出现",回调控制"值是否合法",两者叠加才是完整防线:
let clean = Builder::new() .tag_attributes(maplit::hashmap!["iframe" => maplit::hashset!["src"]]) .attribute_filter(|elem, attr, value| { if elem == "iframe" && attr == "src" { // 只允许来自可信域名的 iframe return value.starts_with("https://partner.example.com/") .then(|| value.into()); } Some(value.into()) }) .clean("<iframe src=\"https://evil.com/x\"></iframe>");🛡️ 安全提示:回调里做域名白名单校验时,建议解析出 host 再精确比对,避免
"https://evil.comexample.com"这类前缀绕过。
四、配套高级 API 速查表
attribute_filter很少孤军奋战,下面这些高级 API 常与它搭配使用(全部定义在 src/lib.rs 中):
| API | 作用 | 典型场景 |
|---|---|---|
url_relative(UrlRelative) | 控制相对 URL:透传 / 基于基准改写 / 拒绝 | 回调改写 URL 后的"第二步",见 src/lib.rs |
set_tag_attribute_value | 为指定标签强制注入属性值(可覆盖用户输入) | 给所有<a>强制加target="_blank" |
allowed_classes/add_allowed_classes | 按标签白名单放行 CSS 类名 | 评论系统只放行少数排版类 |
generic_attribute_prefixes | 放行所有指定前缀的属性(如data-) | 放行前端框架的data-*属性 |
link_rel | 控制<a>的rel属性(默认noopener noreferrer) | 关闭自动 rel 注入 |
strip_comments | 是否剥离 HTML 注释 | 保留模板注释的调试模式 |
id_prefix | 给所有id加统一前缀防冲突 | 多组件页面防 id 污染 |
推荐组合拳
let sanitizer = Builder::default() // 强制给外链加 target="_blank" .set_tag_attribute_value("a", "target", "_blank") // 放行 contenteditable="false">【免费下载链接】ammoniaRepair and secure untrusted HTML
项目地址: https://gitcode.com/gh_mirrors/am/ammonia创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考