Help us shape the future of Go! Take the 2026 Go Developer Survey

pathx

package
v0.21.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package pathx 提供文件路径匹配与清洗工具。

MatchesPath 支持标准 glob 模式和 /** 目录前缀匹配, 适用于 .gitignore 风格的路径过滤场景(代码审查忽略路径、构建排除等)。

SanitizeRel / SanitizeRelStrict 提供仓库相对路径的清洗与 traversal 拒绝: 路径来自外部输入(webhook 载荷)时,清洗逻辑每次都要重写且各处口径容易 漂移(实测同一仓内三档:宽松 / 严格 / 只看 Clean 后的结果)——本包给基线, 严格档额外拒绝显式 ".." 段。

SanitizeFileName 提供**文件名**(展示名 / 存储名 / 上传原始名)的消毒: 与 SanitizeRel 是同族但语义相反的两个原语——SanitizeRel 问「这路径合法吗」 (越界即拒绝),SanitizeFileName 问「这名字能不能安全显示和存储」 (目录成分丢弃、控制字符丢弃、Windows 保留字符替换、超长按字节截断并保扩展名)。

用法:

pathx.MatchesPath([]string{"vendor/**", "*.test"}, "vendor/foo/bar.go") // true
pathx.MatchesPath([]string{"*.go"}, "main.go")                          // true

cleaned, ok := pathx.SanitizeRel("src/./main.go")   // "src/main.go", true
cleaned, ok := pathx.SanitizeRel("../../etc/passwd") // "", false

pathx.SanitizeFileName("a/b/c.txt")          // "c.txt"
pathx.SanitizeFileName(`C:\Users\me\报告.txt`) // "报告.txt"

Package pathx 提供文件路径匹配工具。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MatchesPath

func MatchesPath(patterns []string, path string) bool

MatchesPath 检查路径是否匹配任意一个 glob 模式。 支持标准 filepath.Match 模式以及 /** 目录前缀匹配。

模式示例:

  • "*.go" → 匹配根目录下的 .go 文件
  • "vendor/*" → 匹配 vendor 目录下的直接子项
  • "node_modules/**" → 匹配 node_modules 下所有层级的文件
  • "test/*.txt" → 匹配 test 目录下的 .txt 文件

用法:

pathx.MatchesPath([]string{"vendor/**", "*.test"}, "vendor/foo/bar.go") // true
pathx.MatchesPath([]string{"*.go"}, "main.go")                          // true

func SanitizeFileName added in v0.21.0

func SanitizeFileName(name string, opts ...FileNameOption) string

SanitizeFileName 消毒**展示 / 存储用**的文件名。

与 SanitizeRel 是同族但**语义相反**的两个原语,不要混用:

  • SanitizeRel 管**路径**(仓库相对路径):回答「这个路径合法吗、越不越界」, 越界与绝对路径一律**拒绝**(返回 ok=false)。
  • SanitizeFileName 管**文件名**(展示名 / 存储名 / 上传原始名):回答 「这个名字能不能安全地显示和存储」,目录成分一律**丢弃**(只取最后一段), 不存在 ok=false 这一说——消毒后总有名字可给(空则回退)。

同一段输入在两者下的结论必然不同,这是刻意的:

pathx.SanitizeRel("C:\\Users\\me\\报告.txt")     // "", false(Windows 盘符绝对路径 → 拒绝)
pathx.SanitizeFileName("C:\\Users\\me\\报告.txt") // "报告.txt"(文件名只要最后一段)

消毒规则(顺序即口径):

  1. **剥掉目录成分**:取最后一段,"/" 与 "\" 都算分隔符(跨平台同判)。
  2. **丢弃控制字符**:r < 0x20 或 r == 0x7f。
  3. **Windows 保留字符**(`: * ? " < > |`)替换为下划线 `_`。
  4. **去首尾空白与结尾的点**:结尾的点与空白在 Windows 上是目录项陷阱 ("file." 与 "file " 会指向 "file"),首部空白无此含义故只去空白。
  5. **空名 / 纯符号名回退**:清洗后为空,或通篇没有字母与数字("***" → "___") ⇒ 回退为 fallback(缺省 "file")。纯符号名落盘无碍但对展示无意义, 直接回退比留一串下划线干净。
  6. **超长截断**:按**字节**上限截断,且**保留扩展名**;截断点回落到 rune 边界,不会产出半个 UTF-8 字符。

非 ASCII 字符(中日韩等)一律保留——展示名与存储名的核心诉求就是别把 中文名弄丢。非法 UTF-8 字节替换为 U+FFFD,保证输出是合法 UTF-8。

用法:

pathx.SanitizeFileName("a/b/c.txt")               // "c.txt"
pathx.SanitizeFileName(`C:\Users\me\报告.txt`)      // "报告.txt"
pathx.SanitizeFileName("a:b*c?.txt")               // "a_b_c_.txt"
pathx.SanitizeFileName("...")                      // "file"(纯符号名回退)
pathx.SanitizeFileName("报告.txt",
	pathx.WithMaxFileNameLength(20))                // "报告.txt"(按字节卡)

func SanitizeRel added in v0.20.0

func SanitizeRel(p string) (cleaned string, ok bool)

SanitizeRel 清洗仓库相对路径:统一分隔符为 "/"、消除 "." 与 ".." 段, 绝对路径或越出根目录("..")时 ok=false。

定位:仓库相对路径常来自外部输入(webhook 载荷里的文件路径), 清洗与 traversal 拒绝是同一件事的两面——消费方各写各的清洗逻辑时, 口径会漂移(实测同一仓内三档:宽松 / 严格 / 只看 Clean 后的结果)。 本原语给基线,调用方在基线上选严格档。

分隔符口径:**"/" 与 "\" 都当作分隔符**。仓库路径来自外部载荷时 Windows 风格分隔符同样是路径的一部分;若按运行平台解释(Linux 上 "\" 只是普通字符),同一段载荷在 Linux 与 Windows 上会得到不同结论—— 而 Linux 才是绝大多数服务端环境,那正是 "..\..\" 形绕过最容易漏掉的地方。

判定口径:

  • 绝对路径("/foo"、UNC "//host/share"、Windows 盘符 "C:\foo")→ ok=false;
  • 越出根目录("../x"、"/../x"、"a/../../x")→ ok=false;
  • 清洗后为空("."、"a/.." —— 指向仓库根目录本身,非具体条目)→ ok=false;
  • 空串 / 纯空白 → ok=false。

".." 段的处理:宽松档下 ".." 抵消前一段("a/../b" → "b"),但抵消后 仍要留在根目录内("a/../../b" 越界 → ok=false)。

用法:

cleaned, ok := pathx.SanitizeRel("src/./main.go")   // "src/main.go", true
cleaned, ok := pathx.SanitizeRel("../../etc/passwd") // "", false

func SanitizeRelStrict added in v0.20.0

func SanitizeRelStrict(p string) (cleaned string, ok bool)

SanitizeRelStrict 是 SanitizeRel 的严格档:除 SanitizeRel 的全部判定外, **输入中显式出现的 ".." 段一律拒绝**,即使它能被 Clean 抵消掉 ("a/../b" 在宽松档得到 "b",在严格档直接 ok=false)。

为什么需要这一档:宽松档的 ".." 抵消是纯文本语义,看不见符号链接。 路径先过一层符号链接再按相对路径解析时,文本上合法的 "a/../b" 可能 指向预期之外的位置——安全档宁可拒绝,也不做这层推断。

用法:

cleaned, ok := pathx.SanitizeRelStrict("a/../b") // "", false(宽松档是 "b")

Types

type FileNameOption added in v0.21.0

type FileNameOption func(*fileNameConfig)

FileNameOption 是 SanitizeFileName 的可选配置。

命名带 FileName 前缀而非裸 Option:本包与 pathx 其它导出符号同处一个 import 组,裸 Option 会与 co-import 的其它包的 Option 撞名(矩阵硬规则)。

func WithFileNameFallback added in v0.21.0

func WithFileNameFallback(s string) FileNameOption

WithFileNameFallback 设置空名 / 纯符号名的回退名。

回退名**原样返回**(不参与消毒、不受长度上限约束)——它是调用方给的 常量,不是外部输入,原样透传可预期。

func WithMaxFileNameLength added in v0.21.0

func WithMaxFileNameLength(n int) FileNameOption

WithMaxFileNameLength 设置文件名长度上限,单位**字节**。

n <= 0 表示**不限长**(与矩阵内并发闸门「cap<=0 视为不限」的既有口径一致)。

为什么按字节而不是字符数:文件系统按字节卡(ext4 单目录项 255 字节), 按字符数卡会在多字节文件名下超限落盘失败。截断本身按 rune 边界回落, 不会产出半个 UTF-8 字符。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL