摘要

  • MIME 让每个正文实体获得一个采用 Message-ID 语法的 Content-ID,其他部件因此不必依赖排列位置或外部地址就能引用它。
  • multipart/related 可以用 Content-ID 选定复合对象的根部件;cid: 与长格式 mid: 则把同一标识写进链接。
  • “全局唯一”只是生成约束,不会把标识变成内容哈希、身份认证或全球可取回的资源。最终选择仍取决于容器规则和接收端。

链接第一次可以只朝消息内部走

网页中的链接通常意味着一次离开:浏览器读到地址,再去另一台服务器请求对象。MIME 复合消息形成了另一种路径。一段 HTML 可以引用图片,而图片已经作为另一个正文部件随邮件一同到达;解析链接只是遍历收到的 MIME 树,不必建立网络连接。

这不是简单地把几个文件塞进同一个信封。网关可能重排部件,邮件库可能抽取附件,递归的 multipart 结构里还可能嵌套多个复合对象。若引用只写“第三个部件”或文件名,一次无害的重组就可能改变含义。消息需要一种能随传输保留下来、又不冒充外部位置的名称。

RFC 1341 在 1992 年把 Internet 邮件正文扩展为带类型、可递归组合的实体,也把可选的 Content-ID 与 Content-Description 字段纳入 MIME。后者可以用自然语言说明内容,前者回答“另一个引用所指的是哪个实体”。它与其他字段各管一层:Content-Type 说明解码后的字节应如何解释,Content-Transfer-Encoding 说明字节如何适应传输,multipart boundary 划分序列边界,而 Content-ID 提供逻辑指称。

1996 年的 RFC 2045 明确了标识的语法和生成要求。Content-ID 使用与 Message-ID 相同的形式,并应生成得在全世界唯一。但复用语法并不等于复用对象:Message-ID 命名整封消息,Content-ID 标记一个 MIME 实体,这个实体可能位于层层嵌套之中。

规范举出的用途之一是缓存。message/external-body 描述通过外部方式访问的数据,生成这种实体时必须提供 Content-ID,使缓存能够辨认不同访问说明背后的内容。这个标识却不是从字节计算而来。两个相同值表达的是生成方对内容身份的主张,并非密码学证明;一个格式正确、看似唯一的值,也不会自动建立 DNS 记录、HTTP 源站或公共查询服务。

允许重复的例外揭示了标识的真正层级

如果把 Content-ID 当作数据库表的主键,就会自然地要求一个值只能对应一个序列化节点。RFC 2046 中的 multipart/alternative 表明,这个规则过于粗糙。该容器存放同一信息的不同表现形式,接收端通常选择自己能够显示的最后一种。

当格式转换会损失信息时,不同版本应使用不同的 Content-ID。若多个 message/external-body 部件只是给出同一数据的不同获取方法,它们可以共享 Content-ID,让缓存知道目标仍是同一个对象。此时选择哪个部件,不由标识单独决定,而由外围 multipart 的语义决定。

因此,Content-ID 不是对“边界分隔块”做物理唯一编号,而是关于预期内容身份的一条证据。见到重复值就全部拒绝,会破坏合法的替代表现;无论父容器是什么都任意接受重复值,又会制造歧义甚至被恶意消息利用。可解释的解析必须把标识和它所在的结构一起保留。

复合对象还需要明确哪一部分是根

带图片的 HTML 邮件不是一袋彼此无关的附件。文档组织其他组件,图片和样式离开文档后也不再表达完整意图。1997 年的 RFC 2110 首次标准化了聚合 HTML 文档的 MIME 封装,并把 Content-ID、CID URL 和 Content-Location 串联起来。

1998 年,RFC 2387 将通用容器定义为 multipart/related。type 参数预告根部件的媒体类型;可选的 start 参数通过 Content-ID 指向首先处理的部件。如果没有 start,序列中的第一个部件就是根。

这里分配了两种权力:顺序提供默认值,显式标识可以覆盖默认值。网关若把根部件移到后面,却没有保留或改写 start 关系,消息的实际含义就会变化。反过来,start 也不能跳出正确的 related 容器随意取对象。若 type 与根部件真实的 Content-Type 不一致,RFC 2387 把用户代理行为留为未定义。

related 关系还可能压过附件的一般展示建议。文件名和 Content-Disposition 可以提示“如何另存”,但复合对象的规则决定图片是独立附件,还是根文档不可缺少的组件。只保存字节、不保存关系,等于只保存了材料而没有保存作品的组织方式。

cid: 是标头到链接语法的转换

RFC 2392 为 Message-ID 和 Content-ID 定义了 URL 形式。CID URL 由 cid: 加经过 URL 编码的 addr-spec 构成。实现程序去掉前缀、还原百分号编码,再加回尖括号,就能把恢复后的值直接与 MIME 树中的 Content-ID 标头比较。

采用 URL 形式并未强制使用某种网络传输。这个 scheme 描述的是解析方法:到 MIME 结构中寻找匹配标头。许多邮件库只索引整封消息而不索引其中每一个部件,因此 RFC 2392 还定义了长格式 mid:message-id/content-id,并要求实现支持它。第一段找到消息,第二段找到消息内的实体。短格式 cid: 通常只在同一封消息内引用;存储系统也可以利用全局唯一约定扩大查找范围,但那是产品选择,不是互联网上的通用可达性。

这一规范也保留了替代表现的有限重复情形。当一封消息里有几个相同 Content-ID 的部件时,父容器的规则决定链接最终落到哪一个。看似全球唯一的字符串,仍要依赖本地语法树才能得到确定结果。

位置是另一类主张

RFC 2557 在 1999 年取代 RFC 2110,进一步区分 Content-ID、Content-Location 和 Message-ID。Content-Location 可以是绝对或相对 URI,却不承诺收件人一定能从该 URI 获取资源。它可能只在受限网络内有效,甚至可能是为复合文档解析而设置的虚构位置。

这并不矛盾。打包消息中的 Content-Location 帮助把原文中的地址对应到随包携带的部件,也可以记录该表现形式概念上的来源;它不是一次成功取回的回执。

RFC 2557 还明确区分 MHTML 聚合包的 URI 与根文档的 URI。请求前者得到的是一个包;请求后者可能只得到根页面,再由客户端分别获取依赖。两条路径甚至可能在不同时间看到不同快照。包的身份、根的身份、组件的位置和实际获取结果不能合并成一个字段。

Content-ID 的价值恰恰来自克制:它只承诺给内部关系一个相对稳定的名字。之后,接收端仍须确定消息上下文、解析父容器、选择允许的替代表现、解码正文、执行安全策略,并决定是否渲染。