摘要

  • POP3 在打开当前 maildrop 后才按顺序给邮件编号;编号适合本次 RETR 或 DELE,却不能在断线和列表变化后证明某一项仍是客户端先前处理过的那一项。
  • RFC 1725 移除 LAST 并加入可选 UIDL。RFC 1939 要求 UID 在同一 maildrop 中跨会话持续,包括上次会话未进入 UPDATE 的情形;但客户端仍须自行保存“我已处理过”的记录,并分别判断副本数量与服务器保留策略。

一台笔记本完成了 RETR 4,把邮件安全写入本地。它随后发送了 DELE 4,却在 QUIT 之前失去连接。对用户而言,下载已经发生;对 POP3 服务器而言,删除还没有提交。

RFC 1939 对异常结束有清楚规定:没有进入 UPDATE,服务器就不能移除那些被标记删除的邮件。下次连接时,这封邮件仍会出现在 maildrop 中。若更早的邮件已经被其他行为移除,它甚至不再是第 4 封。客户端面对的是一项熟悉的内容、一个新的位置,以及一次没有正常收尾的旧交易。

这正是 UIDL 的价值所在。它不替断线补做删除,也不替客户端证明本地文件完整;它只给服务器当前列表中的每一项一个可以跨越会话的窄范围标识,让客户端把新列表与自己的历史对上。

会话编号只回答“现在从哪里取”

1993 年的 RFC 1460 描述了 POP3 的基本环境:资源和连接能力较弱的工作站不常驻运行邮件传输系统,邮件先由服务器保存在 maildrop 中,客户端在需要时连接、取回,然后通常删除。

服务器打开并解析 maildrop 后,才依次把第一封编号为 1、第二封编号为 2。这个数字是当前交易里的操作数。客户端用它执行 LIST、RETR、DELE;服务器也用它指出成功或失败所针对的位置。

位置本身没有错误。问题在于它只描述当前排序。较早的一封邮件被删除后,后面的数字会前移;新邮件加入时,集合又会改变。一次连接里的“4”与下一次连接里的“4”没有跨会话的身份承诺。

RFC 1460 还提供 LAST。服务器返回以往交易中访问过的最高消息编号,客户端便可把更大的数字推测为尚未访问。对严格从前往后处理的单一进度,这是一种很省状态的高水位线。

但一条线不能描述孔洞。客户端若已保存 1、2、4 而遗漏 3,最高值 4 会抹掉差异。两台设备可能拥有不同历史,服务器却只有一个值。RSET 还能把该进度复位为零。更重要的是,列表下方的删除会改变后续数字的含义。

LAST 记录的是某种排序下“走到多远”,不是“这个客户端是否见过这一封”。RFC 1725 的修订清单把转向写得很直接:LAST 被移除,可选命令 UIDL 被加入。文档没有声称两者只有一条因果关系,但机制确实从单一边界转向逐项识别。

UIDL 把当前位置与跨会话凭据放在同一行

UIDL 可以带一个消息编号,也可以不带。带编号时,服务器返回当前编号与对应 unique-id;不带时,它为每一封尚未标记删除的邮件返回一行映射。

两列承担不同工作。编号告诉客户端这次应对哪一项执行 RETR 或 DELE。UID 告诉客户端,服务器是否把该项视为过去某次会话中那个标识所对应的同一项。

RFC 1939 把 UID 定义为服务器决定的、长度 1 至 70 个可打印字符的字符串。它必须在一个 maildrop 内识别邮件,并跨会话保持。即使先前交易没有进入 UPDATE,这项持续性要求仍然成立。

异常断线因此成为一项协议测试,而不仅是网络故障。若客户端已保存邮件、服务器又因未 UPDATE 而正确保留它,那么重连后的 UID 必须维持,客户端才有机会识别重复。否则,一个遵守删除规则的服务器会因为重新命名而诱发重复下载,甚至诱发基于错误历史的删除。

服务器也不应在承载该 UID 的项仍存在时,在同一 maildrop 中重新使用它。复用会让新项借用旧项在客户端账本中的历史。

