Spaces:
Sleeping
Sleeping
| package services | |
| import ( | |
| "errors" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "sort" | |
| "strings" | |
| "time" | |
| ) | |
| type FSService struct { | |
| root string | |
| } | |
| type Entry struct { | |
| Name string `json:"name"` | |
| Path string `json:"path"` | |
| Size int64 `json:"size"` | |
| IsDir bool `json:"isDir"` | |
| ModTime time.Time `json:"modTime"` | |
| } | |
| func NewFSService(root string) *FSService { | |
| return &FSService{root: filepath.Clean(root)} | |
| } | |
| func (s *FSService) ResolvePath(raw string) (string, error) { | |
| clean := filepath.Clean(filepath.Join(s.root, raw)) | |
| rootWithSep := s.root + string(os.PathSeparator) | |
| if clean != s.root && !strings.HasPrefix(clean, rootWithSep) { | |
| return "", errors.New("path out of root directory") | |
| } | |
| return clean, nil | |
| } | |
| func (s *FSService) List(raw string) ([]Entry, error) { | |
| path, err := s.ResolvePath(raw) | |
| if err != nil { | |
| return nil, err | |
| } | |
| entries, err := os.ReadDir(path) | |
| if err != nil { | |
| return nil, fmt.Errorf("read dir: %w", err) | |
| } | |
| result := make([]Entry, 0, len(entries)) | |
| for _, e := range entries { | |
| info, err := e.Info() | |
| if err != nil { | |
| continue | |
| } | |
| full := filepath.Join(path, e.Name()) | |
| rel, _ := filepath.Rel(s.root, full) | |
| result = append(result, Entry{ | |
| Name: e.Name(), | |
| Path: filepath.ToSlash(rel), | |
| Size: info.Size(), | |
| IsDir: e.IsDir(), | |
| ModTime: info.ModTime(), | |
| }) | |
| } | |
| sort.Slice(result, func(i, j int) bool { | |
| if result[i].IsDir != result[j].IsDir { | |
| return result[i].IsDir | |
| } | |
| return strings.ToLower(result[i].Name) < strings.ToLower(result[j].Name) | |
| }) | |
| return result, nil | |
| } | |
| func (s *FSService) Delete(raw string) error { | |
| path, err := s.ResolvePath(raw) | |
| if err != nil { | |
| return err | |
| } | |
| return os.RemoveAll(path) | |
| } | |
| func (s *FSService) Rename(rawPath, newName string) error { | |
| if strings.Contains(newName, "/") || strings.Contains(newName, "\\") { | |
| return errors.New("invalid name") | |
| } | |
| path, err := s.ResolvePath(rawPath) | |
| if err != nil { | |
| return err | |
| } | |
| target := filepath.Join(filepath.Dir(path), newName) | |
| target, err = s.ResolvePath(mustRel(s.root, target)) | |
| if err != nil { | |
| return err | |
| } | |
| return os.Rename(path, target) | |
| } | |
| func (s *FSService) Move(fromRaw, toRaw string) error { | |
| fromPath, err := s.ResolvePath(fromRaw) | |
| if err != nil { | |
| return err | |
| } | |
| toPath, err := s.ResolvePath(toRaw) | |
| if err != nil { | |
| return err | |
| } | |
| if err := os.MkdirAll(filepath.Dir(toPath), 0o755); err != nil { | |
| return err | |
| } | |
| return os.Rename(fromPath, toPath) | |
| } | |
| func (s *FSService) SaveUploadedFile(src io.Reader, targetRaw string) error { | |
| targetPath, err := s.ResolvePath(targetRaw) | |
| if err != nil { | |
| return err | |
| } | |
| if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { | |
| return err | |
| } | |
| dst, err := os.Create(targetPath) | |
| if err != nil { | |
| return err | |
| } | |
| defer dst.Close() | |
| _, err = io.Copy(dst, src) | |
| return err | |
| } | |
| func mustRel(root, target string) string { | |
| rel, err := filepath.Rel(root, target) | |
| if err != nil { | |
| return "." | |
| } | |
| return rel | |
| } | |