package main import ( "os" "path/filepath" "testing" ) func TestRuleProblem(t *testing.T) { roots := []string{"/home", "/mnt", "/media"} ok := []string{"/home/u/docs", "/mnt/nas/a", "/media/u/Data/资料", "/mnt/x"} bad := map[string]string{ "/etc": "只能选择", "/home": "本身", "/mnt": "本身", "/home/u": "用户主目录", "/home/u/.ssh": "隐藏", "/home/u/a/.git/x": "隐藏", "/homex/u/a": "只能选择", "/home/u/../../etc": "规范", "relative/path": "规范", "/srv/agent-server": "只能选择", "/media/u/Data/.env": "隐藏", } for _, p := range ok { if msg := ruleProblem(p, roots); msg != "" { t.Errorf("%s 应该允许,实际:%s", p, msg) } } for p, want := range bad { if msg := ruleProblem(p, roots); msg == "" || !contains(msg, want) { t.Errorf("%s 应该拒绝(%s),实际:%q", p, want, msg) } } } func contains(s, sub string) bool { return len(sub) == 0 || (len(s) >= len(sub) && indexOf(s, sub) >= 0) } func indexOf(s, sub string) int { for i := 0; i+len(sub) <= len(s); i++ { if s[i:i+len(sub)] == sub { return i } } return -1 } // 用临时目录模拟允许的根目录 func tempRoots(t *testing.T) (string, []string) { base, _ := filepath.EvalSymlinks(t.TempDir()) root := filepath.Join(base, "mnt") if err := os.MkdirAll(filepath.Join(root, "data", "docs"), 0o755); err != nil { t.Fatal(err) } return base, []string{root} } func TestCheckSourceAndBrowse(t *testing.T) { base, roots := tempRoots(t) root := roots[0] os.MkdirAll(filepath.Join(root, "data", ".hidden"), 0o755) os.MkdirAll(filepath.Join(base, "outside"), 0o755) os.Symlink(filepath.Join(base, "outside"), filepath.Join(root, "escape")) os.Symlink(filepath.Join(root, "data", "docs"), filepath.Join(root, "inner")) os.WriteFile(filepath.Join(root, "file.txt"), []byte("x"), 0o644) if real, err := checkSource(filepath.Join(root, "data", "docs"), roots); err != nil || real != filepath.Join(root, "data", "docs") { t.Fatalf("应该通过:%v %v", real, err) } if real, err := checkSource(filepath.Join(root, "inner"), roots); err != nil || real != filepath.Join(root, "data", "docs") { t.Fatalf("范围内的符号链接解析为真实路径:%v %v", real, err) } cases := map[string]string{ filepath.Join(root, "escape"): "not_allowed", filepath.Join(root, "data", ".hidden"): "not_allowed", filepath.Join(root, "file.txt"): "not_allowed", filepath.Join(root, "nope"): "path_missing", root: "not_allowed", "relative": "not_allowed", } for p, state := range cases { if _, err := checkSource(p, roots); err == nil || err.State != state { t.Errorf("%s 应该是 %s,实际 %v", p, state, err) } } res, cerr := browse(root, roots) if cerr != nil { t.Fatal(cerr) } got := map[string]browseEntry{} for _, e := range res.Entries { got[e.Name] = e } if _, ok := got["file.txt"]; ok { t.Error("文件不应出现在目录选择中") } if !got["data"].Selectable || got["escape"].Selectable || !got["inner"].Selectable { t.Errorf("可选状态不正确:%+v", got) } if res.Parent != "" { t.Errorf("根目录的上级应为空:%q", res.Parent) } sub, _ := browse(filepath.Join(root, "data"), roots) for _, e := range sub.Entries { if e.Name == ".hidden" { t.Error("隐藏目录不应出现") } } if _, cerr := browse(filepath.Join(base, "outside"), roots); cerr == nil { t.Error("不能浏览范围以外的目录") } if _, cerr := browse(filepath.Join(root, "escape"), roots); cerr == nil { t.Error("不能经由符号链接浏览范围以外的目录") } top, _ := browse("", roots) if len(top.Entries) != 1 || top.Entries[0].Selectable { t.Errorf("顶层列出允许的根目录且不能直接选择:%+v", top.Entries) } }