// udeServer: 本地目录懒加载 —— 浏览 udeServer 所在 Linux 主机的文件系统。 // 只列目录、不列文件;从根目录 "/" 开始,前端每展开一个节点请求一次该层子目录。 // 安全边界: // 1. 路径归一化: 必须是绝对路径,filepath.Clean 防穿越 // 2. 黑名单: /proc /sys /dev /run /boot 等系统目录不展示 // 3. 挂载根目录(Mount.BaseDir, 如 /var/ude/mnt)不展示,避免把已挂载点当普通目录选 // 4. 符号链接: DirEntry.IsDir() 对 symlink 返回 false,天然跳过,不跟随 // 5. 权限不足目录: 返回空列表而非报错,树展开不中断 package logic import ( "context" "os" "path/filepath" "strings" "udeServer/internal/apierr" "udeServer/internal/svc" "udeServer/internal/types" "github.com/zeromicro/go-zero/core/logx" ) // 根层直接屏蔽的系统/虚拟目录(仅在这些目录位于 "/" 下时过滤) var dirBlacklist = map[string]bool{ "proc": true, "sys": true, "dev": true, "run": true, "boot": true, "lost+found": true, "snap": true, } type ListDirChildrenLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewListDirChildrenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListDirChildrenLogic { return &ListDirChildrenLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *ListDirChildrenLogic) ListDirChildren(req *types.ListDirChildrenReq) (*types.DirChildrenResp, error) { // 1. 归一化: 空 → 根目录; 必须以 / 开头; Clean 防 ../ 穿越 p := strings.TrimSpace(req.Path) if p == "" { p = "/" } if !strings.HasPrefix(p, "/") { return nil, apierr.BadRequest("path 必须是绝对路径(以 / 开头)") } dir := filepath.Clean(p) // 2. 读取该层目录(只读一层,天然懒加载) entries, err := os.ReadDir(dir) if err != nil { // 权限不足等场景: 记日志 + 返回空列表,不中断前端树的展开 l.Errorf("read dir %s failed: %v", dir, err) return &types.DirChildrenResp{Nodes: []types.LocalDirNode{}}, nil } // 3. 挂载根目录前缀(该目录树下的挂载点不参与选择) baseDir := strings.TrimSuffix(l.svcCtx.Config.Mount.BaseDir, "/") nodes := make([]types.LocalDirNode, 0, len(entries)) for _, e := range entries { if !e.IsDir() { continue // 只要目录,文件一律跳过 } name := e.Name() if dir == "/" && dirBlacklist[name] { continue // 根层黑名单系统目录 } full := filepath.ToSlash(filepath.Join(dir, name)) if baseDir != "" && (full == baseDir || strings.HasPrefix(full, baseDir+"/")) { continue // ude 自己的挂载根目录不展示 } nodes = append(nodes, types.LocalDirNode{ Name: name, Path: full, IsDir: true, HasChildren: hasSubDir(full), }) } return &types.DirChildrenResp{Nodes: nodes}, nil } // hasSubDir 判断目录下是否还有子目录(只读一层、找到一个即返回,开销可控) func hasSubDir(dir string) bool { subs, err := os.ReadDir(dir) if err != nil { return false } for _, s := range subs { if s.IsDir() { return true } } return false }