listdirchildrenlogic.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. // udeServer: 本地目录懒加载 —— 浏览 udeServer 所在 Linux 主机的文件系统。
  2. // 只列目录、不列文件;从根目录 "/" 开始,前端每展开一个节点请求一次该层子目录。
  3. // 安全边界:
  4. // 1. 路径归一化: 必须是绝对路径,filepath.Clean 防穿越
  5. // 2. 黑名单: /proc /sys /dev /run /boot 等系统目录不展示
  6. // 3. 挂载根目录(Mount.BaseDir, 如 /var/ude/mnt)不展示,避免把已挂载点当普通目录选
  7. // 4. 符号链接: DirEntry.IsDir() 对 symlink 返回 false,天然跳过,不跟随
  8. // 5. 权限不足目录: 返回空列表而非报错,树展开不中断
  9. package logic
  10. import (
  11. "context"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. "udeServer/internal/apierr"
  16. "udeServer/internal/svc"
  17. "udeServer/internal/types"
  18. "github.com/zeromicro/go-zero/core/logx"
  19. )
  20. // 根层直接屏蔽的系统/虚拟目录(仅在这些目录位于 "/" 下时过滤)
  21. var dirBlacklist = map[string]bool{
  22. "proc": true,
  23. "sys": true,
  24. "dev": true,
  25. "run": true,
  26. "boot": true,
  27. "lost+found": true,
  28. "snap": true,
  29. }
  30. type ListDirChildrenLogic struct {
  31. logx.Logger
  32. ctx context.Context
  33. svcCtx *svc.ServiceContext
  34. }
  35. func NewListDirChildrenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListDirChildrenLogic {
  36. return &ListDirChildrenLogic{
  37. Logger: logx.WithContext(ctx),
  38. ctx: ctx,
  39. svcCtx: svcCtx,
  40. }
  41. }
  42. func (l *ListDirChildrenLogic) ListDirChildren(req *types.ListDirChildrenReq) (*types.DirChildrenResp, error) {
  43. // 1. 归一化: 空 → 根目录; 必须以 / 开头; Clean 防 ../ 穿越
  44. p := strings.TrimSpace(req.Path)
  45. if p == "" {
  46. p = "/"
  47. }
  48. if !strings.HasPrefix(p, "/") {
  49. return nil, apierr.BadRequest("path 必须是绝对路径(以 / 开头)")
  50. }
  51. dir := filepath.Clean(p)
  52. // 2. 读取该层目录(只读一层,天然懒加载)
  53. entries, err := os.ReadDir(dir)
  54. if err != nil {
  55. // 权限不足等场景: 记日志 + 返回空列表,不中断前端树的展开
  56. l.Errorf("read dir %s failed: %v", dir, err)
  57. return &types.DirChildrenResp{Nodes: []types.LocalDirNode{}}, nil
  58. }
  59. // 3. 挂载根目录前缀(该目录树下的挂载点不参与选择)
  60. baseDir := strings.TrimSuffix(l.svcCtx.Config.Mount.BaseDir, "/")
  61. nodes := make([]types.LocalDirNode, 0, len(entries))
  62. for _, e := range entries {
  63. if !e.IsDir() {
  64. continue // 只要目录,文件一律跳过
  65. }
  66. name := e.Name()
  67. if dir == "/" && dirBlacklist[name] {
  68. continue // 根层黑名单系统目录
  69. }
  70. full := filepath.ToSlash(filepath.Join(dir, name))
  71. if baseDir != "" && (full == baseDir || strings.HasPrefix(full, baseDir+"/")) {
  72. continue // ude 自己的挂载根目录不展示
  73. }
  74. nodes = append(nodes, types.LocalDirNode{
  75. Name: name,
  76. Path: full,
  77. IsDir: true,
  78. HasChildren: hasSubDir(full),
  79. })
  80. }
  81. return &types.DirChildrenResp{Nodes: nodes}, nil
  82. }
  83. // hasSubDir 判断目录下是否还有子目录(只读一层、找到一个即返回,开销可控)
  84. func hasSubDir(dir string) bool {
  85. subs, err := os.ReadDir(dir)
  86. if err != nil {
  87. return false
  88. }
  89. for _, s := range subs {
  90. if s.IsDir() {
  91. return true
  92. }
  93. }
  94. return false
  95. }