摘要

  • RFC 3454 规定“映射—归一化—禁止—双向检查”的固定顺序,但每个使用协议仍须明确自己的 profile、字符范围与策略。
  • 输出相等只表示在该 profile 和 Unicode 表下相等;它不证明语言拼写、视觉呈现、用户意图、账号控制、授权或人的身份相同。

比较问题先于身份问题

Unicode 让互联网协议能承载远超 ASCII 的文字,也让简单字节比较失去可靠性。用户眼中相同的文本,可能由不同码点序列、大小写形式、兼容字符或不可见标记组成。2002 年 12 月发布的 RFC 3454 因此定义 stringprep,在存储或比较之前准备国际化字符串。

它的目标很克制:提高两个人输入自认为相同字符串时得到逐字符一致结果的概率。规范明确承认无法覆盖各语言、各文字系统的所有替代拼写。纯文本、RFC Editor 记录、Datatracker、历史、引用、后续引用与勘误证明规范记录,却不能说明用户本来想写什么,也不能说明谁控制最后生成的标识符。

Profile 才把框架变成规则

Stringprep 不能独立运行。使用它的协议必须定义 profile,说明适用范围、字符库、映射表、归一化方式、禁止输出、双向文本检查和额外限制。两个 profile 可以合法地对同一输入产生不同结果。

映射可以删掉字符、以一个字符替换另一个,或把一个展开为多个;映射产生的新字符在同一阶段不会再次扫描。如果 profile 选择归一化,RFC 3454 要求使用兼容等价的 NFKC,相关算法由 Unicode 标准附件 15说明。更多输入因此可能收敛,但收敛不等于它们在所有字体中看起来相同,更不等于跨语言含义相同。

执行顺序就是合同的一部分

四个步骤必须依次执行:映射、归一化、禁止、双向检查。调换次序可能改变输出。映射阶段被删除的字符不会进入禁止检查;归一化也可能产生随后必须拒绝的序列。

禁止阶段只能返回准备后的字符串或错误,不能同时返回两者。Profile 可以排除控制字符、私用码点、非字符、代理码点和标签字符等。双向规则约束包含从右向左字符的字符串,避免边界方向产生不稳定标识符。这些机制降低语法与显示风险,却没有认证输入者。Unicode 技术报告 36后来系统描述视觉混淆;即使归一化完全正确,也不能消除所有同形字符,更不能从相等字符串推导相同所有者。

Unicode 版本是证据的一部分

RFC 3454 把表固定在 Unicode 3.2,并明确不能自动套用到后来版本。这使当时实现可以复现,也意味着“字符串有效”若不附带 profile 和表版本就是不完整陈述。

未分配码点暴露了兼容难题:存储字符串不得含有未分配码点,因为未来赋值可能改变处理;查询则可以更宽容,让新版客户端仍能搜索旧系统。同一个输入在创建时被拒绝、在查询时被容忍,并不矛盾。操作类型本来就是判断条件。

只保留最终字符串的日志会丢失输入、操作类型、表版本和变换过程。日后升级 profile 时,人们可能看到不同结果,却无法判断是用户改名、软件升级,还是旧记录从未保存足够证据。

Nameprep 展示了价值,也展示了边界

RFC 3491 把 Nameprep 定义为早期 IDNA 的 stringprep profile,RFC 3490 将其放入国际化域名流程。RFC 4690 后来记录相关问题,RFC 5890 所属的 IDNA2008 体系不再使用 stringprep。

这不是说比较不再必要,而是固定 Unicode 版本和既有 profile 难以长期演化。RFC 6885提出 PRECIS 问题,RFC 7564取代 RFC 3454,RFC 8264又取代首版 PRECIS。规范更替证明持续修正,不证明所有旧标识符在某一天突然改变含义。

Heng Lu 的现实层次要求分别保存原始输入、profile、Unicode 表、各阶段结果、错误或输出、比较结果、账号绑定与授权。运行代码优先要求主张停在程序真正执行的操作:代码能证明两个输出匹配,不能替它们指定所有者。最小初始规范则解释为何通用框架把身份与授权留给使用协议。这是后来的编辑分析,不是对作者动机的代言。

RFC 3454 的教训并非归一化无力,而是它只在明确边界内有力。它能让比较确定,不能把比较变成身份。

来源