package storage import ( "errors" "io/fs" "os" "path/filepath" "strings" ) type Storage interface { Stat(path string) (os.FileInfo, error) Open(path string) (*os.File, error) Create(path string) (*os.File, error) Remove(path string) error Rename(oldPath, newPath string) error MkdirAll(path string) error List(path string) ([]os.FileInfo, error) } type Local struct{ Root string } func (s Local) real(path string) (string, error) { root, err := filepath.Abs(s.Root) if err != nil { return "", err } p := strings.TrimPrefix(filepath.Clean("/"+path), string(filepath.Separator)) full := filepath.Join(root, p) rel, err := filepath.Rel(root, full) if err != nil { return "", err } if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { return "", errors.New("path escapes storage root") } return full, nil } func (s Local) Stat(path string) (os.FileInfo, error) { p, e := s.real(path) if e != nil { return nil, e } return os.Stat(p) } func (s Local) Open(path string) (*os.File, error) { p, e := s.real(path) if e != nil { return nil, e } return os.Open(p) } func (s Local) Create(path string) (*os.File, error) { p, e := s.real(path) if e != nil { return nil, e } if e = os.MkdirAll(filepath.Dir(p), 0755); e != nil { return nil, e } return os.Create(p) } func (s Local) Remove(path string) error { p, e := s.real(path) if e != nil { return e } return os.RemoveAll(p) } func (s Local) Rename(a, b string) error { pa, e := s.real(a) if e != nil { return e } pb, e := s.real(b) if e != nil { return e } return os.Rename(pa, pb) } func (s Local) MkdirAll(path string) error { p, e := s.real(path) if e != nil { return e } return os.MkdirAll(p, 0755) } func (s Local) List(path string) ([]os.FileInfo, error) { p, e := s.real(path) if e != nil { return nil, e } f, e := os.Open(p) if e != nil { return nil, e } defer f.Close() return f.Readdir(-1) } var _ fs.FS