1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
18 "golang.org/x/net/context"
21 // slashClean is equivalent to but slightly more efficient than
22 // path.Clean("/" + name).
23 func slashClean(name string) string {
24 if name == "" || name[0] != '/' {
27 return path.Clean(name)
30 // A FileSystem implements access to a collection of named files. The elements
31 // in a file path are separated by slash ('/', U+002F) characters, regardless
32 // of host operating system convention.
34 // Each method has the same semantics as the os package's function of the same
37 // Note that the os.Rename documentation says that "OS-specific restrictions
38 // might apply". In particular, whether or not renaming a file or directory
39 // overwriting another existing file or directory is an error is OS-dependent.
40 type FileSystem interface {
41 Mkdir(ctx context.Context, name string, perm os.FileMode) error
42 OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error)
43 RemoveAll(ctx context.Context, name string) error
44 Rename(ctx context.Context, oldName, newName string) error
45 Stat(ctx context.Context, name string) (os.FileInfo, error)
48 // A File is returned by a FileSystem's OpenFile method and can be served by a
51 // A File may optionally implement the DeadPropsHolder interface, if it can
52 // load and save dead properties.
58 // A Dir implements FileSystem using the native file system restricted to a
59 // specific directory tree.
61 // While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's
62 // string value is a filename on the native file system, not a URL, so it is
63 // separated by filepath.Separator, which isn't necessarily '/'.
65 // An empty Dir is treated as ".".
68 func (d Dir) resolve(name string) string {
69 // This implementation is based on Dir.Open's code in the standard net/http package.
70 if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
71 strings.Contains(name, "\x00") {
78 return filepath.Join(dir, filepath.FromSlash(slashClean(name)))
81 func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
82 if name = d.resolve(name); name == "" {
85 return os.Mkdir(name, perm)
88 func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
89 if name = d.resolve(name); name == "" {
90 return nil, os.ErrNotExist
92 f, err := os.OpenFile(name, flag, perm)
99 func (d Dir) RemoveAll(ctx context.Context, name string) error {
100 if name = d.resolve(name); name == "" {
101 return os.ErrNotExist
103 if name == filepath.Clean(string(d)) {
104 // Prohibit removing the virtual root directory.
107 return os.RemoveAll(name)
110 func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
111 if oldName = d.resolve(oldName); oldName == "" {
112 return os.ErrNotExist
114 if newName = d.resolve(newName); newName == "" {
115 return os.ErrNotExist
117 if root := filepath.Clean(string(d)); root == oldName || root == newName {
118 // Prohibit renaming from or to the virtual root directory.
121 return os.Rename(oldName, newName)
124 func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
125 if name = d.resolve(name); name == "" {
126 return nil, os.ErrNotExist
131 // NewMemFS returns a new in-memory FileSystem implementation.
132 func NewMemFS() FileSystem {
135 children: make(map[string]*memFSNode),
136 mode: 0660 | os.ModeDir,
142 // A memFS implements FileSystem, storing all metadata and actual file data
143 // in-memory. No limits on filesystem size are used, so it is not recommended
144 // this be used where the clients are untrusted.
146 // Concurrent access is permitted. The tree structure is protected by a mutex,
147 // and each node's contents and metadata are protected by a per-node mutex.
149 // TODO: Enforce file permissions.
155 // TODO: clean up and rationalize the walk/find code.
157 // walk walks the directory tree for the fullname, calling f at each step. If f
158 // returns an error, the walk will be aborted and return that same error.
160 // dir is the directory at that step, frag is the name fragment, and final is
161 // whether it is the final step. For example, walking "/foo/bar/x" will result
163 // - "/", "foo", false
164 // - "/foo/", "bar", false
165 // - "/foo/bar/", "x", true
166 // The frag argument will be empty only if dir is the root node and the walk
167 // ends at that root node.
168 func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error {
170 fullname = slashClean(fullname)
172 // Strip any leading "/"s to make fullname a relative path, as the walk
173 // starts at fs.root.
174 if fullname[0] == '/' {
175 fullname = fullname[1:]
180 frag, remaining := fullname, ""
181 i := strings.IndexRune(fullname, '/')
184 frag, remaining = fullname[:i], fullname[i+1:]
186 if frag == "" && dir != &fs.root {
187 panic("webdav: empty path fragment for a clean path")
189 if err := f(dir, frag, final); err != nil {
190 return &os.PathError{
199 child := dir.children[frag]
201 return &os.PathError{
207 if !child.mode.IsDir() {
208 return &os.PathError{
214 dir, fullname = child, remaining
219 // find returns the parent of the named node and the relative name fragment
220 // from the parent to the child. For example, if finding "/foo/bar/baz" then
221 // parent will be the node for "/foo/bar" and frag will be "baz".
223 // If the fullname names the root node, then parent, frag and err will be zero.
225 // find returns an error if the parent does not already exist or the parent
226 // isn't a directory, but it will not return an error per se if the child does
227 // not already exist. The error returned is either nil or an *os.PathError
229 func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) {
230 err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error {
235 parent, frag = parent0, frag0
239 return parent, frag, err
242 func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
246 dir, frag, err := fs.find("mkdir", name)
251 // We can't create the root.
254 if _, ok := dir.children[frag]; ok {
257 dir.children[frag] = &memFSNode{
258 children: make(map[string]*memFSNode),
259 mode: perm.Perm() | os.ModeDir,
265 func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
269 dir, frag, err := fs.find("open", name)
275 // We're opening the root.
276 if flag&(os.O_WRONLY|os.O_RDWR) != 0 {
277 return nil, os.ErrPermission
279 n, frag = &fs.root, "/"
282 n = dir.children[frag]
283 if flag&(os.O_SYNC|os.O_APPEND) != 0 {
284 // memFile doesn't support these flags yet.
285 return nil, os.ErrInvalid
287 if flag&os.O_CREATE != 0 {
288 if flag&os.O_EXCL != 0 && n != nil {
289 return nil, os.ErrExist
295 dir.children[frag] = n
299 return nil, os.ErrNotExist
301 if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 {
308 children := make([]os.FileInfo, 0, len(n.children))
309 for cName, c := range n.children {
310 children = append(children, c.stat(cName))
315 childrenSnapshot: children,
319 func (fs *memFS) RemoveAll(ctx context.Context, name string) error {
323 dir, frag, err := fs.find("remove", name)
328 // We can't remove the root.
331 delete(dir.children, frag)
335 func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
339 oldName = slashClean(oldName)
340 newName = slashClean(newName)
341 if oldName == newName {
344 if strings.HasPrefix(newName, oldName+"/") {
345 // We can't rename oldName to be a sub-directory of itself.
349 oDir, oFrag, err := fs.find("rename", oldName)
354 // We can't rename from the root.
358 nDir, nFrag, err := fs.find("rename", newName)
363 // We can't rename to the root.
367 oNode, ok := oDir.children[oFrag]
369 return os.ErrNotExist
371 if oNode.children != nil {
372 if nNode, ok := nDir.children[nFrag]; ok {
373 if nNode.children == nil {
374 return errNotADirectory
376 if len(nNode.children) != 0 {
377 return errDirectoryNotEmpty
381 delete(oDir.children, oFrag)
382 nDir.children[nFrag] = oNode
386 func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
390 dir, frag, err := fs.find("stat", name)
395 // We're stat'ting the root.
396 return fs.root.stat("/"), nil
398 if n, ok := dir.children[frag]; ok {
399 return n.stat(path.Base(name)), nil
401 return nil, os.ErrNotExist
404 // A memFSNode represents a single entry in the in-memory filesystem and also
405 // implements os.FileInfo.
406 type memFSNode struct {
407 // children is protected by memFS.mu.
408 children map[string]*memFSNode
414 deadProps map[xml.Name]Property
417 func (n *memFSNode) stat(name string) *memFileInfo {
422 size: int64(len(n.data)),
428 func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) {
431 if len(n.deadProps) == 0 {
434 ret := make(map[xml.Name]Property, len(n.deadProps))
435 for k, v := range n.deadProps {
441 func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) {
444 pstat := Propstat{Status: http.StatusOK}
445 for _, patch := range patches {
446 for _, p := range patch.Props {
447 pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName})
449 delete(n.deadProps, p.XMLName)
452 if n.deadProps == nil {
453 n.deadProps = map[xml.Name]Property{}
455 n.deadProps[p.XMLName] = p
458 return []Propstat{pstat}, nil
461 type memFileInfo struct {
468 func (f *memFileInfo) Name() string { return f.name }
469 func (f *memFileInfo) Size() int64 { return f.size }
470 func (f *memFileInfo) Mode() os.FileMode { return f.mode }
471 func (f *memFileInfo) ModTime() time.Time { return f.modTime }
472 func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() }
473 func (f *memFileInfo) Sys() interface{} { return nil }
475 // A memFile is a File implementation for a memFSNode. It is a per-file (not
476 // per-node) read/write position, and a snapshot of the memFS' tree structure
477 // (a node's name and children) for that node.
478 type memFile struct {
481 childrenSnapshot []os.FileInfo
482 // pos is protected by n.mu.
486 // A *memFile implements the optional DeadPropsHolder interface.
487 var _ DeadPropsHolder = (*memFile)(nil)
489 func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() }
490 func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) }
492 func (f *memFile) Close() error {
496 func (f *memFile) Read(p []byte) (int, error) {
498 defer f.n.mu.Unlock()
499 if f.n.mode.IsDir() {
500 return 0, os.ErrInvalid
502 if f.pos >= len(f.n.data) {
505 n := copy(p, f.n.data[f.pos:])
510 func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
512 defer f.n.mu.Unlock()
513 if !f.n.mode.IsDir() {
514 return nil, os.ErrInvalid
517 if old >= len(f.childrenSnapshot) {
518 // The os.File Readdir docs say that at the end of a directory,
519 // the error is io.EOF if count > 0 and nil if count <= 0.
527 if f.pos > len(f.childrenSnapshot) {
528 f.pos = len(f.childrenSnapshot)
531 f.pos = len(f.childrenSnapshot)
534 return f.childrenSnapshot[old:f.pos], nil
537 func (f *memFile) Seek(offset int64, whence int) (int64, error) {
539 defer f.n.mu.Unlock()
541 // TODO: How to handle offsets greater than the size of system int?
548 npos = len(f.n.data) + int(offset)
553 return 0, os.ErrInvalid
556 return int64(f.pos), nil
559 func (f *memFile) Stat() (os.FileInfo, error) {
560 return f.n.stat(f.nameSnapshot), nil
563 func (f *memFile) Write(p []byte) (int, error) {
566 defer f.n.mu.Unlock()
568 if f.n.mode.IsDir() {
569 return 0, os.ErrInvalid
571 if f.pos < len(f.n.data) {
572 n := copy(f.n.data[f.pos:], p)
575 } else if f.pos > len(f.n.data) {
576 // Write permits the creation of holes, if we've seek'ed past the
577 // existing end of file.
578 if f.pos <= cap(f.n.data) {
579 oldLen := len(f.n.data)
580 f.n.data = f.n.data[:f.pos]
581 hole := f.n.data[oldLen:]
582 for i := range hole {
586 d := make([]byte, f.pos, f.pos+len(p))
593 // We should only get here if f.pos == len(f.n.data).
594 f.n.data = append(f.n.data, p...)
595 f.pos = len(f.n.data)
597 f.n.modTime = time.Now()
601 // moveFiles moves files and/or directories from src to dst.
603 // See section 9.9.4 for when various HTTP status codes apply.
604 func moveFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool) (status int, err error) {
606 if _, err := fs.Stat(ctx, dst); err != nil {
607 if !os.IsNotExist(err) {
608 return http.StatusForbidden, err
611 } else if overwrite {
612 // Section 9.9.3 says that "If a resource exists at the destination
613 // and the Overwrite header is "T", then prior to performing the move,
614 // the server must perform a DELETE with "Depth: infinity" on the
615 // destination resource.
616 if err := fs.RemoveAll(ctx, dst); err != nil {
617 return http.StatusForbidden, err
620 return http.StatusPreconditionFailed, os.ErrExist
622 if err := fs.Rename(ctx, src, dst); err != nil {
623 return http.StatusForbidden, err
626 return http.StatusCreated, nil
628 return http.StatusNoContent, nil
631 func copyProps(dst, src File) error {
632 d, ok := dst.(DeadPropsHolder)
636 s, ok := src.(DeadPropsHolder)
640 m, err := s.DeadProps()
644 props := make([]Property, 0, len(m))
645 for _, prop := range m {
646 props = append(props, prop)
648 _, err = d.Patch([]Proppatch{{Props: props}})
652 // copyFiles copies files and/or directories from src to dst.
654 // See section 9.8.5 for when various HTTP status codes apply.
655 func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
656 if recursion == 1000 {
657 return http.StatusInternalServerError, errRecursionTooDeep
661 // TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/
662 // into /A/B/ could lead to infinite recursion if not handled correctly."
664 srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0)
666 if os.IsNotExist(err) {
667 return http.StatusNotFound, err
669 return http.StatusInternalServerError, err
671 defer srcFile.Close()
672 srcStat, err := srcFile.Stat()
674 if os.IsNotExist(err) {
675 return http.StatusNotFound, err
677 return http.StatusInternalServerError, err
679 srcPerm := srcStat.Mode() & os.ModePerm
682 if _, err := fs.Stat(ctx, dst); err != nil {
683 if os.IsNotExist(err) {
686 return http.StatusForbidden, err
690 return http.StatusPreconditionFailed, os.ErrExist
692 if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) {
693 return http.StatusForbidden, err
698 if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
699 return http.StatusForbidden, err
701 if depth == infiniteDepth {
702 children, err := srcFile.Readdir(-1)
704 return http.StatusForbidden, err
706 for _, c := range children {
708 s := path.Join(src, name)
709 d := path.Join(dst, name)
710 cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion)
712 // TODO: MultiStatus.
719 dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm)
721 if os.IsNotExist(err) {
722 return http.StatusConflict, err
724 return http.StatusForbidden, err
727 _, copyErr := io.Copy(dstFile, srcFile)
728 propsErr := copyProps(dstFile, srcFile)
729 closeErr := dstFile.Close()
731 return http.StatusInternalServerError, copyErr
734 return http.StatusInternalServerError, propsErr
737 return http.StatusInternalServerError, closeErr
742 return http.StatusCreated, nil
744 return http.StatusNoContent, nil
747 // walkFS traverses filesystem fs starting at name up to depth levels.
749 // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
750 // walkFS calls walkFn. If a visited file system node is a directory and
751 // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
752 func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
753 // This implementation is based on Walk's code in the standard path/filepath package.
754 err := walkFn(name, info, nil)
756 if info.IsDir() && err == filepath.SkipDir {
761 if !info.IsDir() || depth == 0 {
768 // Read directory names.
769 f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
771 return walkFn(name, info, err)
773 fileInfos, err := f.Readdir(0)
776 return walkFn(name, info, err)
779 for _, fileInfo := range fileInfos {
780 filename := path.Join(name, fileInfo.Name())
781 fileInfo, err := fs.Stat(ctx, filename)
783 if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
787 err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn)
789 if !fileInfo.IsDir() || err != filepath.SkipDir {