Modify: add fs into webdav

pull/247/head
HFO4 5 years ago
parent 327a3c1edf
commit 963100d60f

@ -6,15 +6,12 @@ package webdav
import ( import (
"context" "context"
"encoding/xml"
"io" "io"
"net/http" "net/http"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"time"
) )
// slashClean is equivalent to but slightly more efficient than // slashClean is equivalent to but slightly more efficient than
@ -54,18 +51,18 @@ type File interface {
io.Writer 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. // 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 // string value is a filename on the native file system, not a URL, so it is
// separated by filepath.Separator, which isn't necessarily '/'. // separated by filepath.Separator, which isn't necessarily '/'.
// //
// An empty Dir is treated as ".". // An empty AdapterFS is treated as ".".
type Dir string type AdapterFS string
func (d Dir) resolve(name string) string { func (d AdapterFS) resolve(name string) string {
// This implementation is based on Dir.Open's code in the standard net/http package. // This implementation is based on AdapterFS.Open's code in the standard net/http package.
if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
strings.Contains(name, "\x00") { strings.Contains(name, "\x00") {
return "" return ""
@ -77,14 +74,14 @@ func (d Dir) resolve(name string) string {
return filepath.Join(dir, filepath.FromSlash(slashClean(name))) 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 == "" { if name = d.resolve(name); name == "" {
return os.ErrNotExist return os.ErrNotExist
} }
return os.Mkdir(name, perm) 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 == "" { if name = d.resolve(name); name == "" {
return nil, os.ErrNotExist 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 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 == "" { if name = d.resolve(name); name == "" {
return os.ErrNotExist return os.ErrNotExist
} }
@ -106,7 +103,7 @@ func (d Dir) RemoveAll(ctx context.Context, name string) error {
return os.RemoveAll(name) 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 == "" { if oldName = d.resolve(oldName); oldName == "" {
return os.ErrNotExist return os.ErrNotExist
} }
@ -120,483 +117,13 @@ func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
return os.Rename(oldName, newName) 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 == "" { if name = d.resolve(name); name == "" {
return nil, os.ErrNotExist return nil, os.ErrNotExist
} }
return os.Stat(name) 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. // moveFiles moves files and/or directories from src to dst.
// //
// See section 9.9.4 for when various HTTP status codes apply. // See section 9.9.4 for when various HTTP status codes apply.

@ -145,7 +145,7 @@ func TestDirResolve(t *testing.T) {
} }
for _, tc := range testCases { 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 { 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) 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 == "/" { 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 // real file system name like "/tmp/webdav-test012345", which does
// not end with "/". We skip such cases. // not end with "/". We skip such cases.
} else if statName := stat.Name(); path.Base(fileName) != statName { } else if statName := stat.Name(); path.Base(fileName) != statName {
@ -523,7 +523,7 @@ func TestDir(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(td) defer os.RemoveAll(td)
testFS(t, Dir(td)) testFS(t, AdapterFS(td))
} }
func TestMemFS(t *testing.T) { func TestMemFS(t *testing.T) {

@ -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
}

@ -8,11 +8,13 @@ package webdav // import "golang.org/x/net/webdav"
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/HFO4/cloudreve/pkg/filesystem"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -29,17 +31,18 @@ type Handler struct {
Logger func(*http.Request, error) 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 == "" { if h.Prefix == "" {
return p, http.StatusOK, nil 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 r, http.StatusOK, nil
} }
return p, http.StatusNotFound, errPrefixMismatch 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 status, err := http.StatusBadRequest, errUnsupportedMethod
if h.FileSystem == nil { if h.FileSystem == nil {
status, err = http.StatusInternalServerError, errNoFileSystem status, err = http.StatusInternalServerError, errNoFileSystem
@ -48,25 +51,25 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else { } else {
switch r.Method { switch r.Method {
case "OPTIONS": case "OPTIONS":
status, err = h.handleOptions(w, r) status, err = h.handleOptions(w, r, fs)
case "GET", "HEAD", "POST": case "GET", "HEAD", "POST":
status, err = h.handleGetHeadPost(w, r) status, err = h.handleGetHeadPost(w, r,fs)
case "DELETE": case "DELETE":
status, err = h.handleDelete(w, r) status, err = h.handleDelete(w, r,fs)
case "PUT": case "PUT":
status, err = h.handlePut(w, r) status, err = h.handlePut(w, r,fs)
case "MKCOL": case "MKCOL":
status, err = h.handleMkcol(w, r) status, err = h.handleMkcol(w, r,fs)
case "COPY", "MOVE": case "COPY", "MOVE":
status, err = h.handleCopyMove(w, r) status, err = h.handleCopyMove(w, r,fs)
case "LOCK": case "LOCK":
status, err = h.handleLock(w, r) status, err = h.handleLock(w, r,fs)
case "UNLOCK": case "UNLOCK":
status, err = h.handleUnlock(w, r) status, err = h.handleUnlock(w, r,fs)
case "PROPFIND": case "PROPFIND":
status, err = h.handlePropfind(w, r) status, err = h.handlePropfind(w, r,fs)
case "PROPPATCH": 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 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") hdr := r.Header.Get("If")
if hdr == "" { if hdr == "" {
// An empty If header means that the client hasn't previously created locks. // 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 { if u.Host != r.Host {
continue continue
} }
lsrc, status, err = h.stripPrefix(u.Path) lsrc, status, err = h.stripPrefix(u.Path,fs.User.ID)
if err != nil { if err != nil {
return nil, status, err 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 return nil, http.StatusPreconditionFailed, ErrLocked
} }
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
@ -191,8 +194,8 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
return 0, nil return 0, nil
} }
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
@ -220,12 +223,12 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
return 0, nil return 0, nil
} }
func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
release, status, err := h.confirmLocks(r, reqPath, "") release, status, err := h.confirmLocks(r, reqPath, "",fs)
if err != nil { if err != nil {
return status, err return status, err
} }
@ -250,12 +253,12 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
return http.StatusNoContent, nil return http.StatusNoContent, nil
} }
func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
release, status, err := h.confirmLocks(r, reqPath, "") release, status, err := h.confirmLocks(r, reqPath, "",fs)
if err != nil { if err != nil {
return status, err return status, err
} }
@ -289,12 +292,12 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
return http.StatusCreated, nil return http.StatusCreated, nil
} }
func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
release, status, err := h.confirmLocks(r, reqPath, "") release, status, err := h.confirmLocks(r, reqPath, "", fs)
if err != nil { if err != nil {
return status, err return status, err
} }
@ -314,7 +317,7 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
return http.StatusCreated, nil 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") hdr := r.Header.Get("Destination")
if hdr == "" { if hdr == "" {
return http.StatusBadRequest, errInvalidDestination return http.StatusBadRequest, errInvalidDestination
@ -327,12 +330,12 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
return http.StatusBadGateway, errInvalidDestination 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 { if err != nil {
return status, err return status, err
} }
dst, status, err := h.stripPrefix(u.Path) dst, status, err := h.stripPrefix(u.Path, fs.User.ID)
if err != nil { if err != nil {
return status, err 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 // even though a COPY doesn't modify the source, if a concurrent
// operation modifies the source. However, the litmus test explicitly // operation modifies the source. However, the litmus test explicitly
// checks that COPYing a locked-by-another source is OK. // 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 { if err != nil {
return status, err 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) 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 { if err != nil {
return status, err 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") 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")) duration, err := parseTimeout(r.Header.Get("Timeout"))
if err != nil { if err != nil {
return http.StatusBadRequest, err return http.StatusBadRequest, err
@ -433,7 +436,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
return http.StatusBadRequest, errInvalidDepth 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 { if err != nil {
return status, err return status, err
} }
@ -483,7 +486,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
return 0, nil 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 // 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. // Lock-Token value is a Coded-URL. We strip its angle brackets.
t := r.Header.Get("Lock-Token") 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) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
@ -574,12 +577,12 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
return 0, nil return 0, nil
} }
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) { 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) reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
release, status, err := h.confirmLocks(r, reqPath, "") release, status, err := h.confirmLocks(r, reqPath, "",fs)
if err != nil { if err != nil {
return status, err return status, err
} }

@ -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)
}

@ -5,23 +5,29 @@ import (
"github.com/HFO4/cloudreve/pkg/conf" "github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/routers/controllers" "github.com/HFO4/cloudreve/routers/controllers"
"github.com/gin-contrib/cors" "github.com/gin-contrib/cors"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// initWebDAV 初始化WebDAV相关路由
func initWebDAV(group *gin.RouterGroup){
dav := group.Group(":uid")
{
dav.Any("*path", controllers.ServeWebDAV)
}
}
// InitRouter 初始化路由 // InitRouter 初始化路由
func InitRouter() *gin.Engine { func InitRouter() *gin.Engine {
r := gin.Default() 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: 根据配置文件来 // CORS TODO: 根据配置文件来
if conf.CORSConfig.AllowOrigins[0] != "UNSET" || conf.CORSConfig.AllowAllOrigins { 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, AllowOrigins: conf.CORSConfig.AllowOrigins,
AllowAllOrigins: conf.CORSConfig.AllowAllOrigins, AllowAllOrigins: conf.CORSConfig.AllowAllOrigins,
AllowMethods: conf.CORSConfig.AllowHeaders, AllowMethods: conf.CORSConfig.AllowHeaders,
@ -33,15 +39,14 @@ func InitRouter() *gin.Engine {
// 测试模式加入Mock助手中间件 // 测试模式加入Mock助手中间件
if gin.Mode() == gin.TestMode { 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") site := v3.Group("site")
@ -145,5 +150,8 @@ func InitRouter() *gin.Engine {
} }
} }
// 初始化WebDAV相关路由
initWebDAV(v3.Group("dav"))
return r return r
} }

Loading…
Cancel
Save