摘要
- RFC 5182 的 SAVE 把一组消息放进单一搜索结果变量,后续命令以
$引用。它没有保存查询原因,也没有创建具名、可并存、可长期审计的快照。 - SELECT、EXAMINE、新 UIDVALIDITY、SAVE 失败、
NOTSAVED与 EXPUNGE 都会重置或改变这组消息;空集合仍可让 FETCH、COPY 等命令正常返回OK。
一块运维面板写着“已保存搜索:过期邮件”。操作员看到绿色状态,以为筛选条件与待处理集合都还在。随后系统切换了邮箱,又回到原邮箱执行 COPY $。命令没有报错,却没有复制任何邮件。
这不是某个真实产品事故,而是 RFC 5182 状态机直接允许的结果。成功的 SELECT 会把搜索结果变量清空;空的 $ 仍是合法、但不匹配任何消息的集合。协议执行正确,面板用词却把一个会话变量升级成了持久证据。
SEARCHRES 省掉的是往返,不是责任
普通做法是:服务器返回 SEARCH 找到的消息列表,客户端解析、重排,再把列表送回 FETCH、STORE、COPY、SEARCH 或 UID EXPUNGE。SEARCHRES 让服务器把结果留在内部,客户端用 $ 代替 message-set。
支持方通过 SEARCHRES capability 宣告能力,并同时支持 ESEARCH。使用 RETURN (SAVE) 时,如果没有别的返回选项,服务器甚至会抑制原本的 SEARCH 列表响应。客户端可能从未看到具体标识,就直接让后续命令消费结果。
这正是协议优化的价值:减少带宽与延迟,也让服务器优化相依命令。但标准没有要求保存原查询、业务理由、操作者、截止时间或持久 ID。变量也没有名字与版本。新的成功 SAVE 会覆盖旧值。
产品如果需要“已保存搜索”,就应自己创建对象:明确名称、不可变 ID、查询、范围、时间、创建者和预计结果。不能只给 $ 加一个友好标签,就把本地承诺冒充成协议事实。
邮箱选择定义变量的世界
SELECT 或 EXAMINE 成功后,变量被重置为空。邮箱打开期间如果服务器发出新的 UIDVALIDITY,变量也被清空。UID 只有与邮箱名和 UIDVALIDITY 组合后,才具有可延续的身份;换了这一代,旧结果没有权力继续发言。
EXPUNGE 则在不执行新搜索的情况下改变集合。被删除的成员自动移出。若服务器内部保存的是消息序号,其他序号还必须随 EXPUNGE 通知调整。十点保存的集合到十点零五分可能已缩小、重新编号,但线上的字符仍然只是 $。
消费命令还决定解释空间。普通 SEARCH 的结果可以交给 UID FETCH,此时按 UID 解析;UID SEARCH 的结果也可以交给普通 FETCH,此时按消息序号解析。并不是生产者给集合盖上了永远不变的类型章。
因此,审计单位至少应包含连接、选中邮箱、UIDVALIDITY、SAVE 生产者、完整查询与返回选项、命令接收顺序、期间的 EXPUNGE 或切换事件,以及每个消费命令的 UID/序号上下文。脱离这些字段,$ 没有可验证身份。
SAVE 失败不会保留旧安全垫
RFC 5182 对状态变化划分得很细。返回 BAD 的 SEARCH 不改变变量;没有 SAVE 的 SEARCH,无论成功还是返回 NO,也不改变变量。带 SAVE 的 SEARCH 若返回 NO,则把变量清空。
这是闭合失败。若新集合没有成功安装,却让旧集合悄悄留在同一符号后面,下一条破坏性命令可能对过期对象生效。标准宁愿让后续操作得到明确的空集合。
服务器还可以因资源上限拒绝保存,返回带 NOTSAVED 的 NO,并同样清空变量。RFC 明确指出,跨连接保存结果会增加状态,可能帮助拒绝服务压力。宣告 SEARCHRES 只证明理解这种能力,不保证每次 SAVE 都有资源可用。
所以,正确重试不是在失败后继续执行 STORE $,也不是假定上一批仍存在;而是终止这一条授权链,重新搜索,并建立新的本地操作身份。
OK 可以确认一次空操作
空集合来源很多:搜索本来没有命中;SELECT、EXAMINE 或 UIDVALIDITY 重置;SAVE 失败;NOTSAVED;最后一个成员被 EXPUNGE。RFC 要求接受 message-set 的命令把空 $ 当作有效的“不匹配集合”。
因此,FETCH $ 可以没有任何 FETCH 响应,最终仍返回 tagged OK。COPY 也可以返回 OK,同时明确什么都没复制。协议层成功表示命令被正确执行,不表示保留、迁移或安全策略触及了任何对象。
监控必须分开四个分母:政策想处理的对象、SEARCH 计算出的结果、变量在消费时实际含有的对象、最终发生作用的对象。再往上还有用户或控制系统看到的结果。把这些都压成“成功次数”,会让零工作量看起来像完整履约。
流水线需要相依图
SAVE 后紧接使用 $ 的命令,会形成直接相依。服务器必须按接收顺序执行,尽管内部可以并行或用查询替换集合,只要外部语义不变。这样客户端可以不等一次网络往返,就连续提交 SEARCH 与 FETCH。
一个 SAVE 后跟 COPY、STORE 等多个消费者,也可以是明确的:它们按顺序使用同一结果。两个 SAVE 则不同。标准没有分配两个槽位,第二个结果总会覆盖第一个。
IMAP tag 用来关联命令与响应,但 tag 不是搜索结果的持久名字。只看响应到达时间,无法替代接收顺序和共享状态变化。每个 $ 消费者都应在日志中指向当时有效的 SAVE 生产者。
RFC 5267 的 CONTEXT 提供了有意不同的机制:上下文可由命令 tag 标识、接收增量更新,并可取消。把 SEARCHRES 当成 CONTEXT 或本地任务队列,会把最小共同规范强迫成它从未承诺的服务。
返回选项决定保存了多少
即使搜索完整成功,SAVE 也不一定保存所有命中。ESEARCH 的 SAVE MIN 只保留最小项,SAVE MAX 只保留最大项,二者一起只保留一到两个端点。若包含 ALL 或 COUNT,RFC 5182 要求保存全部命中,即便 COUNT 本来可用只计算数量的优化。
RFC 9394 后来规定 PARTIAL 与 SAVE 的组合:没有 ALL 时,变量保存的是分页窗口及适用的 MIN/MAX。RFC 9738 又规定 MESSAGELIMIT 截断时只保存截断集合;那一条“不完整”边界已有 BTW 独立文章,本篇不重复。这里的结论是:返回选项属于结果身份,不能从任务名称推断。
“处理过期邮件”可能代表全部、第一封、最后一封或一页。真正的机器在命令行里,不在面板标题里。
让本地权力留下本地证据
IANA 注册 SEARCHRES,RFC 9051 把相关状态机纳入 IMAP4rev2。这些事实建立互操作语言,但不证明某家服务已部署、不证明客户端正确使用,也不证明资源充足或政策完成。
领导层应批准一条证据链,而非一个字符。不可逆操作前,本地系统创建不可变操作 ID,冻结规则、查询、返回选项、邮箱与 UIDVALIDITY;随后记录生产者、消费者、顺序、重置、EXPUNGE、集合数量与最终效果。
验收问题也应改变:能否解释每个被处理对象为何属于这次决定?能否解释每个原本应处理却没有处理的对象?如果回答只是“服务器接受了 $”,运行代码的实际承诺已经被符号遮住。
来源
- RFC 5182 HTML
- RFC 5182 纯文本
- RFC Editor 信息页
- IETF Datatracker 文档页
- IETF Datatracker 历史
- IETF Datatracker 引用
- RFC 5182 勘误
- RFC 9051 — IMAP4rev2
- RFC 9051 信息页
- RFC 4731 — ESEARCH
- RFC 4466 — IMAP ABNF 汇编
- RFC 4315 — UIDPLUS
- RFC 3501 — IMAP4rev1
- RFC 5267 — IMAP CONTEXT
- RFC 9738 — MESSAGELIMIT
- RFC 9394 — PARTIAL
- IANA IMAP 能力注册表
- Heng Lu:现实层级
- Heng Lu:最小初始规范与自愿采用
- Heng Lu:运行代码优先
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
