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 ¶
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"(文件名只要最后一段)
消毒规则(顺序即口径):
- **剥掉目录成分**:取最后一段,"/" 与 "\" 都算分隔符(跨平台同判)。
- **丢弃控制字符**:r < 0x20 或 r == 0x7f。
- **Windows 保留字符**(`: * ? " < > |`)替换为下划线 `_`。
- **去首尾空白与结尾的点**:结尾的点与空白在 Windows 上是目录项陷阱 ("file." 与 "file " 会指向 "file"),首部空白无此含义故只去空白。
- **空名 / 纯符号名回退**:清洗后为空,或通篇没有字母与数字("***" → "___") ⇒ 回退为 fallback(缺省 "file")。纯符号名落盘无碍但对展示无意义, 直接回退比留一串下划线干净。
- **超长截断**:按**字节**上限截断,且**保留扩展名**;截断点回落到 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
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
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 字符。