Files
MeBox/internal/service/filemanager_roots.go
T

128 lines
3.4 KiB
Go

package service
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
func (s *FileManagerService) requireAllowedPath(path string, forbidRoot bool) (string, map[string]string, error) {
roots, _, err := s.allowedRootList()
if err != nil {
return "", nil, err
}
abs, err := filepath.Abs(strings.TrimSpace(path))
if err != nil {
return "", nil, err
}
if !s.withinAllowed(abs, roots) {
return "", nil, ErrPathOutOfBounds
}
if forbidRoot && s.isAllowedRoot(abs, roots) {
return "", nil, ErrRootMutation
}
return abs, roots, nil
}
// allowedRootList returns the union of configured storage roots as
// label → absolute-path plus a sorted UI list.
func (s *FileManagerService) allowedRootList() (map[string]string, []Root, error) {
roots, err := s.allowedRoots()
if err != nil {
return nil, nil, err
}
rootList := make([]Root, 0, len(roots))
seen := map[string]struct{}{}
for label, p := range roots {
if _, ok := seen[p]; ok {
continue
}
seen[p] = struct{}{}
rootList = append(rootList, Root{Label: label, Path: p})
}
sort.Slice(rootList, func(i, j int) bool { return rootList[i].Label < rootList[j].Label })
return roots, rootList, nil
}
func (s *FileManagerService) allowedRoots() (map[string]string, error) {
roots := map[string]string{}
add := func(label, p string) {
if p == "" {
return
}
abs, err := filepath.Abs(p)
if err != nil {
return
}
if _, err := os.Stat(abs); err != nil {
return
}
roots[label] = abs
}
add("data", s.cfg.App.DataDir)
add("cache", s.cfg.Cache.CacheDir)
add("movies", s.cfg.Media.MoviesDir)
add("tv", s.cfg.Media.TVDir)
add("anime", s.cfg.Media.AnimeDir)
add("downloads", envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads"))
add("media", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"))
if s.repo != nil && s.repo.Setting != nil {
addSetting := func(label, key string) {
if value, err := s.repo.Setting.Get(context.Background(), key); err == nil {
add(label, strings.TrimSpace(value))
}
}
addSetting("organize-source", "organize.source_dir")
addSetting("organize-target", "organize.target_dir")
addSetting("downloader-savepath", "downloader.savepath")
// 兼容历史键名 qbittorrent.savepath:旧版本把下载器保存目录存在该键下
if value, err := s.repo.Setting.Get(context.Background(), "downloader.savepath"); err != nil || strings.TrimSpace(value) == "" {
addSetting("downloader-savepath", "qbittorrent.savepath")
}
}
if s.repo != nil && s.repo.Library != nil {
libs, err := s.repo.Library.List(context.Background())
if err == nil {
for _, l := range libs {
if len(l.Roots) > 0 {
for i, root := range l.Roots {
if !root.Enabled {
continue
}
label := strings.TrimSpace(root.Name)
if label == "" {
label = fmt.Sprintf("路径%d", i+1)
}
add("library:"+l.Name+":"+label, resolveMappedDestinationPath(root.Path))
}
continue
}
add("library:"+l.Name, resolveMappedDestinationPath(l.Path))
}
}
}
return roots, nil
}
func (s *FileManagerService) withinAllowed(path string, roots map[string]string) bool {
for _, root := range roots {
if pathWithin(path, root) {
return true
}
}
return false
}
func (s *FileManagerService) isAllowedRoot(path string, roots map[string]string) bool {
path = filepath.Clean(path)
for _, root := range roots {
if strings.EqualFold(path, filepath.Clean(root)) {
return true
}
}
return false
}