摘要

  • message/partial 让每个分片仍是一封可独立传输的邮件;id 负责归组,number 负责顺序,total 给出预期片数,接收端据此尝试重组。
  • 重组结果取得内层实体的语义,而不是永久保留“邮件包着邮件”的外壳。首个外层消息与内层实体按确定规则合并头字段,后续外壳的头字段不会进入结果。
  • 相同 id 和连续编号只是可验证的组合声明,不是内容哈希、发件人认证、相同副本证明或阅读回执。记录描述候选关系,运行中的本地代码才产生可用对象。

全球邮件面对的却是本地上限

用户看到的是一个文档,邮件路径看到的却是一串独立资源决定。始发系统愿意收,下一跳可能愿意排队,再下一座网关仍可能因为容量或实现限制而拒绝。SMTP 本身没有规定一个全球统一的最大消息尺寸。

1989 年的 RFC 1123 要求邮件软件至少能收发 64K 字节,并称更大的上限“非常可取”。同一段文字也承认,实际系统仍有实现限制,而人们已经用邮件传递一兆字节甚至更大的完整文档。这项要求提高了共同底线,却没有把每一跳的本地能力变成端到端承诺。

如果解决方案要求所有邮件服务器同步提高上限,它就会把异构互联网当成一个可统一升级的系统。MIME 选择了另一条路:保留既有传输单位,让多个完整外层消息承载一个更大的内层实体。整体不由某个中间机构宣布,而在接收软件拿到足够片段后才有机会形成。

外壳是完整邮件,正文仍只是局部

1992 年 6 月发布的 RFC 1341 首次定义 message/partial。这种媒体类型表示:外层消息的正文只是更大消息的一片。外层消息本身却完全真实,可以拥有自己的 Message-ID、Received 链、到达时间和投递结果。

它与 multipart/mixed 的方向相反。multipart 是一次投递中装着多个部分;message/partial 是多次投递共同指向一个内层对象。因此,编号 4 可以先到,编号 1 可以绕远路,两个外壳可以经历不同网关。传输事实不必一致,语义顺序仍可一致。

这种分层避免了把局部记录夸大成整体事实。SMTP 接收一片,只证明这一片在该观察点被接收。邮箱存下三片,只证明三份外壳存在。内层实体尚未通过本地重组之前,没有任何一条投递记录能够替它宣告完成。

三个参数构成薄而明确的共同层

id 把分片归入同一个预期集合,生成时应尽量接近全球唯一。number 规定位置,而且从 1 开始,不从 0 开始。total 声明总片数。三个参数出现的先后次序不影响含义。

最后一片必须带 total;较早片段可以省略,后来的规范版本则鼓励提前给出。这个不对称安排允许发送方在尚未算出最终片数时开始切分,同时要求它在自称最后一片时给接收方一个可检验的终点。

1993 年的 RFC 1521 保留并细化了这套机制,但没有赋予标识符额外权威。id 不是根据内容计算的摘要。两个相同编号可能携带不同字节;两个集合可能错误地复用标识;发送者也可能宣称一个永远凑不齐的总数。

接收端仍要逐项判断:从 1 到总数是否连续,重复片是否一致,不同片宣称的 total 是否冲突,规模和嵌套是否超过预算,拼接后能否解析为合法 MIME 实体。公共规则只把问题变得可重复验证,不替任何接收者作出信任决定。

重组完成后,脚手架应当消失

MIME 把部分封装设计为语义透明。若内层是一段音频,重组结果就应当表现为音频实体,而不是长期显示成“一封邮件包含另一封邮件,里面才是音频”。message/partial 是穿越尺寸限制的脚手架,不是内容永久身份的一部分。

这也意味着同一过程存在两组标识。每个外层消息可以有自己的 Message-ID,因为它们的确分别投递。内层消息也可能带有 Message-ID,它属于最终实体。外层标识回答“哪一次投递”,内层标识回答“重组出的哪一个消息”。把前者复制成后者,会抹掉旅程与对象之间的界线。

