如何校验Unicode字符串的Well-formedness(格式良构性)
Unicode业务输入合规性校验落地方案
首先明确一个常见认知偏差:Unicode官方定义的基础Well-formedness规则,只覆盖编码层面的合法性判断——比如代理对是否完整、码位是否在Unicode分配的合法范围内,完全无法拦截你遇到的组合字符滥用、Zalgo文本、显示错乱这类问题。这类显示层的滥用管控,对应Unicode安全相关的判定规则,落地可以分三层做:
第一层:前置基础校验
- 先做编码合法性判断:拦截非法代理对、未分配的无效码位、非业务放开的私有区码位。这层是所有字符串处理的基础,绝大多数编程语言的标准库都自带现成方法,不需要自己实现,比如Java的
Character.isValidCodePoint()、Python的编码异常捕获+str.isprintable()组合就能覆盖。 - 默认拦截所有控制类字符:除了业务确需放开的换行、普通制表符之外,所有Cc(控制字符)、Cf(格式字符)类码位全部拦截,零宽空格、双向文本控制符这类高频被用来制造显示混乱、钓鱼仿冒的字符都在这个分类里。
第二层:核心滥用场景拦截(覆盖开头组合字符污染问题)
- 按字形集群做边界校验:按照Unicode标准定义的字形集群分割规则把输入拆成独立的显示单元,只要拆分后的第一个单元以组合字符(通用类别为Mn、Mc、Me的码位)开头,直接判定为不合规,正好解决组合字符放在开头拼接时污染前序文本的问题。
- 限制单个字形的组合字符数量:正常人类使用的文本里,单个基础字符附带的组合变音符号不会超过3-4个,Zalgo类异常文本通常会给单个字符叠加十几个甚至几十个组合字符制造错乱效果,你可以根据业务宽松度设置阈值(普通社区场景设为单字形组合字符不超过8个足够,宽松场景可以放到15个),超过阈值直接拦截。
- 不要自己写正则维护组合字符列表,Unicode每年都会新增码位,直接用成熟的ICU库或者各语言生态里跟随Unicode版本同步更新的字形处理库就行,维护成本极低,不容易漏规则。
第三层:可选进阶校验(适合对用户名一致性要求高的场景)
- 做规范化校验:先把输入做NFKC规范化处理,检测是否存在和常用字符同形的特殊Unicode字,避免用户用同形异义字仿冒其他用户名实施钓鱼。
- 做语系范围准入:如果你的业务主要服务特定语系的用户,直接从码位段层面限制允许输入的字符范围,比如面向中文用户的业务不需要放开冷门古文字、小众特殊符号的码位,能大幅缩小滥用面。
不要为了拦截滥用直接全量禁止组合字符,很多语言的正常文字(比如带变音符号的印欧语、部分少数民族文字)本身就依赖组合字符显示,直接封禁会误伤正常用户的合理使用。
给一个最小可落地的校验逻辑伪代码参考:
MAX_COMBINING_PER_GRAPHEME = 8 ALLOWED_CONTROL_CHARS = {'\n', '\t'} def validate_user_input(input_str: str) -> bool: # 基础码位校验 for code_point in input_str: if not is_valid_unicode_codepoint(code_point): return False if is_control_char(code_point) and code_point not in ALLOWED_CONTROL_CHARS: return False # 字形集群校验 grapheme_list = split_grapheme_clusters(input_str) # 首字形不能以组合字符开头 if is_combining_mark(grapheme_list[0][0]): return False # 单字形组合字符数量校验 for grapheme in grapheme_list: combining_count = 0 for c in grapheme: if is_combining_mark(c): combining_count += 1 if combining_count > MAX_COMBINING_PER_GRAPHEME: return False return True
内容的提问来源于stack exchange,提问作者Dolda2000
相关产品推荐
相关产品推荐