持久记忆位于客户端,不在 UID 命令里

UIDL 自己不保存“已读”“已归档”或“已成功写盘”。服务器只产生并延续标识。客户端决定什么叫“已经处理”,并把 UID、服务器和账户范围、完成状态写入本地记录。

这种分工符合 POP3 的简洁路线。服务器不必维护每台设备各自的已读状态,也不必变成远程文件夹同步系统。不同客户端可以有不同账本,断续连接只需先比较较小的 UID 列表,再决定要取哪些正文。

代价也落在边缘。客户端丢失账本,可能重新下载全部保留邮件;服务器迁移若保留正文却重新生成 UID,会让整批旧邮件看起来像新邮件;客户端若在本地写入成功前就把 UID 标成完成,之后的删除可能造成无法挽回的丢失。

因此实现至少要分开记录 uid_seen、retrieval_completed、delete_marked、update_committed 与 session_aborted。收到成功的 UIDL 行只证明标识映射可用,不证明正文已落盘,也不证明删除已经提交。

“唯一”没有扩大到全世界,也未必对应单一副本

UID 的范围是一个 maildrop。同一个字符串出现在另一账户、另一服务器或另一 maildrop 中,没有规定的联系。它不是邮件头里的 Message-ID,不认证发件人或收件人,也不证明投递、内容完整性或来源真实性。

RFC 1939 还保留了一个容易被名称掩盖的例外。服务器通常最好存储任意分配的 UID,但也可以根据邮件计算散列。客户端必须能处理同一 maildrop 中两份完全相同的邮件得到相同 UID 的情况。

这意味着 UID 不能自动成为“每个物理副本一行”的数据库主键。若当前列表中同一 UID 出现两次,客户端需要保留其数量与当前位置,而不能因本地映射只容纳一个值就悄悄合并。标识回答的是有限的跨会话识别问题,不总是回答服务器存了几份不可区分的副本。

协议证据最常见的误读,正是把一个词升级成更大的保证:unique 被写成全球唯一,persistent 被写成永久,identity 被写成认证。UIDL 的文本反而用范围、寿命和相同副本例外阻止这种升级。

UID 持续不等于邮件永远保留

服务器在某项存在期间维持 UID,并不承诺让该项永远存在。RFC 1939 明确警告,把已读邮件留在服务器上会积累成百上千的内容。站点可以施加配额或保留规则,也可能在 POP3 交易之外移除邮件,只要向用户说明政策。

因此,一个旧 UID 不再出现在列表中,不能单独证明“客户端已经安全删除”。另一个客户端可能执行删除,站点可能到期清理,当前列表也可能不完整或失败。客户端必须先区分会话错误、政策移除与真实缺席,再决定恢复动作。

RFC 2449 后来加入 CAPA,因为此前可选能力往往只能通过试探发现。CAPA 中的 UIDL 行证明服务器声称支持命令;它不证明 UID 质量,也不统计实际部署或一致性。

同一 RFC 的 EXPIRE 可以公告最短保留政策、零或 NEVER,但它刻意不给某一封邮件精确的过期时刻。服务器可能从到达、首次列出、首次取回或其他事件开始计时。

能力、标识与保留是三份证据。CAPA 说明命令可用,UIDL 说明当前项如何跨会话识别,EXPIRE 说明站点可能如何管理寿命。任何一项都不能替代另外两项。

一个比编号活得久、却不越权的答案

UIDL 没有让 POP3 变成 IMAP,也没有解决文件夹、标志、多设备冲突或服务器端已读同步。它做的是更窄、更适合断续连接的一件事:让当前列表里的临时位置与客户端过去保存的记录建立联系。

这一设计的历史意义在于约束。服务器给出足以识别的连续性,却不声称全球身份;客户端保留使用历史,却不能把本地记忆伪装成服务器保留证明;异常断线不执行删除,但也不应抹掉仍存在邮件的 UID。

编号活到本次连接结束。UID 活到足以让记忆穿过断线。二者之差,构成了 POP3 在保持简单的同时支持“留在服务器”使用方式的关键边界。

来源