摘要

  • RFC 5229 加入 set、字符串展开和匹配变量,但只有脚本明确声明 variables 能力后才能使用。
  • 这块记忆受到严格限制:捕获值取决于实际执行的分支,只在当前运行脚本中可见,且超出实现上限时可能被截断。

筛选规则需要一点记忆,而不是一台更强的服务器

到 2008 年 1 月,Sieve 已经有清晰用途:在邮件最终投递阶段执行筛选。基础规范把它定义成一种有意保持克制的语言:没有变量,没有循环,也不能调用 shell 命令。这些“没有”不是等待任何扩展随意填补的空白,而是划定用户脚本可以要求邮件服务器做什么。

RFC 5229 谨慎地打开了其中一条缝。脚本先写出 require "variables",然后可用 set 把字符串存到命名变量里,再把它嵌进另一段字符串,或者用新测试检查脚本生成的字符串。成功的通配匹配还能把整个匹配结果放进 ${0},把通配符对应的片段放进 ${1}、${2} 等变量。这样,规则就能从邮件头提取列表名,将它拼入邮箱路径,而不必在几处反复抄写同一段文本。

这不等于筛选器获得了持久记忆。规范只说变量对“当前正在运行的脚本”可见;它没有定义整个邮箱共享的存储、过往邮件档案或用户之间的共享状态。变量这个词听起来像很大的能力,标准实际规定的对象要小得多:脚本执行期间,某个名字暂时对应一段字符串。

显式声明本身就是安全边界

脚本必须先请求该能力。没有 variables,Sieve 字符串的解释方式不会悄悄改变。在可扩展语言里,这很重要:启用一项功能,不只是调用一个操作,也是脚本与解释器明确约定“此功能存在”。

只有当控制流程执行到某条语句时,变量才按当时的值展开,而且只展开一遍。引用未定义的变量会得到空字符串;变量名不区分大小写。这种写法很简洁,却可能把错误藏起来:拼错变量名后,片段会消失而不是显式报缺值;如果变量的值里还含有 ${...},也不会再进行第二轮递归展开。

扩展只带来几种有限操作:ASCII 大小写转换、首字符大小写转换、通配符转义和字符串长度;另有 string 测试。它没有加入算术、循环或任意代码执行。所谓“更有表达力”,是可以命名并复用有限的字符串,不是可以无边界地计算。

捕获值取决于真正执行过的测试

匹配变量把控制流程变成数据来源的一部分。RFC 5229 要求测试从左到右执行,并在布尔结果已经明确时短路。例如 anyof (true, header :matches ...) 会直接得到真,邮件头测试根本不会运行,因此不会在那里生成捕获值。之后一次成功的 :matches 可以替换先前的捕获列表;失败的测试则不会提供新片段。复杂规则中,若不知道哪条测试实际执行,就不能贸然相信 ${1} 的内容。

其他扩展定义的新匹配类型也不会自动继承捕获副作用。RFC 5229 三个月后发布的 RFC 5173 给出了很有说明力的界线:启用 variables 时,正文测试的匹配键可以展开变量;但正文测试中的通配符匹配不得设置匹配变量。标准允许两项能力组合,同时也拒绝把表面相似的操作视为完全相同。

最低实现要求把“有限”写成了数字:至少支持 128 个变量,变量名至少 32 个字符,变量值至少 4,000 个字符,以及 ${1} 到 ${9} 这些匹配变量。若值超过某个实现自身的上限,规范建议尽量在编译阶段发现;若只能在运行时发现,则应截断,而且不应将此视为错误。安全注意事项因此提醒:不要把大型、具有安全意义的数据结构塞进变量,捕获的文本也可能由发件人任意控制。

漫长的草案史并不等于采用证据

Datatracker 的版本记录把个人草案追溯到 2003 年 3 月,随后从 2004 年末出现 Sieve 工作组版本,并在 2005 年经历多次修订。IESG 说明将其列为 Sieve 邮件过滤语言工作组的产物。2006 年的批准与 2008 年 1 月 RFC 发布,是记录中的两个不同节点;仅凭日期无法解释标准化为何历时数年,也不能推断多少服务器实际部署了它。

Heng Lu 关于“最小初始规范”的文章可以作为一副分析镜头:require 让精简的基础规则与局部启用的新能力并存。“Running-Code Primacy”提醒我们,规范中写入某项能力,不代表任何具体服务器已经实现或开放它;关于“现实层次”的分析,则帮助区分脚本文字、解释器状态、服务器行为和用户最终在邮箱里看到的结果。这些是后来的分析框架,不是 RFC 作者意图的证据。

过滤器现在能够记住一次匹配抓到了什么,也能在同一脚本中复用一些命名字符串。但这本身不证明它保留了此前的对话、认证了发件人,或确认邮件最终到达哪里。RFC 5229 的历史变化,是让一处经过显式声明的接口更实用,而不是抹去 Sieve 原有的边界。

来源