mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
128 lines
3.4 KiB
Go
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
|
|
}
|