登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go x/text/secure/bidirule 混合左右方向字符为什么不符合规则

来源:17golang原创

时间:2026-10-27 06:23:20 183浏览 收藏

先给结论:golang.org/x/text/secure/bidirule 并不是“看到左右方向字符混合就一律拒绝”。它先根据首字符把标签归为 LTR 或 RTL,再检查该方向允许出现的 Unicode Bidi 类别、结尾字符以及数字组合。混合文本不符合规则,通常是因为首字符决定的分支与后续字符类别冲突,或者 RTL 标签同时出现了 EN 和 AN 数字。

官方地址:https://pkg.go.dev/golang.org/x/text/secure/bidirule

排查这类问题要分开看三个问题:字符串“看起来”属于哪个方向、它是否满足 RFC 5893 的 Bidi Rule、以及你是一次性校验还是分块读取。DirectionString 只能回答第一个问题,完整合法性应交给 ValidString 或 Transformer。
  • 方向判断:DirectionString 根据标签内容报告方向类别。
  • 完整校验:ValidString 直接返回字符串是否符合 Bidi Rule。
  • 流式校验:New 返回实现 transform.Transformer 的校验器,发现不合法输入时返回 ErrInvalid。

为什么混合左右方向字符会不符合规则

双向规则关注的是 Unicode Bidi 属性,不是屏幕上肉眼看到的左右排列。RFC 5893 把标签分成 LTR 和 RTL:首字符是 L 时按 LTR 规则处理,首字符是 R 或 AL 时按 RTL 规则处理。后面的拉丁字母、阿拉伯字母、数字、标点和组合标记,都要落在该分支的允许集合里。

因此,“英文开头、夹一个阿拉伯字符”与“阿拉伯字符开头、带少量英文数字”不是同一件事。前者可能因为 RTL 字符不在 LTR 标签允许集合中而失败;后者也不是自动成功,还要继续满足 RTL 的末尾和数字约束。

检查点LTR 标签RTL 标签常见失败原因
首字符LR 或 AL首字符不属于三类起始属性
允许类别L、EN、ES、CS、ET、ON、BN、NSMR、AL、AN、EN、ES、CS、ET、ON、BN、NSM中途出现不属于该分支的类别
末尾L 或 EN,后面只能跟 NSMR、AL、EN 或 AN,后面只能跟 NSM末尾停在标点、空格或其它中性字符
数字组合按 LTR 规则检查EN 与 AN 不能同时出现欧洲数字与阿拉伯数字混用
Bidi Rule 按首字符分为 LTR 和 RTL 并检查允许字符与数字互斥的静态说明图
图1:Bidi Rule 的静态分类说明图,展示首字符决定方向后各自适用的字符类别与数字约束。

先用 DirectionString 判断是哪条规则

DirectionString 的用途是给排查提供方向线索。它报告的是标签的方向类别,并不等于“通过了 Bidi Rule”。当结果表明输入是 RTL 时,应该重点看 RTL 的允许集合、最后一个有效字符以及 EN/AN 是否混用;当结果是 LTR 时,则检查是否误把 R、AL 或 AN 字符放进了 LTR 标签。

package main

import (
    "fmt"

    "golang.org/x/text/secure/bidirule"
)

func main() {
    samples := []string{"alpha-7", "אבג-7", "אבג١2"}

    for _, sample := range samples {
        // 方向只用于定位排查分支,不把它当成最终合法性结论。
        direction := bidirule.DirectionString(sample)

        // ValidString 才会把首字符、允许类别、结尾和数字规则一起检查。
        valid := bidirule.ValidString(sample)
        fmt.Printf("%q direction=%v valid=%v\n", sample, direction, valid)
    }
}

这里的样例只用于说明判断维度,不能根据某个显示字体推断网络顺序。Bidi Rule 处理的是字符串中的 Unicode 属性;应用仍应把输入按原始字符串保存,再在需要的边界调用校验。

重点排查末尾字符与 NSM 组合

很多人只检查“第一个字符是不是阿拉伯字母”,却忽略了结尾。LTR 标签必须以 L 或 EN 结束,RTL 标签必须以 R、AL、EN 或 AN 结束,之后允许跟非间距标记 NSM。普通标点、分隔符或中性字符即使出现在允许集合中,也可能不能作为最终有效字符。

RTL 分支还有一个容易遗漏的门槛:如果出现欧洲数字 EN,就不能再出现阿拉伯数字 AN;反过来也一样。这个限制不是 Go 的数字解析规则,而是 Bidi Rule 为了避免显示歧义设定的标签约束。

