摘要

  • :raw 保留未解码表示,:content 递归选择 MIME 部件,:text 则使用本地实现的尽力文字抽取。同一封邮件并不存在一个供所有过滤器共享的唯一正文。
  • 可复核的高影响过滤必须保存邮件哈希、引擎与抽取器版本、变换、选择器、比较器、转换失败和被跳过部件。命中不等于危险,未命中也不等于内容不存在。

邮件阅读器把复杂结构整理成一页,人便容易把“正文”当成一个对象。MIME 实际提供的是树:外层容器、替代版本、附件、嵌套邮件、传输编码和各自的字符集。Sieve 规则要先从这棵树构造一组字符串,之后才有比较。

甚至“没有正文”也有精确定义。只有头部、后面没有空行分隔符的邮件会产生空字符串集合,所有 body 测试都返回 false,包括寻找空字符串的测试。这与存在一个内容为空的正文不是同一张收据。

Raw 搜到的可能是包装

:raw 把整个未解码正文当成一个条目。它不移除传输编码,也不把 MIME 边界和内层部件头解释成结构;这些标记本身都能参与匹配。除非外围环境已经拒收语法错误邮件,raw 不能仅因语法错误拒绝过滤。

这适合回答“收到的表示里有没有这些字节”。它不自动回答“收件人看见了什么”。关键词可能藏在 base64 序列、boundary、内层 Content-Type 或 multipart 的序言里。命中证明的是表示面,不是语义面。

因为 raw 只有一个条目,通配模式还可能跨过另一种变换会分开的区域。把规则从 raw 改为部件感知,不是性能优化,而是改变证据范围。

Content 先决定哪些树枝有资格

:content 按 MIME 类型选择部件。完整的 type/subtype 只选精确类型,只有 type 时覆盖其子类型,空选择器覆盖所有 MIME 类型。斜线出现在开头、结尾或重复出现时不会宽松匹配,而是什么都不选。

搜索递归进入 multipart 与 message/rfc822。每个合格部件独立比较,表达式不得跨越 MIME 边界。阅读器里相邻的两个片段如果来自不同部件,就不能拼成一次连续命中。

选择 multipart 容器时,搜索的是它的序言和尾声;子部件仍要靠自己的类型入选。选择嵌套邮件时,内层头部可以成为一个字符串,而内层正文仍要单独选择。结果来自哪条树枝,是结果含义的一部分。

引擎会解码常见传输编码并把支持的文字转成 UTF-8。但对无法解码或转换的部件,RFC 允许按 US-ASCII 处理、直接省略,或采用本地约定。因此,false 也可能意味着“这个部件没有进入比较”。

Text 的默认值是一项隐形依赖

:text 是尽力提取 UTF-8 文字,也是没有显式指定变换时的默认值。简单实现可以把它等同于 :content "text";更复杂的实现可以去掉标记、转换专有文档,甚至对图片做 OCR。规范没有要求这些路径产生相同数量或相同内容的字符串。

于是软件升级会改变政策。新增文档转换器可能让附件里的词第一次被看见;OCR 或字符库变化可能让旧命中消失。脚本没有改,实际观察面却变了。

这不是反对更强的抽取能力,而是要求把能力写进记录。只保存“规则命中”,没有抽取器版本、启用能力、错误、被省略部件和产出字符串哈希,就无法重做那次观察。

字符串命中不是安全判决

RFC 5173 明确警告:raw 字符串匹配可能比预期更宽,文字匹配不能替代垃圾邮件、病毒或其他安全过滤系统。它还要求限制容量,避免恶意 body 匹配让其他用户失去服务。

误报可能因为包装里出现某个词而删除正常邮件;漏报可能因为类型没选中、转换失败或引擎不认识附件而放过危险内容。一个布尔值不会告诉审计者走的是哪条路径。

RFC 5229 的既有主题仍然独立:脚本变量与捕获的临时作用域。body 的通配匹配不会顺带写入 ${1} 等捕获变量。这条边界阻止命中暗中变成记忆,却不保证抽取出的正文完整或稳定。

来源