add Fs filter

This commit is contained in:
Hanno Hecker 2015-12-13 14:56:00 +01:00
parent 9d44c3003b
commit 3b1997ba29
4 changed files with 399 additions and 0 deletions

153
filter.go Normal file
View File

@ -0,0 +1,153 @@
package afero
import (
"os"
"time"
)
// An afero Fs with an extra filter
//
// The FilterFs is run before the source Fs, any non nil error is returned
// to the caller without going to the source Fs. If every filter in the
// chain returns a nil error, the call is sent to the source Fs.
//
// see the TestReadonlyRemoveAndRead() in filter_test.go for an example use
// of filtering (e.g. admins get write access, normal users just readonly)
type FilterFs interface {
Fs
AddFilter(Fs)
}
type Filter struct {
chain []Fs
source Fs
}
// create a new FilterFs that implements Fs, argument must be an Fs, not
// a FilterFs
func NewFilter(fs Fs) FilterFs {
return &Filter{source: fs}
}
// 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) 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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
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
}
}
return f.source.Stat(name)
}
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
}
}
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
}
}
return f.source.Chtimes(name, atime, mtime)
}

65
filter_readonly.go Normal file
View File

@ -0,0 +1,65 @@
package afero
import (
"os"
"syscall"
"time"
)
type ReadOnlyFilter struct {
}
func NewReadonlyFilter() Fs {
return &ReadOnlyFilter{}
}
func (r *ReadOnlyFilter) Chtimes(n string, a, m time.Time) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) Chmod(n string, m os.FileMode) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) Name() string {
return "readOnlyFilter"
}
func (r *ReadOnlyFilter) Stat(name string) (os.FileInfo, error) {
return nil, nil
}
func (r *ReadOnlyFilter) Rename(o, n string) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) RemoveAll(p string) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) Remove(n string) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
if flag&os.O_RDONLY != 0 {
return nil, nil
}
return nil, syscall.EPERM
}
func (r *ReadOnlyFilter) Open(n string) (File, error) {
return nil, nil
}
func (r *ReadOnlyFilter) Mkdir(n string, p os.FileMode) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) MkdirAll(n string, p os.FileMode) error {
return syscall.EPERM
}
func (r *ReadOnlyFilter) Create(n string) (File, error) {
return nil, syscall.EPERM
}

104
filter_regexp.go Normal file
View File

@ -0,0 +1,104 @@
package afero
import (
"os"
"regexp"
"syscall"
"time"
)
type RegexpFilter struct {
file *regexp.Regexp
dir *regexp.Regexp
}
func NewRegexpFilter(file *regexp.Regexp, dir *regexp.Regexp) Fs {
return &RegexpFilter{file: file, dir: dir}
}
func (r *RegexpFilter) Chtimes(n string, a, m time.Time) error {
if !r.file.MatchString(n) {
return syscall.ENOENT
}
return nil
}
func (r *RegexpFilter) Chmod(n string, m os.FileMode) error {
if !r.file.MatchString(n) {
return syscall.ENOENT
}
return nil
}
func (r *RegexpFilter) Name() string {
return "RegexpFilter"
}
func (r *RegexpFilter) Stat(n string) (os.FileInfo, error) {
// FIXME - what about Stat() on dirs?
if !r.file.MatchString(n) {
return nil, syscall.ENOENT
}
return nil, nil
}
func (r *RegexpFilter) Rename(o, n string) error {
// FIXME - what about renaming dirs?
switch {
case !r.file.MatchString(o):
return syscall.ENOENT
case !r.file.MatchString(n):
return syscall.EPERM
default:
return nil
}
}
func (r *RegexpFilter) RemoveAll(p string) error {
if !r.dir.MatchString(p) {
return syscall.EPERM // FIXME ENOENT?
}
return nil
}
func (r *RegexpFilter) Remove(n string) error {
if !r.file.MatchString(n) {
return syscall.ENOENT
}
return nil
}
func (r *RegexpFilter) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
if !r.file.MatchString(name) {
return nil, syscall.ENOENT
}
return nil, nil
}
func (r *RegexpFilter) Open(n string) (File, error) {
if !r.file.MatchString(n) {
return nil, syscall.ENOENT
}
return nil, nil
}
func (r *RegexpFilter) Mkdir(n string, p os.FileMode) error {
if !r.dir.MatchString(n) {
return syscall.EPERM
}
return nil
}
func (r *RegexpFilter) MkdirAll(n string, p os.FileMode) error {
if !r.dir.MatchString(n) {
return syscall.EPERM
}
return nil
}
func (r *RegexpFilter) Create(n string) (File, error) {
if !r.file.MatchString(n) {
return nil, syscall.EPERM
}
return nil, nil
}

77
filter_test.go Normal file
View File

@ -0,0 +1,77 @@
package afero
import (
"regexp"
"testing"
)
func TestReadOnly(t *testing.T) {
mfs := &MemMapFs{}
fs := NewFilter(mfs)
fs.AddFilter(NewReadonlyFilter())
_, err := fs.Create("/file.txt")
if err == nil {
t.Errorf("Did not fail to create file")
}
t.Logf("ERR=%s", err)
}
func TestReadonlyRemoveAndRead(t *testing.T) {
mfs := &MemMapFs{}
fh, err := mfs.Create("/file.txt")
fh.Write([]byte("content here"))
fh.Close()
fs := NewFilter(mfs)
fs.AddFilter(NewReadonlyFilter())
err = fs.Remove("/file.txt")
if err == nil {
t.Errorf("Did not fail to remove file")
}
fh, err = fs.Open("/file.txt")
if err != nil {
t.Errorf("Failed to open file: %s", err)
}
buf := make([]byte, len("content here"))
_, err = fh.Read(buf)
fh.Close()
if string(buf) != "content here" {
t.Errorf("Failed to read file: %s", err)
}
err = mfs.Remove("/file.txt")
if err != nil {
t.Errorf("Failed to remove file")
}
fh, err = fs.Open("/file.txt")
if err == nil {
fh.Close()
t.Errorf("File still present")
}
}
func TestRegexp(t *testing.T) {
mfs := &MemMapFs{}
fs := NewFilter(mfs)
fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`), nil))
_, err := fs.Create("/file.html")
if err == nil {
t.Errorf("Did not fail to create file")
}
t.Logf("ERR=%s", err)
}
func TestRORegexpChain(t *testing.T) {
mfs := &MemMapFs{}
fs := NewFilter(mfs)
fs.AddFilter(NewReadonlyFilter())
fs.AddFilter(NewRegexpFilter(regexp.MustCompile(`\.txt$`), nil))
_, err := fs.Create("/file.txt")
if err == nil {
t.Errorf("Did not fail to create file")
}
t.Logf("ERR=%s", err)
}