func checkLabel(label string) error {
    // 先做整串规则判断,避免只按视觉结果猜测方向是否安全。
    if !bidirule.ValidString(label) {
        return bidirule.ErrInvalid
    }

    // 通过后再把它交给后续的域名或标识符业务逻辑。
    return nil
}

如果业务需要给出更具体的错误提示,可以在进入校验前记录字符的 Unicode 分类或把输入拆成可定位的字段,但不要把“自动替换成看起来相似的字符”当成通用修复。规范化和允许字符策略属于上层协议,不能由 ValidString 代替。

用 ValidString 处理单个值

对于请求参数、用户名片段或一次性标签,ValidString 是最清晰的入口。它返回布尔值,不负责修改原字符串,也不会告诉你是哪一个字符违反规则,所以调用方通常需要把失败记录到输入字段或业务错误中。

package main

import (
    "fmt"

    "golang.org/x/text/secure/bidirule"
)

func validateIdentifier(value string) error {
    // 校验失败时保留原值,方便上层记录字段名和请求上下文。
    if !bidirule.ValidString(value) {
        return fmt.Errorf("identifier does not conform to the Bidi Rule")
    }

    // 通过规则只代表双向标签约束满足,不代表业务名称唯一。
    return nil
}

注意,Bidi Rule 是一层安全约束,不是完整的域名、账号或标识符验证器。即使返回 true,仍可能需要做长度、字符白名单、唯一性和业务格式检查。

分块输入用 Transformer,错误后要重置

输入来自流或需要与 golang.org/x/text/transform 组合时,可以使用 bidirule.New()。官方文档说明它实现了 transform.Transformer;该转换器带有状态,复用前必须调用 Reset。当输入不符合规则时,检查 ErrInvalid,不要把已经产生的部分输出当成完整合法结果。

package main

import (
    "bytes"
    "fmt"
    "io"

    "golang.org/x/text/secure/bidirule"
    "golang.org/x/text/transform"
)

func readLabel(input []byte) error {
    transformer := bidirule.New()
    reader := transform.NewReader(bytes.NewReader(input), transformer)

    // ReadAll 让 Transformer 消费完整输入,规则错误会沿读取边界返回。
    _, err := io.ReadAll(reader)
    if err == bidirule.ErrInvalid {
        return fmt.Errorf("label failed the Bidi Rule")
    }
    return err
}

func reuseTransformer(first, second []byte) error {
    transformer := bidirule.New()
    buffer := make([]byte, 256)

    // 直接调用 Transform 时要处理输入和输出切片的消费位置。
    _, _, err := transformer.Transform(buffer, first, true)
    if err != nil {
        return err
    }

    // 同一个实例有状态,开始下一段独立输入前必须清空状态。
    transformer.Reset()
    _, _, err = transformer.Transform(buffer, second, true)
    return err
}

transform.NewReader 适合把校验放到读取边界;直接调用 Transform 则适合已有分块缓冲区的代码。无论选哪种方式,都要区分“输入还没消费完”与“规则确实非法”,并按照 transform.Transformer 的返回值继续处理。

DirectionString、ValidString 和 Transformer 的职责边界关系静态说明图
图2:API 职责关系说明图,区分方向信号、整串合法性判断与流式校验。

用这张决策表选择 API

场景优先入口重点处理不要误解
只想知道标签偏向DirectionString根据方向进入对应排查分支方向结果不是合法性结果
一次性校验完整字符串ValidString失败后返回业务错误它不会自动修复或规范化
Reader 或分块输入New + transform处理 ErrInvalid、消费进度和状态复用实例前必须 Reset

实际排障可以按这个顺序执行:先看首字符和 DirectionString,再对照对应方向的允许类别;如果是 RTL,单独检查 EN 与 AN 是否混用;最后检查末尾有效字符和 NSM。这样能把“显示方向看起来不对”和“规范校验失败”分开,避免盲目删除字符或更换编码。

常见问题

混合英文和阿拉伯文一定会失败吗?

不一定。结果取决于首字符决定的 LTR/RTL 分支、每个字符的 Bidi 类别、结尾约束和数字规则;混合本身不是唯一判据。

DirectionString 返回 RTL 就代表 ValidString 一定为 true 吗?

不是。DirectionString 只报告方向类别,RTL 字符串仍可能因末尾字符、非法类别或 EN/AN 混用而不符合规则。

为什么最后是标点会失败?

标点可能属于允许集合,但不一定是该方向允许的最终有效类别。RTL 和 LTR 都要求末尾落在各自指定的字符类别中,后面只能跟 NSM。

Transformer 校验失败后还能继续读下一条吗?

如果下一条是独立输入,应创建新的 Transformer 或先调用 Reset;同时按读取接口处理已经消费的字节,不要把失败输入产生的部分结果当成下一条数据。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>