From 205066d39194ad50b4daa6537f9f4d13e5a46e92 Mon Sep 17 00:00:00 2001 From: Hanno Hecker Date: Wed, 30 Dec 2015 08:16:22 +0100 Subject: [PATCH] Add BasePathFs as FilterFs * add a BasePathFs implemented as FilterFs: The BasePathFs restricts all operations to a given path within an Fs. The given file name to the operations on this Fs will be prepended with the base path before calling the base Fs. Any file name (after filepath.Clean()) outside this base path will be treated as non existing file. * fix meaning of "facere" - "facio" means "I do", "facere" is the base form --- README.md | 17 +++++-- basepath.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++ basepath_test.go | 19 +++++++ 3 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 basepath.go create mode 100644 basepath_test.go diff --git a/README.md b/README.md index 83310e5..67df853 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,6 @@ implement: * TAR * S3 * Mem buffering to disk/network -* BasePath (where all paths are relative to a fixed basepath) # Filters @@ -299,12 +298,20 @@ The `AddFilter` adds a new FilterFs before any existing filters. ## Available filters -* NewReadonlyFilter() - provide a read only view of the source Fs -* NewRegexpFilter(*regexp.Regexp) - provide a filtered view on file names, any -file (not directory) NOT matching the passed regexp will be treated as -non-existing +# NewBasePathFs(Fs, path) +The BasePathFs restricts all operations to a given path within an Fs. +The given file name to the operations on this Fs will be prepended with +the base path before calling the base Fs. +# NewReadonlyFilter() + +provide a read only view of the source Fs + +# NewRegexpFilter(\*regexp.Regexp) + +provide a filtered view on file names, any file (not directory) NOT matching +the passed regexp will be treated as non-existing # About the project diff --git a/basepath.go b/basepath.go new file mode 100644 index 0000000..87ce784 --- /dev/null +++ b/basepath.go @@ -0,0 +1,130 @@ +package afero + +import ( + "os" + "path/filepath" + "strings" + "time" +) + +type BasePathFs struct { + base Fs + path string +} + +// The BasePathFs restricts all operations to a given path within an Fs. +// The given file name to the operations on this Fs will be prepended with +// the base path before calling the base Fs. +// Any file name (after filepath.Clean()) outside this base path will be +// treated as non existing file. +// +// Note that it does not clean the error messages on return, so you may +// reveal the real path on errors. +func NewBasePathFs(base Fs, path string) FilterFs { + return &BasePathFs{base: base, path: filepath.Clean(path) + string(os.PathSeparator)} +} + +func (b *BasePathFs) AddFilter(fs FilterFs) { + fs.SetSource(b.base) + b.base = fs +} + +func (b *BasePathFs) SetSource(fs Fs) { + b.base = fs +} + +// on a file outside the base path it returns the given file name and an error, +// else the given file with the base path prepended +func (b *BasePathFs) RealPath(name string) (path string, err error) { + path = filepath.Clean(filepath.Join(b.path, name)) + if !strings.HasPrefix(path, b.path) { + return name, os.ErrNotExist + } + return path, nil +} + +func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{"chtimes", name, err} + } + return b.base.Chtimes(name, atime, mtime) +} + +func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{"chmod", name, err} + } + return b.base.Chmod(name, mode) +} + +func (b *BasePathFs) Name() string { + return "BasePathFs" +} + +func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{"stat", name, err} + } + return b.base.Stat(name) +} + +func (b *BasePathFs) Rename(oldname, newname string) (err error) { + if oldname, err = b.RealPath(oldname); err != nil { + return &os.PathError{"rename", oldname, err} + } + if newname, err = b.RealPath(newname); err != nil { + return &os.PathError{"rename", newname, err} + } + return b.base.Rename(oldname, newname) +} + +func (b *BasePathFs) RemoveAll(name string) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{"remove_all", name, err} + } + return b.base.RemoveAll(name) +} + +func (b *BasePathFs) Remove(name string) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{"remove", name, err} + } + return b.base.Remove(name) +} + +func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{"openfile", name, err} + } + return b.base.OpenFile(name, flag, mode) +} + +func (b *BasePathFs) Open(name string) (f File, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{"open", name, err} + } + return b.base.Open(name) +} + +func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{"mkdir", name, err} + } + return b.base.Mkdir(name, mode) +} + +func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{"mkdir", name, err} + } + return b.base.MkdirAll(name, mode) +} + +func (b *BasePathFs) Create(name string) (f File, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{"create", name, err} + } + return b.base.Create(name) +} + +// vim: ts=4 sw=4 noexpandtab nolist syn=go diff --git a/basepath_test.go b/basepath_test.go new file mode 100644 index 0000000..f45567e --- /dev/null +++ b/basepath_test.go @@ -0,0 +1,19 @@ +package afero + +import ( + "testing" +) + +func TestBasePath(t *testing.T) { + baseFs := &MemMapFs{} + baseFs.MkdirAll("/base/path/tmp", 0777) + bp := NewBasePathFs(baseFs, "/base/path") + + if _, err := bp.Create("/tmp/foo"); err != nil { + t.Errorf("Failed to set real path") + } + + if fh, err := bp.Create("../tmp/bar"); err == nil { + t.Errorf("succeeded in creating %s ...", fh.Name()) + } +}