diff --git a/filter.go b/filter.go index ae85b36..5cdcf2d 100644 --- a/filter.go +++ b/filter.go @@ -15,14 +15,18 @@ import ( // of filtering (e.g. admins get write access, normal users just readonly) type FilterFs interface { Fs - AddFilter(Fs) + AddFilter(FilterFs) + SetSource(Fs) } type Filter struct { - chain []Fs source Fs } +func (f *Filter) SetSource(fs Fs) { + f.source = fs +} + // create a new FilterFs that implements Fs, argument must be an Fs, not // a FilterFs func NewFilter(fs Fs) FilterFs { @@ -30,101 +34,44 @@ func NewFilter(fs Fs) FilterFs { } // prepend a filter in the filter chain -func (f *Filter) AddFilter(fs Fs) { - c := []Fs{fs} - for _, ch := range f.chain { - c = append(c, ch) - } - f.chain = c +func (f *Filter) AddFilter(fs FilterFs) { + fs.SetSource(f.source) + f.source = fs } func (f *Filter) Create(name string) (file File, err error) { - for _, c := range f.chain { - file, err = c.Create(name) - if err != nil { - return - } - } return f.source.Create(name) } -func (f *Filter) Mkdir(name string, perm os.FileMode) (err error) { - for _, c := range f.chain { - err = c.Mkdir(name, perm) - if err != nil { - return - } - } +func (f *Filter) Mkdir(name string, perm os.FileMode) (error) { return f.source.Mkdir(name, perm) } -func (f *Filter) MkdirAll(path string, perm os.FileMode) (err error) { - for _, c := range f.chain { - err = c.MkdirAll(path, perm) - if err != nil { - return - } - } +func (f *Filter) MkdirAll(path string, perm os.FileMode) (error) { return f.source.MkdirAll(path, perm) } -func (f *Filter) Open(name string) (file File, err error) { - for _, c := range f.chain { - file, err = c.Open(name) - if err != nil { - return - } - } +func (f *Filter) Open(name string) (File, error) { return f.source.Open(name) } -func (f *Filter) OpenFile(name string, flag int, perm os.FileMode) (file File, err error) { - for _, c := range f.chain { - file, err = c.OpenFile(name, flag, perm) - if err != nil { - return - } - } +func (f *Filter) OpenFile(name string, flag int, perm os.FileMode) (File, error) { return f.source.OpenFile(name, flag, perm) } -func (f *Filter) Remove(name string) (err error) { - for _, c := range f.chain { - err = c.Remove(name) - if err != nil { - return - } - } +func (f *Filter) Remove(name string) (error) { return f.source.Remove(name) } -func (f *Filter) RemoveAll(path string) (err error) { - for _, c := range f.chain { - err = c.RemoveAll(path) - if err != nil { - return - } - } +func (f *Filter) RemoveAll(path string) (error) { return f.source.RemoveAll(path) } -func (f *Filter) Rename(oldname, newname string) (err error) { - for _, c := range f.chain { - err = c.Rename(oldname, newname) - if err != nil { - return - } - } +func (f *Filter) Rename(oldname, newname string) (error) { return f.source.Rename(oldname, newname) } -func (f *Filter) Stat(name string) (fi os.FileInfo, err error) { - for _, c := range f.chain { - fi, err = c.Stat(name) - if err != nil { - return - } - } +func (f *Filter) Stat(name string) (os.FileInfo, error) { return f.source.Stat(name) } @@ -132,22 +79,10 @@ func (f *Filter) Name() string { return f.source.Name() } -func (f *Filter) Chmod(name string, mode os.FileMode) (err error) { - for _, c := range f.chain { - err = c.Chmod(name, mode) - if err != nil { - return - } - } +func (f *Filter) Chmod(name string, mode os.FileMode) (error) { return f.source.Chmod(name, mode) } -func (f *Filter) Chtimes(name string, atime, mtime time.Time) (err error) { - for _, c := range f.chain { - err = c.Chtimes(name, atime, mtime) - if err != nil { - return - } - } +func (f *Filter) Chtimes(name string, atime, mtime time.Time) (error) { return f.source.Chtimes(name, atime, mtime) } diff --git a/filter_readonly.go b/filter_readonly.go index 0ff67a7..c24c09b 100644 --- a/filter_readonly.go +++ b/filter_readonly.go @@ -7,12 +7,27 @@ import ( ) type ReadOnlyFilter struct { + source Fs } -func NewReadonlyFilter() Fs { +func NewReadonlyFilter() FilterFs { return &ReadOnlyFilter{} } +func (r *ReadOnlyFilter) SetSource(fs Fs) { + r.source = fs +} + +// prepend a filter in the filter chain +func (r *ReadOnlyFilter) AddFilter(fs FilterFs) { + fs.SetSource(r.source) + r.source = fs +} + +func (r *ReadOnlyFilter) ReadDir(name string) ([]os.FileInfo, error) { + return ReadDir(r.source, name) +} + func (r *ReadOnlyFilter) Chtimes(n string, a, m time.Time) error { return syscall.EPERM } @@ -22,11 +37,11 @@ func (r *ReadOnlyFilter) Chmod(n string, m os.FileMode) error { } func (r *ReadOnlyFilter) Name() string { - return "readOnlyFilter" + return "ReadOnlyFilter" } func (r *ReadOnlyFilter) Stat(name string) (os.FileInfo, error) { - return nil, nil + return r.source.Stat(name) } func (r *ReadOnlyFilter) Rename(o, n string) error { @@ -45,11 +60,11 @@ func (r *ReadOnlyFilter) OpenFile(name string, flag int, perm os.FileMode) (File if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { return nil, syscall.EPERM } - return nil, nil + return r.source.OpenFile(name, flag, perm) } func (r *ReadOnlyFilter) Open(n string) (File, error) { - return nil, nil + return r.source.Open(n) } func (r *ReadOnlyFilter) Mkdir(n string, p os.FileMode) error { diff --git a/filter_regexp.go b/filter_regexp.go new file mode 100644 index 0000000..36e97e6 --- /dev/null +++ b/filter_regexp.go @@ -0,0 +1,224 @@ +package afero + +import ( + "os" + "regexp" + "syscall" + "time" +) + +// The RegexpFilter filters files (not directories) by regular expression. Only +// files matching the given regexp will be allowed, all others get a ENOENT error ( +// "No such file or directory"). +// +type RegexpFilter struct { + re *regexp.Regexp + source Fs +} + +type RegexpFile struct { + f File + re *regexp.Regexp +} + +func NewRegexpFilter(re *regexp.Regexp) FilterFs { + return &RegexpFilter{re: re} +} + +// prepend a filter in the filter chain +func (r *RegexpFilter) AddFilter(fs FilterFs) { + fs.SetSource(r.source) + r.source = fs +} + +func (r *RegexpFilter) SetSource(fs Fs) { + r.source = fs +} + +func (r *RegexpFilter) matchesName(name string) error { + if r.re == nil { + return nil + } + if r.re.MatchString(name) { + return nil + } + return syscall.ENOENT +} + +func (r *RegexpFilter) dirOrMatches(name string) error { + dir, err := IsDir(r.source, name) + if err != nil { + return err + } + if dir { + return nil + } + return r.matchesName(name) +} + +func (r *RegexpFilter) Chtimes(name string, a, m time.Time) error { + if err := r.dirOrMatches(name); err != nil { + return err + } + return r.source.Chtimes(name, a, m) +} + +func (r *RegexpFilter) Chmod(name string, mode os.FileMode) error { + if err := r.dirOrMatches(name); err != nil { + return err + } + return r.source.Chmod(name, mode) +} + +func (r *RegexpFilter) Name() string { + return "RegexpFilter" +} + +func (r *RegexpFilter) Stat(name string) (os.FileInfo, error) { + if err := r.dirOrMatches(name); err != nil { + return nil, err + } + return r.source.Stat(name) +} + +func (r *RegexpFilter) Rename(oldname, newname string) error { + dir, err := IsDir(r.source, oldname) + if err != nil { + return err + } + if dir { + return nil + } + if err := r.matchesName(oldname); err != nil { + return err + } + if err := r.matchesName(newname); err != nil { + return err + } + return r.source.Rename(oldname, newname) +} + +func (r *RegexpFilter) RemoveAll(p string) error { + dir, err := IsDir(r.source, p) + if err != nil { + return err + } + if !dir { + if err := r.matchesName(p); err != nil { + return err + } + } + return r.source.RemoveAll(p) +} + +func (r *RegexpFilter) Remove(name string) error { + if err := r.dirOrMatches(name); err != nil { + return err + } + return r.source.Remove(name) +} + +func (r *RegexpFilter) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + if err := r.dirOrMatches(name); err != nil { + return nil, err + } + return r.source.OpenFile(name, flag, perm) +} + +func (r *RegexpFilter) Open(name string) (File, error) { + dir, err := IsDir(r.source, name) + if err != nil { + return nil, err + } + if !dir { + if err := r.matchesName(name); err != nil { + return nil, err + } + } + f, err := r.source.Open(name) + return &RegexpFile{f: f, re: r.re}, nil +} + +func (r *RegexpFilter) Mkdir(n string, p os.FileMode) error { + return r.source.Mkdir(n, p) +} + +func (r *RegexpFilter) MkdirAll(n string, p os.FileMode) error { + return r.source.MkdirAll(n, p) +} + +func (r *RegexpFilter) Create(name string) (File, error) { + if err := r.matchesName(name); err != nil { + return nil, err + } + return r.source.Create(name) +} + +func (f *RegexpFile) Close() error { + return f.f.Close() +} + +func (f *RegexpFile) Read(s []byte) (int, error) { + return f.f.Read(s) +} + +func (f *RegexpFile) ReadAt(s []byte, o int64) (int, error) { + return f.f.ReadAt(s, o) +} + +func (f *RegexpFile) Seek(o int64, w int) (int64, error) { + return f.f.Seek(o, w) +} + +func (f *RegexpFile) Write(s []byte) (int, error) { + return f.f.Write(s) +} + +func (f *RegexpFile) WriteAt(s []byte, o int64) (int, error) { + return f.f.WriteAt(s, o) +} + +func (f *RegexpFile) Name() string { + return f.f.Name() +} + +func (f *RegexpFile) Readdir(c int) (fi []os.FileInfo, err error) { + var rfi []os.FileInfo + rfi, err = f.f.Readdir(c) + if err != nil { + return nil, err + } + for _, i := range rfi { + if i.IsDir() || f.re.MatchString(i.Name()) { + fi = append(fi, i) + } + } + return fi, nil +} + +func (f *RegexpFile) Readdirnames(c int) (n []string, err error) { + fi, err := f.Readdir(c) + if err != nil { + return nil, err + } + for _, s := range fi { + n = append(n, s.Name()) + } + return n, nil +} + +func (f *RegexpFile) Stat() (os.FileInfo, error) { + return f.f.Stat() +} + +func (f *RegexpFile) Sync() error { + return f.f.Sync() +} + +func (f *RegexpFile) Truncate(s int64) error { + return f.f.Truncate(s) +} + +func (f *RegexpFile) WriteString(s string) (int, error) { + return f.f.WriteString(s) +} diff --git a/filter_test.go b/filter_test.go index 803fdb0..dcbc6f4 100644 --- a/filter_test.go +++ b/filter_test.go @@ -1,11 +1,11 @@ package afero import ( -// "regexp" + "regexp" "testing" ) -func TestReadOnly(t *testing.T) { +func TestFilterReadOnly(t *testing.T) { mfs := &MemMapFs{} fs := NewFilter(mfs) fs.AddFilter(NewReadonlyFilter()) @@ -16,7 +16,7 @@ func TestReadOnly(t *testing.T) { t.Logf("ERR=%s", err) } -func TestReadonlyRemoveAndRead(t *testing.T) { +func TestFilterReadonlyRemoveAndRead(t *testing.T) { mfs := &MemMapFs{} fh, err := mfs.Create("/file.txt") fh.Write([]byte("content here")) @@ -52,11 +52,11 @@ func TestReadonlyRemoveAndRead(t *testing.T) { t.Errorf("File still present") } } -/* -func TestRegexp(t *testing.T) { + +func TestFilterRegexp(t *testing.T) { mfs := &MemMapFs{} fs := NewFilter(mfs) - fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`), nil)) + fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`))) _, err := fs.Create("/file.html") if err == nil { t.Errorf("Did not fail to create file") @@ -64,15 +64,40 @@ func TestRegexp(t *testing.T) { t.Logf("ERR=%s", err) } -func TestRORegexpChain(t *testing.T) { +func TestFilterRORegexpChain(t *testing.T) { mfs := &MemMapFs{} fs := NewFilter(mfs) fs.AddFilter(NewReadonlyFilter()) - fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`), nil)) + fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`))) _, err := fs.Create("/file.txt") if err == nil { t.Errorf("Did not fail to create file") } t.Logf("ERR=%s", err) } -*/ + +func TestFilterRegexReadDir(t *testing.T) { + mfs := &MemMapFs{} + fs := NewFilter(mfs) + fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`))) + fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`^a`))) + + mfs.MkdirAll("/dir/sub", 0777) + for _, name := range []string{"afile.txt", "afile.html", "bfile.txt"} { + for _, dir := range []string{"/dir/", "/dir/sub/"} { + fh, _ := mfs.Create(dir + name) + fh.Close() + } + } + + files, _ := ReadDir(fs, "/dir") + if len(files) != 2 { // afile.txt, sub + t.Errorf("Got wrong number of files: %#v", files) + } + + f, _ := fs.Open("/dir/sub") + names, _ := f.Readdirnames(-1) + if len(names) != 1 { + t.Errorf("Got wrong number of names: %v", names) + } +}