摘要

  • RFC 2342 把个人、其他用户与共享命名空间的名称解释约定交给客户端发现,而不是让客户端猜测服务器的本地目录结构。
  • 命名空间声明只是证据链的起点;名称可发现、邮箱存在、权限足够、消息可取、内容已呈现与人类阅读仍是不同状态。

地图回答的是语法问题

1998 年的 RFC 2342 处理了一个很实际的兼容性难题:不同 IMAP 服务器可能把个人邮箱放在空前缀、INBOX. 或其他根下,共享邮箱也可能使用 #shared/、Public Folders/ 等入口。客户端如果不知道这些规则,就只能让用户手工填写。

NAMESPACE 命令把这套命名约定变成协议响应。三个有序位置分别对应个人、其他用户和共享命名空间;每项提供前缀与层级分隔符,不可用的类别以 NIL 表示。服务器还可以提供多个命名空间,让不同分支使用不同分隔符。

这是一张地图的图例,不是仓库盘点。服务器甚至可以只向当前连接暴露其支持命名空间的一个子集,同一服务器对不同用户的响应也可以不同。看到 (("~" "/")),只能说明客户端可以据此构造其他用户邮箱名;它不能推出某个用户名存在,更不能推出该用户的邮箱对当前身份开放。

枚举本身受权限与隐私约束

RFC 2342 建议客户端在其他用户命名空间前缀后追加 %,尝试用 LIST 找到可用用户。规范随即设置了边界:服务器不应列出没有授予 list access 的用户名;它可以只返回已授权者,也可以拒绝宽泛查询,要求客户端提交具体用户名。

规范中的一组例子极有解释力。客户端先请求 #Users/%,服务器返回 NO;当客户端改为 #Users/Mike/%,服务器才列出 Mike 的 INBOX 与 Foo。命名空间在两次请求中都存在,改变的是查询精度和当前访问条件。

这一限制也是安全机制。若命名空间声明自动等于完整账户目录,服务器就会向未授权者泄露用户名,既暴露组织信息,也为攻击提供起点。

LIST、订阅与存在仍不是同一件事

后来的 RFC 9051 把边界写得更清楚:LIST 返回的是当前客户端可用全部邮箱名的一个子集,而且可以返回零条。名称还可能带有 \Noselect 或 \NonExistent 属性。订阅列表甚至可以保留一个已经不存在的邮箱名。

因此至少要分开四个判断:服务器宣告了命名分支;LIST 返回了某个名称;该名称仍对应邮箱;当前连接可以选择它。缓存把这些状态混在一起时,前缀变更、邮箱删除或权限变化都会被误报成“邮件丢失”。

看得见名称,不等于读得了邮箱

RFC 4314 把 ACL 权限拆开:l 允许邮箱出现在 LIST/LSUB 中,r 才允许 SELECT 与 STATUS,s 控制 Seen/Unseen 状态能否跨会话保存。创建、插入、删除、清除和管理 ACL 又各有权限。

这意味着一个邮箱可以被列出却不能被读取。RFC 9051 明确讨论了只有 l、没有 r 的情形:列表能看到名字,STATUS 却不能成立,响应必须反映不可选择状态。RFC 8440 后来让 LIST 可以附带 MYRIGHTS,减少往返,但权限信息仍只说明“允许做什么”,不是“已经做成了什么”。

SELECT 成功才会让连接进入 selected state,并返回 FLAGS、EXISTS、UIDNEXT、UIDVALIDITY 等邮箱状态。即使如此,消息正文是否 FETCH 完成、客户端是否解码、窗口是否可见、用户是否阅读,仍需要后续收据。

已读标志也不是人的阅读回执

协议可以记录 \Seen,但该标志会受 FETCH、STORE 和 ACL s 权限影响。后台索引器、预览器或同步客户端都可能改变它。它描述服务器端消息状态,不观察人的眼睛、理解或注意。

这正是运行现实与符号声明必须分开的地方。RFC 文档给出了客户端可本地执行的名称构造规则;实际邮箱状态由当时的服务器、权限与命令结果决定。命名约定可以启动下一步,不应提前替下一步签字。

RFC 2342 的价值并不因这条边界而减弱。恰恰相反,它把自己负责的层说得足够精确:客户端终于不必猜地图图例。它只是从未承诺地图上的每一扇门都存在,更没有承诺门后的人已经读信。