57 lines
1.4 KiB
Go
57 lines
1.4 KiB
Go
|
|
package logic
|
||
|
|
|
||
|
|
import (
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// 校验存储目录拼接不会越出上传根目录
|
||
|
|
func TestSafeJoin(t *testing.T) {
|
||
|
|
root := "./uploader/"
|
||
|
|
cleanRoot := filepath.Clean(root)
|
||
|
|
|
||
|
|
cases := []struct {
|
||
|
|
name string
|
||
|
|
sub []string
|
||
|
|
}{
|
||
|
|
{"正常相对路径", []string{"frc", "2026-01/ab"}},
|
||
|
|
{"bucket穿越", []string{"../../etc/cron.d", "2026-01/ab"}},
|
||
|
|
{"子目录穿越", []string{"frc", "../../../etc"}},
|
||
|
|
{"绝对路径bucket", []string{"/etc/cron.d", "2026-01/ab"}},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, c := range cases {
|
||
|
|
t.Run(c.name, func(t *testing.T) {
|
||
|
|
got, err := safeJoin(root, c.sub...)
|
||
|
|
if err != nil {
|
||
|
|
return // 被拒绝同样是安全的
|
||
|
|
}
|
||
|
|
if got != cleanRoot && !strings.HasPrefix(got, cleanRoot+string(filepath.Separator)) {
|
||
|
|
t.Fatalf("路径越出上传根目录: %s", got)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := safeJoin(root, "../../etc/cron.d", "2026-01/ab"); err == nil {
|
||
|
|
t.Fatal("路径穿越未被拒绝")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 校验 bucket 名称白名单
|
||
|
|
func TestBucketPattern(t *testing.T) {
|
||
|
|
valid := []string{"frc", "my-bucket", "my_bucket", "A1"}
|
||
|
|
for _, v := range valid {
|
||
|
|
if !bucketPattern.MatchString(v) {
|
||
|
|
t.Errorf("应允许的 bucket 被拒绝: %q", v)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
invalid := []string{"", "../etc", "a/b", "a\\b", "a b", "a.b", strings.Repeat("a", 65)}
|
||
|
|
for _, v := range invalid {
|
||
|
|
if bucketPattern.MatchString(v) {
|
||
|
|
t.Errorf("应拒绝的 bucket 被放行: %q", v)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|