到达顺序同样没有正文排序权。编号 3 先到是队列和路径证据,不能让它排在编号 1 之前。number 才是拼接规则。速度是一种观察,不是语义授权。

第一片还携带头字段的选择权

重组不只是把正文相接。每个外壳都有 From、Date、Subject、Received 等字段,若全部保留,结果会出现互相竞争的头部。因此 MIME 同时规定切分只能发生在行边界,并给出明确的头字段合并法。

第一外层消息的普通头字段被复制,但 Content-* 以及 Subject、Message-ID、Encrypted、MIME-Version 除外。内层实体提供 Content-* 和上述几个选定字段;其他内层头字段会被丢弃。第二个以及后续外层消息的所有头字段,都不会进入重组结果。

1996 年的 RFC 2046 把这套分工写得很清楚:编号 1 的外壳提供保留下来的外部语境,内层提供媒体描述与选定的语义字段,后来各片的外壳只保留为各自旅程的证据,不参加最终头部的“投票”。

所以第一片不只是第一个字节区间。若系统只保留各段正文却过早删除第一外壳,它可能凑齐数据仍无法按照规范恢复头部。若它选择“最先抵达”的外壳而非 number=1,则是用本地偶然性发明了另一套协议。

正确的保存方式是并列保留原始外壳与派生实体。前者用于解释投递、延误、重复和不同认证结果;后者用于呈现重组后的内容。把派生结果当成一次完整投递,会洗掉系统实际做过的选择。

不同网关迫使格式采用共同最低能力

8bit 与二进制内容暴露了更深的约束。message 类型的外层不能简单再套一层 base64 或 quoted-printable 来解决。如果二进制内层被切开,每个 message/partial 外壳都会依赖二进制传输。某座只见到其中一片的 7bit 网关无法等齐其余片后重编码,因为别的片可能经过完全不同的网关。

因此,message/partial 必须使用 7bit 传输编码,内层也不能依赖 8bit 或 binary。配套的 RFC 2045 规定 MIME 传输编码总框架,RFC 2046 则为部分消息设置更严格的共同底线。

这项限制承认了去中心化路径的现实:没有网关被假定能看到全体,没有中继因为掌握一片就成为重组中心。发送方要预先选择每条独立路径都能承载的表示法。共同层增加一点编码成本,换来不依赖中央可见性的互操作性。

不同中继还可能采用不同切分阈值。一个集合重组后,结果本身仍可能是另一个 message/partial。规范明确允许这种嵌套。接收端应识别层次并在本地深度和资源上限内再次执行,而不能假定一次归组就已经看完全部历史。

SIZE 提前暴露门槛,却不负责重组

1995 年的 RFC 1870 为 SMTP 增加 SIZE 扩展。服务器可以声明固定最大尺寸,客户端可以在传送正文前给出估计值,从而让明显超限的事务更早失败。

SIZE 与 message/partial 处理的是相邻但不同的层。SIZE 是一对 SMTP 客户端与服务器对一封消息作出的入站判断;部分媒体类型是接收端把多封已投递消息解释成一个实体。SIZE 的肯定响应并非后续传输或最终投递的绝对保证,更无法证明用户代理支持重组。

前者让某个本地上限更早可见,后者让内容在多个不同上限之间仍有重建办法。把 SIZE 说成部分消息的简单替代,会把两种权力重新混在一起。

一次重组至少需要三本账

外壳账应保存每封消息的原始字节、外层 Message-ID、Received、到达时间与处置。集合账应记录规范化后的 id、编号、各片宣称的总数、缺口、冲突副本与过期决定。重组账应记录每个编号采用哪一片、拼接顺序、头字段合并、解析结果、媒体类型和后续嵌套。

来源、完整性和安全判定必须在标识符之外另做。数字齐全的集合仍可能包含恶意内容;各外壳的认证结果可能不一致;解析成功不代表签名通过,也不代表应用应显示,更不代表用户看过。

message/partial 的价值正在于克制。它让独立实现用一套最小确定规则协作,却不建立一个替所有接收者宣布真相的中心。记录给出关系,运行代码验证关系,实际生成的对象才进入下一层现实。