diff --git a/pkg/webdav/file.go b/pkg/webdav/file.go index 9f53778..3e4a018 100644 --- a/pkg/webdav/file.go +++ b/pkg/webdav/file.go @@ -6,15 +6,12 @@ package webdav import ( "context" - "encoding/xml" "io" "net/http" "os" "path" "path/filepath" "strings" - "sync" - "time" ) // slashClean is equivalent to but slightly more efficient than @@ -54,18 +51,18 @@ type File interface { io.Writer } -// A Dir implements FileSystem using the native file system restricted to a +// A AdapterFS implements FileSystem using the native file system restricted to a // specific directory tree. // -// While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's +// While the FileSystem.OpenFile method takes '/'-separated paths, a AdapterFS's // string value is a filename on the native file system, not a URL, so it is // separated by filepath.Separator, which isn't necessarily '/'. // -// An empty Dir is treated as ".". -type Dir string +// An empty AdapterFS is treated as ".". +type AdapterFS string -func (d Dir) resolve(name string) string { - // This implementation is based on Dir.Open's code in the standard net/http package. +func (d AdapterFS) resolve(name string) string { + // This implementation is based on AdapterFS.Open's code in the standard net/http package. if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || strings.Contains(name, "\x00") { return "" @@ -77,14 +74,14 @@ func (d Dir) resolve(name string) string { return filepath.Join(dir, filepath.FromSlash(slashClean(name))) } -func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error { +func (d AdapterFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { if name = d.resolve(name); name == "" { return os.ErrNotExist } return os.Mkdir(name, perm) } -func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { +func (d AdapterFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { if name = d.resolve(name); name == "" { return nil, os.ErrNotExist } @@ -95,7 +92,7 @@ func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMo return f, nil } -func (d Dir) RemoveAll(ctx context.Context, name string) error { +func (d AdapterFS) RemoveAll(ctx context.Context, name string) error { if name = d.resolve(name); name == "" { return os.ErrNotExist } @@ -106,7 +103,7 @@ func (d Dir) RemoveAll(ctx context.Context, name string) error { return os.RemoveAll(name) } -func (d Dir) Rename(ctx context.Context, oldName, newName string) error { +func (d AdapterFS) Rename(ctx context.Context, oldName, newName string) error { if oldName = d.resolve(oldName); oldName == "" { return os.ErrNotExist } @@ -120,483 +117,13 @@ func (d Dir) Rename(ctx context.Context, oldName, newName string) error { return os.Rename(oldName, newName) } -func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) { +func (d AdapterFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { if name = d.resolve(name); name == "" { return nil, os.ErrNotExist } return os.Stat(name) } -// NewMemFS returns a new in-memory FileSystem implementation. -func NewMemFS() FileSystem { - return &memFS{ - root: memFSNode{ - children: make(map[string]*memFSNode), - mode: 0660 | os.ModeDir, - modTime: time.Now(), - }, - } -} - -// A memFS implements FileSystem, storing all metadata and actual file data -// in-memory. No limits on filesystem size are used, so it is not recommended -// this be used where the clients are untrusted. -// -// Concurrent access is permitted. The tree structure is protected by a mutex, -// and each node's contents and metadata are protected by a per-node mutex. -// -// TODO: Enforce file permissions. -type memFS struct { - mu sync.Mutex - root memFSNode -} - -// TODO: clean up and rationalize the walk/find code. - -// walk walks the directory tree for the fullname, calling f at each step. If f -// returns an error, the walk will be aborted and return that same error. -// -// dir is the directory at that step, frag is the name fragment, and final is -// whether it is the final step. For example, walking "/foo/bar/x" will result -// in 3 calls to f: -// - "/", "foo", false -// - "/foo/", "bar", false -// - "/foo/bar/", "x", true -// The frag argument will be empty only if dir is the root node and the walk -// ends at that root node. -func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error { - original := fullname - fullname = slashClean(fullname) - - // Strip any leading "/"s to make fullname a relative path, as the walk - // starts at fs.root. - if fullname[0] == '/' { - fullname = fullname[1:] - } - dir := &fs.root - - for { - frag, remaining := fullname, "" - i := strings.IndexRune(fullname, '/') - final := i < 0 - if !final { - frag, remaining = fullname[:i], fullname[i+1:] - } - if frag == "" && dir != &fs.root { - panic("webdav: empty path fragment for a clean path") - } - if err := f(dir, frag, final); err != nil { - return &os.PathError{ - Op: op, - Path: original, - Err: err, - } - } - if final { - break - } - child := dir.children[frag] - if child == nil { - return &os.PathError{ - Op: op, - Path: original, - Err: os.ErrNotExist, - } - } - if !child.mode.IsDir() { - return &os.PathError{ - Op: op, - Path: original, - Err: os.ErrInvalid, - } - } - dir, fullname = child, remaining - } - return nil -} - -// find returns the parent of the named node and the relative name fragment -// from the parent to the child. For example, if finding "/foo/bar/baz" then -// parent will be the node for "/foo/bar" and frag will be "baz". -// -// If the fullname names the root node, then parent, frag and err will be zero. -// -// find returns an error if the parent does not already exist or the parent -// isn't a directory, but it will not return an error per se if the child does -// not already exist. The error returned is either nil or an *os.PathError -// whose Op is op. -func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) { - err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error { - if !final { - return nil - } - if frag0 != "" { - parent, frag = parent0, frag0 - } - return nil - }) - return parent, frag, err -} - -func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { - fs.mu.Lock() - defer fs.mu.Unlock() - - dir, frag, err := fs.find("mkdir", name) - if err != nil { - return err - } - if dir == nil { - // We can't create the root. - return os.ErrInvalid - } - if _, ok := dir.children[frag]; ok { - return os.ErrExist - } - dir.children[frag] = &memFSNode{ - children: make(map[string]*memFSNode), - mode: perm.Perm() | os.ModeDir, - modTime: time.Now(), - } - return nil -} - -func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { - fs.mu.Lock() - defer fs.mu.Unlock() - - dir, frag, err := fs.find("open", name) - if err != nil { - return nil, err - } - var n *memFSNode - if dir == nil { - // We're opening the root. - if flag&(os.O_WRONLY|os.O_RDWR) != 0 { - return nil, os.ErrPermission - } - n, frag = &fs.root, "/" - - } else { - n = dir.children[frag] - if flag&(os.O_SYNC|os.O_APPEND) != 0 { - // memFile doesn't support these flags yet. - return nil, os.ErrInvalid - } - if flag&os.O_CREATE != 0 { - if flag&os.O_EXCL != 0 && n != nil { - return nil, os.ErrExist - } - if n == nil { - n = &memFSNode{ - mode: perm.Perm(), - } - dir.children[frag] = n - } - } - if n == nil { - return nil, os.ErrNotExist - } - if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 { - n.mu.Lock() - n.data = nil - n.mu.Unlock() - } - } - - children := make([]os.FileInfo, 0, len(n.children)) - for cName, c := range n.children { - children = append(children, c.stat(cName)) - } - return &memFile{ - n: n, - nameSnapshot: frag, - childrenSnapshot: children, - }, nil -} - -func (fs *memFS) RemoveAll(ctx context.Context, name string) error { - fs.mu.Lock() - defer fs.mu.Unlock() - - dir, frag, err := fs.find("remove", name) - if err != nil { - return err - } - if dir == nil { - // We can't remove the root. - return os.ErrInvalid - } - delete(dir.children, frag) - return nil -} - -func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error { - fs.mu.Lock() - defer fs.mu.Unlock() - - oldName = slashClean(oldName) - newName = slashClean(newName) - if oldName == newName { - return nil - } - if strings.HasPrefix(newName, oldName+"/") { - // We can't rename oldName to be a sub-directory of itself. - return os.ErrInvalid - } - - oDir, oFrag, err := fs.find("rename", oldName) - if err != nil { - return err - } - if oDir == nil { - // We can't rename from the root. - return os.ErrInvalid - } - - nDir, nFrag, err := fs.find("rename", newName) - if err != nil { - return err - } - if nDir == nil { - // We can't rename to the root. - return os.ErrInvalid - } - - oNode, ok := oDir.children[oFrag] - if !ok { - return os.ErrNotExist - } - if oNode.children != nil { - if nNode, ok := nDir.children[nFrag]; ok { - if nNode.children == nil { - return errNotADirectory - } - if len(nNode.children) != 0 { - return errDirectoryNotEmpty - } - } - } - delete(oDir.children, oFrag) - nDir.children[nFrag] = oNode - return nil -} - -func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { - fs.mu.Lock() - defer fs.mu.Unlock() - - dir, frag, err := fs.find("stat", name) - if err != nil { - return nil, err - } - if dir == nil { - // We're stat'ting the root. - return fs.root.stat("/"), nil - } - if n, ok := dir.children[frag]; ok { - return n.stat(path.Base(name)), nil - } - return nil, os.ErrNotExist -} - -// A memFSNode represents a single entry in the in-memory filesystem and also -// implements os.FileInfo. -type memFSNode struct { - // children is protected by memFS.mu. - children map[string]*memFSNode - - mu sync.Mutex - data []byte - mode os.FileMode - modTime time.Time - deadProps map[xml.Name]Property -} - -func (n *memFSNode) stat(name string) *memFileInfo { - n.mu.Lock() - defer n.mu.Unlock() - return &memFileInfo{ - name: name, - size: int64(len(n.data)), - mode: n.mode, - modTime: n.modTime, - } -} - -func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) { - n.mu.Lock() - defer n.mu.Unlock() - if len(n.deadProps) == 0 { - return nil, nil - } - ret := make(map[xml.Name]Property, len(n.deadProps)) - for k, v := range n.deadProps { - ret[k] = v - } - return ret, nil -} - -func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) { - n.mu.Lock() - defer n.mu.Unlock() - pstat := Propstat{Status: http.StatusOK} - for _, patch := range patches { - for _, p := range patch.Props { - pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName}) - if patch.Remove { - delete(n.deadProps, p.XMLName) - continue - } - if n.deadProps == nil { - n.deadProps = map[xml.Name]Property{} - } - n.deadProps[p.XMLName] = p - } - } - return []Propstat{pstat}, nil -} - -type memFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (f *memFileInfo) Name() string { return f.name } -func (f *memFileInfo) Size() int64 { return f.size } -func (f *memFileInfo) Mode() os.FileMode { return f.mode } -func (f *memFileInfo) ModTime() time.Time { return f.modTime } -func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() } -func (f *memFileInfo) Sys() interface{} { return nil } - -// A memFile is a File implementation for a memFSNode. It is a per-file (not -// per-node) read/write position, and a snapshot of the memFS' tree structure -// (a node's name and children) for that node. -type memFile struct { - n *memFSNode - nameSnapshot string - childrenSnapshot []os.FileInfo - // pos is protected by n.mu. - pos int -} - -// A *memFile implements the optional DeadPropsHolder interface. -var _ DeadPropsHolder = (*memFile)(nil) - -func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() } -func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) } - -func (f *memFile) Close() error { - return nil -} - -func (f *memFile) Read(p []byte) (int, error) { - f.n.mu.Lock() - defer f.n.mu.Unlock() - if f.n.mode.IsDir() { - return 0, os.ErrInvalid - } - if f.pos >= len(f.n.data) { - return 0, io.EOF - } - n := copy(p, f.n.data[f.pos:]) - f.pos += n - return n, nil -} - -func (f *memFile) Readdir(count int) ([]os.FileInfo, error) { - f.n.mu.Lock() - defer f.n.mu.Unlock() - if !f.n.mode.IsDir() { - return nil, os.ErrInvalid - } - old := f.pos - if old >= len(f.childrenSnapshot) { - // The os.File Readdir docs say that at the end of a directory, - // the error is io.EOF if count > 0 and nil if count <= 0. - if count > 0 { - return nil, io.EOF - } - return nil, nil - } - if count > 0 { - f.pos += count - if f.pos > len(f.childrenSnapshot) { - f.pos = len(f.childrenSnapshot) - } - } else { - f.pos = len(f.childrenSnapshot) - old = 0 - } - return f.childrenSnapshot[old:f.pos], nil -} - -func (f *memFile) Seek(offset int64, whence int) (int64, error) { - f.n.mu.Lock() - defer f.n.mu.Unlock() - npos := f.pos - // TODO: How to handle offsets greater than the size of system int? - switch whence { - case os.SEEK_SET: - npos = int(offset) - case os.SEEK_CUR: - npos += int(offset) - case os.SEEK_END: - npos = len(f.n.data) + int(offset) - default: - npos = -1 - } - if npos < 0 { - return 0, os.ErrInvalid - } - f.pos = npos - return int64(f.pos), nil -} - -func (f *memFile) Stat() (os.FileInfo, error) { - return f.n.stat(f.nameSnapshot), nil -} - -func (f *memFile) Write(p []byte) (int, error) { - lenp := len(p) - f.n.mu.Lock() - defer f.n.mu.Unlock() - - if f.n.mode.IsDir() { - return 0, os.ErrInvalid - } - if f.pos < len(f.n.data) { - n := copy(f.n.data[f.pos:], p) - f.pos += n - p = p[n:] - } else if f.pos > len(f.n.data) { - // Write permits the creation of holes, if we've seek'ed past the - // existing end of file. - if f.pos <= cap(f.n.data) { - oldLen := len(f.n.data) - f.n.data = f.n.data[:f.pos] - hole := f.n.data[oldLen:] - for i := range hole { - hole[i] = 0 - } - } else { - d := make([]byte, f.pos, f.pos+len(p)) - copy(d, f.n.data) - f.n.data = d - } - } - - if len(p) > 0 { - // We should only get here if f.pos == len(f.n.data). - f.n.data = append(f.n.data, p...) - f.pos = len(f.n.data) - } - f.n.modTime = time.Now() - return lenp, nil -} - // moveFiles moves files and/or directories from src to dst. // // See section 9.9.4 for when various HTTP status codes apply. diff --git a/pkg/webdav/file_test.go b/pkg/webdav/file_test.go index 04b8a45..f192167 100644 --- a/pkg/webdav/file_test.go +++ b/pkg/webdav/file_test.go @@ -145,7 +145,7 @@ func TestDirResolve(t *testing.T) { } for _, tc := range testCases { - d := Dir(filepath.FromSlash(tc.dir)) + d := AdapterFS(filepath.FromSlash(tc.dir)) if got := filepath.ToSlash(d.resolve(tc.name)); got != tc.want { t.Errorf("dir=%q, name=%q: got %q, want %q", tc.dir, tc.name, got, tc.want) } @@ -487,7 +487,7 @@ func testFS(t *testing.T, fs FileSystem) { } if fileName == "/" { - // For a Dir FileSystem, the virtual file system root maps to a + // For a AdapterFS FileSystem, the virtual file system root maps to a // real file system name like "/tmp/webdav-test012345", which does // not end with "/". We skip such cases. } else if statName := stat.Name(); path.Base(fileName) != statName { @@ -523,7 +523,7 @@ func TestDir(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(td) - testFS(t, Dir(td)) + testFS(t, AdapterFS(td)) } func TestMemFS(t *testing.T) { diff --git a/pkg/webdav/mem.go b/pkg/webdav/mem.go new file mode 100644 index 0000000..98eba73 --- /dev/null +++ b/pkg/webdav/mem.go @@ -0,0 +1,483 @@ +package webdav + +import ( + "context" + "encoding/xml" + "io" + "net/http" + "os" + "path" + "strings" + "sync" + "time" +) + +// NewMemFS returns a new in-memory FileSystem implementation. +func NewMemFS() FileSystem { + return &memFS{ + root: memFSNode{ + children: make(map[string]*memFSNode), + mode: 0660 | os.ModeDir, + modTime: time.Now(), + }, + } +} + +// A memFS implements FileSystem, storing all metadata and actual file data +// in-memory. No limits on filesystem size are used, so it is not recommended +// this be used where the clients are untrusted. +// +// Concurrent access is permitted. The tree structure is protected by a mutex, +// and each node's contents and metadata are protected by a per-node mutex. +// +// TODO: Enforce file permissions. +type memFS struct { + mu sync.Mutex + root memFSNode +} + +// TODO: clean up and rationalize the walk/find code. + +// walk walks the directory tree for the fullname, calling f at each step. If f +// returns an error, the walk will be aborted and return that same error. +// +// dir is the directory at that step, frag is the name fragment, and final is +// whether it is the final step. For example, walking "/foo/bar/x" will result +// in 3 calls to f: +// - "/", "foo", false +// - "/foo/", "bar", false +// - "/foo/bar/", "x", true +// The frag argument will be empty only if dir is the root node and the walk +// ends at that root node. +func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error { + original := fullname + fullname = slashClean(fullname) + + // Strip any leading "/"s to make fullname a relative path, as the walk + // starts at fs.root. + if fullname[0] == '/' { + fullname = fullname[1:] + } + dir := &fs.root + + for { + frag, remaining := fullname, "" + i := strings.IndexRune(fullname, '/') + final := i < 0 + if !final { + frag, remaining = fullname[:i], fullname[i+1:] + } + if frag == "" && dir != &fs.root { + panic("webdav: empty path fragment for a clean path") + } + if err := f(dir, frag, final); err != nil { + return &os.PathError{ + Op: op, + Path: original, + Err: err, + } + } + if final { + break + } + child := dir.children[frag] + if child == nil { + return &os.PathError{ + Op: op, + Path: original, + Err: os.ErrNotExist, + } + } + if !child.mode.IsDir() { + return &os.PathError{ + Op: op, + Path: original, + Err: os.ErrInvalid, + } + } + dir, fullname = child, remaining + } + return nil +} + +// find returns the parent of the named node and the relative name fragment +// from the parent to the child. For example, if finding "/foo/bar/baz" then +// parent will be the node for "/foo/bar" and frag will be "baz". +// +// If the fullname names the root node, then parent, frag and err will be zero. +// +// find returns an error if the parent does not already exist or the parent +// isn't a directory, but it will not return an error per se if the child does +// not already exist. The error returned is either nil or an *os.PathError +// whose Op is op. +func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) { + err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error { + if !final { + return nil + } + if frag0 != "" { + parent, frag = parent0, frag0 + } + return nil + }) + return parent, frag, err +} + +func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("mkdir", name) + if err != nil { + return err + } + if dir == nil { + // We can't create the root. + return os.ErrInvalid + } + if _, ok := dir.children[frag]; ok { + return os.ErrExist + } + dir.children[frag] = &memFSNode{ + children: make(map[string]*memFSNode), + mode: perm.Perm() | os.ModeDir, + modTime: time.Now(), + } + return nil +} + +func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("open", name) + if err != nil { + return nil, err + } + var n *memFSNode + if dir == nil { + // We're opening the root. + if flag&(os.O_WRONLY|os.O_RDWR) != 0 { + return nil, os.ErrPermission + } + n, frag = &fs.root, "/" + + } else { + n = dir.children[frag] + if flag&(os.O_SYNC|os.O_APPEND) != 0 { + // memFile doesn't support these flags yet. + return nil, os.ErrInvalid + } + if flag&os.O_CREATE != 0 { + if flag&os.O_EXCL != 0 && n != nil { + return nil, os.ErrExist + } + if n == nil { + n = &memFSNode{ + mode: perm.Perm(), + } + dir.children[frag] = n + } + } + if n == nil { + return nil, os.ErrNotExist + } + if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 { + n.mu.Lock() + n.data = nil + n.mu.Unlock() + } + } + + children := make([]os.FileInfo, 0, len(n.children)) + for cName, c := range n.children { + children = append(children, c.stat(cName)) + } + return &memFile{ + n: n, + nameSnapshot: frag, + childrenSnapshot: children, + }, nil +} + +func (fs *memFS) RemoveAll(ctx context.Context, name string) error { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("remove", name) + if err != nil { + return err + } + if dir == nil { + // We can't remove the root. + return os.ErrInvalid + } + delete(dir.children, frag) + return nil +} + +func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error { + fs.mu.Lock() + defer fs.mu.Unlock() + + oldName = slashClean(oldName) + newName = slashClean(newName) + if oldName == newName { + return nil + } + if strings.HasPrefix(newName, oldName+"/") { + // We can't rename oldName to be a sub-directory of itself. + return os.ErrInvalid + } + + oDir, oFrag, err := fs.find("rename", oldName) + if err != nil { + return err + } + if oDir == nil { + // We can't rename from the root. + return os.ErrInvalid + } + + nDir, nFrag, err := fs.find("rename", newName) + if err != nil { + return err + } + if nDir == nil { + // We can't rename to the root. + return os.ErrInvalid + } + + oNode, ok := oDir.children[oFrag] + if !ok { + return os.ErrNotExist + } + if oNode.children != nil { + if nNode, ok := nDir.children[nFrag]; ok { + if nNode.children == nil { + return errNotADirectory + } + if len(nNode.children) != 0 { + return errDirectoryNotEmpty + } + } + } + delete(oDir.children, oFrag) + nDir.children[nFrag] = oNode + return nil +} + +func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("stat", name) + if err != nil { + return nil, err + } + if dir == nil { + // We're stat'ting the root. + return fs.root.stat("/"), nil + } + if n, ok := dir.children[frag]; ok { + return n.stat(path.Base(name)), nil + } + return nil, os.ErrNotExist +} + +// A memFSNode represents a single entry in the in-memory filesystem and also +// implements os.FileInfo. +type memFSNode struct { + // children is protected by memFS.mu. + children map[string]*memFSNode + + mu sync.Mutex + data []byte + mode os.FileMode + modTime time.Time + deadProps map[xml.Name]Property +} + +func (n *memFSNode) stat(name string) *memFileInfo { + n.mu.Lock() + defer n.mu.Unlock() + return &memFileInfo{ + name: name, + size: int64(len(n.data)), + mode: n.mode, + modTime: n.modTime, + } +} + +func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) { + n.mu.Lock() + defer n.mu.Unlock() + if len(n.deadProps) == 0 { + return nil, nil + } + ret := make(map[xml.Name]Property, len(n.deadProps)) + for k, v := range n.deadProps { + ret[k] = v + } + return ret, nil +} + +func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) { + n.mu.Lock() + defer n.mu.Unlock() + pstat := Propstat{Status: http.StatusOK} + for _, patch := range patches { + for _, p := range patch.Props { + pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName}) + if patch.Remove { + delete(n.deadProps, p.XMLName) + continue + } + if n.deadProps == nil { + n.deadProps = map[xml.Name]Property{} + } + n.deadProps[p.XMLName] = p + } + } + return []Propstat{pstat}, nil +} + +type memFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (f *memFileInfo) Name() string { return f.name } +func (f *memFileInfo) Size() int64 { return f.size } +func (f *memFileInfo) Mode() os.FileMode { return f.mode } +func (f *memFileInfo) ModTime() time.Time { return f.modTime } +func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() } +func (f *memFileInfo) Sys() interface{} { return nil } + +// A memFile is a File implementation for a memFSNode. It is a per-file (not +// per-node) read/write position, and a snapshot of the memFS' tree structure +// (a node's name and children) for that node. +type memFile struct { + n *memFSNode + nameSnapshot string + childrenSnapshot []os.FileInfo + // pos is protected by n.mu. + pos int +} + +// A *memFile implements the optional DeadPropsHolder interface. +var _ DeadPropsHolder = (*memFile)(nil) + +func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() } +func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) } + +func (f *memFile) Close() error { + return nil +} + +func (f *memFile) Read(p []byte) (int, error) { + f.n.mu.Lock() + defer f.n.mu.Unlock() + if f.n.mode.IsDir() { + return 0, os.ErrInvalid + } + if f.pos >= len(f.n.data) { + return 0, io.EOF + } + n := copy(p, f.n.data[f.pos:]) + f.pos += n + return n, nil +} + +func (f *memFile) Readdir(count int) ([]os.FileInfo, error) { + f.n.mu.Lock() + defer f.n.mu.Unlock() + if !f.n.mode.IsDir() { + return nil, os.ErrInvalid + } + old := f.pos + if old >= len(f.childrenSnapshot) { + // The os.File Readdir docs say that at the end of a directory, + // the error is io.EOF if count > 0 and nil if count <= 0. + if count > 0 { + return nil, io.EOF + } + return nil, nil + } + if count > 0 { + f.pos += count + if f.pos > len(f.childrenSnapshot) { + f.pos = len(f.childrenSnapshot) + } + } else { + f.pos = len(f.childrenSnapshot) + old = 0 + } + return f.childrenSnapshot[old:f.pos], nil +} + +func (f *memFile) Seek(offset int64, whence int) (int64, error) { + f.n.mu.Lock() + defer f.n.mu.Unlock() + npos := f.pos + // TODO: How to handle offsets greater than the size of system int? + switch whence { + case os.SEEK_SET: + npos = int(offset) + case os.SEEK_CUR: + npos += int(offset) + case os.SEEK_END: + npos = len(f.n.data) + int(offset) + default: + npos = -1 + } + if npos < 0 { + return 0, os.ErrInvalid + } + f.pos = npos + return int64(f.pos), nil +} + +func (f *memFile) Stat() (os.FileInfo, error) { + return f.n.stat(f.nameSnapshot), nil +} + +func (f *memFile) Write(p []byte) (int, error) { + lenp := len(p) + f.n.mu.Lock() + defer f.n.mu.Unlock() + + if f.n.mode.IsDir() { + return 0, os.ErrInvalid + } + if f.pos < len(f.n.data) { + n := copy(f.n.data[f.pos:], p) + f.pos += n + p = p[n:] + } else if f.pos > len(f.n.data) { + // Write permits the creation of holes, if we've seek'ed past the + // existing end of file. + if f.pos <= cap(f.n.data) { + oldLen := len(f.n.data) + f.n.data = f.n.data[:f.pos] + hole := f.n.data[oldLen:] + for i := range hole { + hole[i] = 0 + } + } else { + d := make([]byte, f.pos, f.pos+len(p)) + copy(d, f.n.data) + f.n.data = d + } + } + + if len(p) > 0 { + // We should only get here if f.pos == len(f.n.data). + f.n.data = append(f.n.data, p...) + f.pos = len(f.n.data) + } + f.n.modTime = time.Now() + return lenp, nil +} diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go index d88995f..92eb055 100644 --- a/pkg/webdav/webdav.go +++ b/pkg/webdav/webdav.go @@ -8,11 +8,13 @@ package webdav // import "golang.org/x/net/webdav" import ( "errors" "fmt" + "github.com/HFO4/cloudreve/pkg/filesystem" "io" "net/http" "net/url" "os" "path" + "strconv" "strings" "time" ) @@ -29,17 +31,18 @@ type Handler struct { Logger func(*http.Request, error) } -func (h *Handler) stripPrefix(p string) (string, int, error) { +func (h *Handler) stripPrefix(p string, uid uint) (string, int, error) { if h.Prefix == "" { return p, http.StatusOK, nil } - if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) { + prefix := h.Prefix + strconv.FormatUint(uint64(uid),10) + if r := strings.TrimPrefix(p, prefix); len(r) < len(p) { return r, http.StatusOK, nil } return p, http.StatusNotFound, errPrefixMismatch } -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) { status, err := http.StatusBadRequest, errUnsupportedMethod if h.FileSystem == nil { status, err = http.StatusInternalServerError, errNoFileSystem @@ -48,25 +51,25 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else { switch r.Method { case "OPTIONS": - status, err = h.handleOptions(w, r) + status, err = h.handleOptions(w, r, fs) case "GET", "HEAD", "POST": - status, err = h.handleGetHeadPost(w, r) + status, err = h.handleGetHeadPost(w, r,fs) case "DELETE": - status, err = h.handleDelete(w, r) + status, err = h.handleDelete(w, r,fs) case "PUT": - status, err = h.handlePut(w, r) + status, err = h.handlePut(w, r,fs) case "MKCOL": - status, err = h.handleMkcol(w, r) + status, err = h.handleMkcol(w, r,fs) case "COPY", "MOVE": - status, err = h.handleCopyMove(w, r) + status, err = h.handleCopyMove(w, r,fs) case "LOCK": - status, err = h.handleLock(w, r) + status, err = h.handleLock(w, r,fs) case "UNLOCK": - status, err = h.handleUnlock(w, r) + status, err = h.handleUnlock(w, r,fs) case "PROPFIND": - status, err = h.handlePropfind(w, r) + status, err = h.handlePropfind(w, r,fs) case "PROPPATCH": - status, err = h.handleProppatch(w, r) + status, err = h.handleProppatch(w, r,fs) } } @@ -96,7 +99,7 @@ func (h *Handler) lock(now time.Time, root string) (token string, status int, er return token, 0, nil } -func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) { +func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.FileSystem) (release func(), status int, err error) { hdr := r.Header.Get("If") if hdr == "" { // An empty If header means that the client hasn't previously created locks. @@ -148,7 +151,7 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func() if u.Host != r.Host { continue } - lsrc, status, err = h.stripPrefix(u.Path) + lsrc, status, err = h.stripPrefix(u.Path,fs.User.ID) if err != nil { return nil, status, err } @@ -169,8 +172,8 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func() return nil, http.StatusPreconditionFailed, ErrLocked } -func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } @@ -191,8 +194,8 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status return 0, nil } -func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } @@ -220,12 +223,12 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta return 0, nil } -func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } - release, status, err := h.confirmLocks(r, reqPath, "") + release, status, err := h.confirmLocks(r, reqPath, "",fs) if err != nil { return status, err } @@ -250,12 +253,12 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i return http.StatusNoContent, nil } -func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } - release, status, err := h.confirmLocks(r, reqPath, "") + release, status, err := h.confirmLocks(r, reqPath, "",fs) if err != nil { return status, err } @@ -289,12 +292,12 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, return http.StatusCreated, nil } -func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID) if err != nil { return status, err } - release, status, err := h.confirmLocks(r, reqPath, "") + release, status, err := h.confirmLocks(r, reqPath, "", fs) if err != nil { return status, err } @@ -314,7 +317,7 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in return http.StatusCreated, nil } -func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) { +func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { hdr := r.Header.Get("Destination") if hdr == "" { return http.StatusBadRequest, errInvalidDestination @@ -327,12 +330,12 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status return http.StatusBadGateway, errInvalidDestination } - src, status, err := h.stripPrefix(r.URL.Path) + src, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } - dst, status, err := h.stripPrefix(u.Path) + dst, status, err := h.stripPrefix(u.Path, fs.User.ID) if err != nil { return status, err } @@ -352,7 +355,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status // even though a COPY doesn't modify the source, if a concurrent // operation modifies the source. However, the litmus test explicitly // checks that COPYing a locked-by-another source is OK. - release, status, err := h.confirmLocks(r, "", dst) + release, status, err := h.confirmLocks(r, "", dst, fs) if err != nil { return status, err } @@ -372,7 +375,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0) } - release, status, err := h.confirmLocks(r, src, dst) + release, status, err := h.confirmLocks(r, src, dst,fs) if err != nil { return status, err } @@ -389,7 +392,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T") } -func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) { +func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (retStatus int, retErr error) { duration, err := parseTimeout(r.Header.Get("Timeout")) if err != nil { return http.StatusBadRequest, err @@ -433,7 +436,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus return http.StatusBadRequest, errInvalidDepth } } - reqPath, status, err := h.stripPrefix(r.URL.Path) + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } @@ -483,7 +486,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus return 0, nil } -func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) { +func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the // Lock-Token value is a Coded-URL. We strip its angle brackets. t := r.Header.Get("Lock-Token") @@ -506,8 +509,8 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status i } } -func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } @@ -574,12 +577,12 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status return 0, nil } -func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) { - reqPath, status, err := h.stripPrefix(r.URL.Path) +func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { + reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID) if err != nil { return status, err } - release, status, err := h.confirmLocks(r, reqPath, "") + release, status, err := h.confirmLocks(r, reqPath, "",fs) if err != nil { return status, err } diff --git a/routers/controllers/webdav.go b/routers/controllers/webdav.go new file mode 100644 index 0000000..e3cf0a2 --- /dev/null +++ b/routers/controllers/webdav.go @@ -0,0 +1,31 @@ +package controllers + +import ( + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/util" + "github.com/HFO4/cloudreve/pkg/webdav" + "github.com/gin-gonic/gin" +) + +var handler *webdav.Handler + +func init(){ + handler = &webdav.Handler{ + Prefix: "/dav/", + FileSystem: webdav.AdapterFS(""), + LockSystem: webdav.NewMemLS(), + } +} + +func ServeWebDAV(c *gin.Context){ + // 测试用user + user,_ := model.GetUserByID(1) + c.Set("user",&user) + fs,err := filesystem.NewFileSystemFromContext(c) + if err != nil{ + util.Log().Panic("%s",err) + } + + handler.ServeHTTP(c.Writer,c.Request,fs) +} diff --git a/routers/router.go b/routers/router.go index 7eea3c3..7e769b7 100644 --- a/routers/router.go +++ b/routers/router.go @@ -5,23 +5,29 @@ import ( "github.com/HFO4/cloudreve/pkg/conf" "github.com/HFO4/cloudreve/routers/controllers" "github.com/gin-contrib/cors" - "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" ) +// initWebDAV 初始化WebDAV相关路由 +func initWebDAV(group *gin.RouterGroup){ + dav := group.Group(":uid") + { + dav.Any("*path", controllers.ServeWebDAV) + } +} + // InitRouter 初始化路由 func InitRouter() *gin.Engine { r := gin.Default() - pprof.Register(r) - + v3 := r.Group("/api/v3") /* 中间件 */ - r.Use(middleware.Session(conf.SystemConfig.SessionSecret)) + v3.Use(middleware.Session(conf.SystemConfig.SessionSecret)) // CORS TODO: 根据配置文件来 if conf.CORSConfig.AllowOrigins[0] != "UNSET" || conf.CORSConfig.AllowAllOrigins { - r.Use(cors.New(cors.Config{ + v3.Use(cors.New(cors.Config{ AllowOrigins: conf.CORSConfig.AllowOrigins, AllowAllOrigins: conf.CORSConfig.AllowAllOrigins, AllowMethods: conf.CORSConfig.AllowHeaders, @@ -33,15 +39,14 @@ func InitRouter() *gin.Engine { // 测试模式加入Mock助手中间件 if gin.Mode() == gin.TestMode { - r.Use(middleware.MockHelper()) + v3.Use(middleware.MockHelper()) } - r.Use(middleware.CurrentUser()) + v3.Use(middleware.CurrentUser()) /* 路由 */ - v3 := r.Group("/api/v3") { // 全局设置相关 site := v3.Group("site") @@ -145,5 +150,8 @@ func InitRouter() *gin.Engine { } } + + // 初始化WebDAV相关路由 + initWebDAV(v3.Group("dav")) return r }