github.com/Andyfoo/golang/x/net@v0.0.0-20190901054642-57c1bf301704/webdav/file.go (about)

     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.
     4  
     5  package webdav
     6  
     7  import (
     8  	"context"
     9  	"encoding/xml"
    10  	"io"
    11  	"net/http"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  )
    19  
    20  // slashClean is equivalent to but slightly more efficient than
    21  // path.Clean("/" + name).
    22  func slashClean(name string) string {
    23  	if name == "" || name[0] != '/' {
    24  		name = "/" + name
    25  	}
    26  	return path.Clean(name)
    27  }
    28  
    29  // A FileSystem implements access to a collection of named files. The elements
    30  // in a file path are separated by slash ('/', U+002F) characters, regardless
    31  // of host operating system convention.
    32  //
    33  // Each method has the same semantics as the os package's function of the same
    34  // name.
    35  //
    36  // Note that the os.Rename documentation says that "OS-specific restrictions
    37  // might apply". In particular, whether or not renaming a file or directory
    38  // overwriting another existing file or directory is an error is OS-dependent.
    39  type FileSystem interface {
    40  	Mkdir(ctx context.Context, name string, perm os.FileMode) error
    41  	OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error)
    42  	RemoveAll(ctx context.Context, name string) error
    43  	Rename(ctx context.Context, oldName, newName string) error
    44  	Stat(ctx context.Context, name string) (os.FileInfo, error)
    45  }
    46  
    47  // A File is returned by a FileSystem's OpenFile method and can be served by a
    48  // Handler.
    49  //
    50  // A File may optionally implement the DeadPropsHolder interface, if it can
    51  // load and save dead properties.
    52  type File interface {
    53  	http.File
    54  	io.Writer
    55  }
    56  
    57  // A Dir implements FileSystem using the native file system restricted to a
    58  // specific directory tree.
    59  //
    60  // While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's
    61  // string value is a filename on the native file system, not a URL, so it is
    62  // separated by filepath.Separator, which isn't necessarily '/'.
    63  //
    64  // An empty Dir is treated as ".".
    65  type Dir string
    66  
    67  func (d Dir) resolve(name string) string {
    68  	// This implementation is based on Dir.Open's code in the standard net/http package.
    69  	if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
    70  		strings.Contains(name, "\x00") {
    71  		return ""
    72  	}
    73  	dir := string(d)
    74  	if dir == "" {
    75  		dir = "."
    76  	}
    77  	return filepath.Join(dir, filepath.FromSlash(slashClean(name)))
    78  }
    79  
    80  func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
    81  	if name = d.resolve(name); name == "" {
    82  		return os.ErrNotExist
    83  	}
    84  	return os.Mkdir(name, perm)
    85  }
    86  
    87  func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
    88  	if name = d.resolve(name); name == "" {
    89  		return nil, os.ErrNotExist
    90  	}
    91  	f, err := os.OpenFile(name, flag, perm)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return f, nil
    96  }
    97  
    98  func (d Dir) RemoveAll(ctx context.Context, name string) error {
    99  	if name = d.resolve(name); name == "" {
   100  		return os.ErrNotExist
   101  	}
   102  	if name == filepath.Clean(string(d)) {
   103  		// Prohibit removing the virtual root directory.
   104  		return os.ErrInvalid
   105  	}
   106  	return os.RemoveAll(name)
   107  }
   108  
   109  func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
   110  	if oldName = d.resolve(oldName); oldName == "" {
   111  		return os.ErrNotExist
   112  	}
   113  	if newName = d.resolve(newName); newName == "" {
   114  		return os.ErrNotExist
   115  	}
   116  	if root := filepath.Clean(string(d)); root == oldName || root == newName {
   117  		// Prohibit renaming from or to the virtual root directory.
   118  		return os.ErrInvalid
   119  	}
   120  	return os.Rename(oldName, newName)
   121  }
   122  
   123  func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
   124  	if name = d.resolve(name); name == "" {
   125  		return nil, os.ErrNotExist
   126  	}
   127  	return os.Stat(name)
   128  }
   129  
   130  // NewMemFS returns a new in-memory FileSystem implementation.
   131  func NewMemFS() FileSystem {
   132  	return &memFS{
   133  		root: memFSNode{
   134  			children: make(map[string]*memFSNode),
   135  			mode:     0660 | os.ModeDir,
   136  			modTime:  time.Now(),
   137  		},
   138  	}
   139  }
   140  
   141  // A memFS implements FileSystem, storing all metadata and actual file data
   142  // in-memory. No limits on filesystem size are used, so it is not recommended
   143  // this be used where the clients are untrusted.
   144  //
   145  // Concurrent access is permitted. The tree structure is protected by a mutex,
   146  // and each node's contents and metadata are protected by a per-node mutex.
   147  //
   148  // TODO: Enforce file permissions.
   149  type memFS struct {
   150  	mu   sync.Mutex
   151  	root memFSNode
   152  }
   153  
   154  // TODO: clean up and rationalize the walk/find code.
   155  
   156  // walk walks the directory tree for the fullname, calling f at each step. If f
   157  // returns an error, the walk will be aborted and return that same error.
   158  //
   159  // dir is the directory at that step, frag is the name fragment, and final is
   160  // whether it is the final step. For example, walking "/foo/bar/x" will result
   161  // in 3 calls to f:
   162  //   - "/", "foo", false
   163  //   - "/foo/", "bar", false
   164  //   - "/foo/bar/", "x", true
   165  // The frag argument will be empty only if dir is the root node and the walk
   166  // ends at that root node.
   167  func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error {
   168  	original := fullname
   169  	fullname = slashClean(fullname)
   170  
   171  	// Strip any leading "/"s to make fullname a relative path, as the walk
   172  	// starts at fs.root.
   173  	if fullname[0] == '/' {
   174  		fullname = fullname[1:]
   175  	}
   176  	dir := &fs.root
   177  
   178  	for {
   179  		frag, remaining := fullname, ""
   180  		i := strings.IndexRune(fullname, '/')
   181  		final := i < 0
   182  		if !final {
   183  			frag, remaining = fullname[:i], fullname[i+1:]
   184  		}
   185  		if frag == "" && dir != &fs.root {
   186  			panic("webdav: empty path fragment for a clean path")
   187  		}
   188  		if err := f(dir, frag, final); err != nil {
   189  			return &os.PathError{
   190  				Op:   op,
   191  				Path: original,
   192  				Err:  err,
   193  			}
   194  		}
   195  		if final {
   196  			break
   197  		}
   198  		child := dir.children[frag]
   199  		if child == nil {
   200  			return &os.PathError{
   201  				Op:   op,
   202  				Path: original,
   203  				Err:  os.ErrNotExist,
   204  			}
   205  		}
   206  		if !child.mode.IsDir() {
   207  			return &os.PathError{
   208  				Op:   op,
   209  				Path: original,
   210  				Err:  os.ErrInvalid,
   211  			}
   212  		}
   213  		dir, fullname = child, remaining
   214  	}
   215  	return nil
   216  }
   217  
   218  // find returns the parent of the named node and the relative name fragment
   219  // from the parent to the child. For example, if finding "/foo/bar/baz" then
   220  // parent will be the node for "/foo/bar" and frag will be "baz".
   221  //
   222  // If the fullname names the root node, then parent, frag and err will be zero.
   223  //
   224  // find returns an error if the parent does not already exist or the parent
   225  // isn't a directory, but it will not return an error per se if the child does
   226  // not already exist. The error returned is either nil or an *os.PathError
   227  // whose Op is op.
   228  func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) {
   229  	err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error {
   230  		if !final {
   231  			return nil
   232  		}
   233  		if frag0 != "" {
   234  			parent, frag = parent0, frag0
   235  		}
   236  		return nil
   237  	})
   238  	return parent, frag, err
   239  }
   240  
   241  func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
   242  	fs.mu.Lock()
   243  	defer fs.mu.Unlock()
   244  
   245  	dir, frag, err := fs.find("mkdir", name)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	if dir == nil {
   250  		// We can't create the root.
   251  		return os.ErrInvalid
   252  	}
   253  	if _, ok := dir.children[frag]; ok {
   254  		return os.ErrExist
   255  	}
   256  	dir.children[frag] = &memFSNode{
   257  		children: make(map[string]*memFSNode),
   258  		mode:     perm.Perm() | os.ModeDir,
   259  		modTime:  time.Now(),
   260  	}
   261  	return nil
   262  }
   263  
   264  func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
   265  	fs.mu.Lock()
   266  	defer fs.mu.Unlock()
   267  
   268  	dir, frag, err := fs.find("open", name)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	var n *memFSNode
   273  	if dir == nil {
   274  		// We're opening the root.
   275  		if flag&(os.O_WRONLY|os.O_RDWR) != 0 {
   276  			return nil, os.ErrPermission
   277  		}
   278  		n, frag = &fs.root, "/"
   279  
   280  	} else {
   281  		n = dir.children[frag]
   282  		if flag&(os.O_SYNC|os.O_APPEND) != 0 {
   283  			// memFile doesn't support these flags yet.
   284  			return nil, os.ErrInvalid
   285  		}
   286  		if flag&os.O_CREATE != 0 {
   287  			if flag&os.O_EXCL != 0 && n != nil {
   288  				return nil, os.ErrExist
   289  			}
   290  			if n == nil {
   291  				n = &memFSNode{
   292  					mode: perm.Perm(),
   293  				}
   294  				dir.children[frag] = n
   295  			}
   296  		}
   297  		if n == nil {
   298  			return nil, os.ErrNotExist
   299  		}
   300  		if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 {
   301  			n.mu.Lock()
   302  			n.data = nil
   303  			n.mu.Unlock()
   304  		}
   305  	}
   306  
   307  	children := make([]os.FileInfo, 0, len(n.children))
   308  	for cName, c := range n.children {
   309  		children = append(children, c.stat(cName))
   310  	}
   311  	return &memFile{
   312  		n:                n,
   313  		nameSnapshot:     frag,
   314  		childrenSnapshot: children,
   315  	}, nil
   316  }
   317  
   318  func (fs *memFS) RemoveAll(ctx context.Context, name string) error {
   319  	fs.mu.Lock()
   320  	defer fs.mu.Unlock()
   321  
   322  	dir, frag, err := fs.find("remove", name)
   323  	if err != nil {
   324  		return err
   325  	}
   326  	if dir == nil {
   327  		// We can't remove the root.
   328  		return os.ErrInvalid
   329  	}
   330  	delete(dir.children, frag)
   331  	return nil
   332  }
   333  
   334  func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
   335  	fs.mu.Lock()
   336  	defer fs.mu.Unlock()
   337  
   338  	oldName = slashClean(oldName)
   339  	newName = slashClean(newName)
   340  	if oldName == newName {
   341  		return nil
   342  	}
   343  	if strings.HasPrefix(newName, oldName+"/") {
   344  		// We can't rename oldName to be a sub-directory of itself.
   345  		return os.ErrInvalid
   346  	}
   347  
   348  	oDir, oFrag, err := fs.find("rename", oldName)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	if oDir == nil {
   353  		// We can't rename from the root.
   354  		return os.ErrInvalid
   355  	}
   356  
   357  	nDir, nFrag, err := fs.find("rename", newName)
   358  	if err != nil {
   359  		return err
   360  	}
   361  	if nDir == nil {
   362  		// We can't rename to the root.
   363  		return os.ErrInvalid
   364  	}
   365  
   366  	oNode, ok := oDir.children[oFrag]
   367  	if !ok {
   368  		return os.ErrNotExist
   369  	}
   370  	if oNode.children != nil {
   371  		if nNode, ok := nDir.children[nFrag]; ok {
   372  			if nNode.children == nil {
   373  				return errNotADirectory
   374  			}
   375  			if len(nNode.children) != 0 {
   376  				return errDirectoryNotEmpty
   377  			}
   378  		}
   379  	}
   380  	delete(oDir.children, oFrag)
   381  	nDir.children[nFrag] = oNode
   382  	return nil
   383  }
   384  
   385  func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
   386  	fs.mu.Lock()
   387  	defer fs.mu.Unlock()
   388  
   389  	dir, frag, err := fs.find("stat", name)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	if dir == nil {
   394  		// We're stat'ting the root.
   395  		return fs.root.stat("/"), nil
   396  	}
   397  	if n, ok := dir.children[frag]; ok {
   398  		return n.stat(path.Base(name)), nil
   399  	}
   400  	return nil, os.ErrNotExist
   401  }
   402  
   403  // A memFSNode represents a single entry in the in-memory filesystem and also
   404  // implements os.FileInfo.
   405  type memFSNode struct {
   406  	// children is protected by memFS.mu.
   407  	children map[string]*memFSNode
   408  
   409  	mu        sync.Mutex
   410  	data      []byte
   411  	mode      os.FileMode
   412  	modTime   time.Time
   413  	deadProps map[xml.Name]Property
   414  }
   415  
   416  func (n *memFSNode) stat(name string) *memFileInfo {
   417  	n.mu.Lock()
   418  	defer n.mu.Unlock()
   419  	return &memFileInfo{
   420  		name:    name,
   421  		size:    int64(len(n.data)),
   422  		mode:    n.mode,
   423  		modTime: n.modTime,
   424  	}
   425  }
   426  
   427  func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) {
   428  	n.mu.Lock()
   429  	defer n.mu.Unlock()
   430  	if len(n.deadProps) == 0 {
   431  		return nil, nil
   432  	}
   433  	ret := make(map[xml.Name]Property, len(n.deadProps))
   434  	for k, v := range n.deadProps {
   435  		ret[k] = v
   436  	}
   437  	return ret, nil
   438  }
   439  
   440  func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) {
   441  	n.mu.Lock()
   442  	defer n.mu.Unlock()
   443  	pstat := Propstat{Status: http.StatusOK}
   444  	for _, patch := range patches {
   445  		for _, p := range patch.Props {
   446  			pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName})
   447  			if patch.Remove {
   448  				delete(n.deadProps, p.XMLName)
   449  				continue
   450  			}
   451  			if n.deadProps == nil {
   452  				n.deadProps = map[xml.Name]Property{}
   453  			}
   454  			n.deadProps[p.XMLName] = p
   455  		}
   456  	}
   457  	return []Propstat{pstat}, nil
   458  }
   459  
   460  type memFileInfo struct {
   461  	name    string
   462  	size    int64
   463  	mode    os.FileMode
   464  	modTime time.Time
   465  }
   466  
   467  func (f *memFileInfo) Name() string       { return f.name }
   468  func (f *memFileInfo) Size() int64        { return f.size }
   469  func (f *memFileInfo) Mode() os.FileMode  { return f.mode }
   470  func (f *memFileInfo) ModTime() time.Time { return f.modTime }
   471  func (f *memFileInfo) IsDir() bool        { return f.mode.IsDir() }
   472  func (f *memFileInfo) Sys() interface{}   { return nil }
   473  
   474  // A memFile is a File implementation for a memFSNode. It is a per-file (not
   475  // per-node) read/write position, and a snapshot of the memFS' tree structure
   476  // (a node's name and children) for that node.
   477  type memFile struct {
   478  	n                *memFSNode
   479  	nameSnapshot     string
   480  	childrenSnapshot []os.FileInfo
   481  	// pos is protected by n.mu.
   482  	pos int
   483  }
   484  
   485  // A *memFile implements the optional DeadPropsHolder interface.
   486  var _ DeadPropsHolder = (*memFile)(nil)
   487  
   488  func (f *memFile) DeadProps() (map[xml.Name]Property, error)     { return f.n.DeadProps() }
   489  func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) }
   490  
   491  func (f *memFile) Close() error {
   492  	return nil
   493  }
   494  
   495  func (f *memFile) Read(p []byte) (int, error) {
   496  	f.n.mu.Lock()
   497  	defer f.n.mu.Unlock()
   498  	if f.n.mode.IsDir() {
   499  		return 0, os.ErrInvalid
   500  	}
   501  	if f.pos >= len(f.n.data) {
   502  		return 0, io.EOF
   503  	}
   504  	n := copy(p, f.n.data[f.pos:])
   505  	f.pos += n
   506  	return n, nil
   507  }
   508  
   509  func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
   510  	f.n.mu.Lock()
   511  	defer f.n.mu.Unlock()
   512  	if !f.n.mode.IsDir() {
   513  		return nil, os.ErrInvalid
   514  	}
   515  	old := f.pos
   516  	if old >= len(f.childrenSnapshot) {
   517  		// The os.File Readdir docs say that at the end of a directory,
   518  		// the error is io.EOF if count > 0 and nil if count <= 0.
   519  		if count > 0 {
   520  			return nil, io.EOF
   521  		}
   522  		return nil, nil
   523  	}
   524  	if count > 0 {
   525  		f.pos += count
   526  		if f.pos > len(f.childrenSnapshot) {
   527  			f.pos = len(f.childrenSnapshot)
   528  		}
   529  	} else {
   530  		f.pos = len(f.childrenSnapshot)
   531  		old = 0
   532  	}
   533  	return f.childrenSnapshot[old:f.pos], nil
   534  }
   535  
   536  func (f *memFile) Seek(offset int64, whence int) (int64, error) {
   537  	f.n.mu.Lock()
   538  	defer f.n.mu.Unlock()
   539  	npos := f.pos
   540  	// TODO: How to handle offsets greater than the size of system int?
   541  	switch whence {
   542  	case os.SEEK_SET:
   543  		npos = int(offset)
   544  	case os.SEEK_CUR:
   545  		npos += int(offset)
   546  	case os.SEEK_END:
   547  		npos = len(f.n.data) + int(offset)
   548  	default:
   549  		npos = -1
   550  	}
   551  	if npos < 0 {
   552  		return 0, os.ErrInvalid
   553  	}
   554  	f.pos = npos
   555  	return int64(f.pos), nil
   556  }
   557  
   558  func (f *memFile) Stat() (os.FileInfo, error) {
   559  	return f.n.stat(f.nameSnapshot), nil
   560  }
   561  
   562  func (f *memFile) Write(p []byte) (int, error) {
   563  	lenp := len(p)
   564  	f.n.mu.Lock()
   565  	defer f.n.mu.Unlock()
   566  
   567  	if f.n.mode.IsDir() {
   568  		return 0, os.ErrInvalid
   569  	}
   570  	if f.pos < len(f.n.data) {
   571  		n := copy(f.n.data[f.pos:], p)
   572  		f.pos += n
   573  		p = p[n:]
   574  	} else if f.pos > len(f.n.data) {
   575  		// Write permits the creation of holes, if we've seek'ed past the
   576  		// existing end of file.
   577  		if f.pos <= cap(f.n.data) {
   578  			oldLen := len(f.n.data)
   579  			f.n.data = f.n.data[:f.pos]
   580  			hole := f.n.data[oldLen:]
   581  			for i := range hole {
   582  				hole[i] = 0
   583  			}
   584  		} else {
   585  			d := make([]byte, f.pos, f.pos+len(p))
   586  			copy(d, f.n.data)
   587  			f.n.data = d
   588  		}
   589  	}
   590  
   591  	if len(p) > 0 {
   592  		// We should only get here if f.pos == len(f.n.data).
   593  		f.n.data = append(f.n.data, p...)
   594  		f.pos = len(f.n.data)
   595  	}
   596  	f.n.modTime = time.Now()
   597  	return lenp, nil
   598  }
   599  
   600  // moveFiles moves files and/or directories from src to dst.
   601  //
   602  // See section 9.9.4 for when various HTTP status codes apply.
   603  func moveFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool) (status int, err error) {
   604  	created := false
   605  	if _, err := fs.Stat(ctx, dst); err != nil {
   606  		if !os.IsNotExist(err) {
   607  			return http.StatusForbidden, err
   608  		}
   609  		created = true
   610  	} else if overwrite {
   611  		// Section 9.9.3 says that "If a resource exists at the destination
   612  		// and the Overwrite header is "T", then prior to performing the move,
   613  		// the server must perform a DELETE with "Depth: infinity" on the
   614  		// destination resource.
   615  		if err := fs.RemoveAll(ctx, dst); err != nil {
   616  			return http.StatusForbidden, err
   617  		}
   618  	} else {
   619  		return http.StatusPreconditionFailed, os.ErrExist
   620  	}
   621  	if err := fs.Rename(ctx, src, dst); err != nil {
   622  		return http.StatusForbidden, err
   623  	}
   624  	if created {
   625  		return http.StatusCreated, nil
   626  	}
   627  	return http.StatusNoContent, nil
   628  }
   629  
   630  func copyProps(dst, src File) error {
   631  	d, ok := dst.(DeadPropsHolder)
   632  	if !ok {
   633  		return nil
   634  	}
   635  	s, ok := src.(DeadPropsHolder)
   636  	if !ok {
   637  		return nil
   638  	}
   639  	m, err := s.DeadProps()
   640  	if err != nil {
   641  		return err
   642  	}
   643  	props := make([]Property, 0, len(m))
   644  	for _, prop := range m {
   645  		props = append(props, prop)
   646  	}
   647  	_, err = d.Patch([]Proppatch{{Props: props}})
   648  	return err
   649  }
   650  
   651  // copyFiles copies files and/or directories from src to dst.
   652  //
   653  // See section 9.8.5 for when various HTTP status codes apply.
   654  func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
   655  	if recursion == 1000 {
   656  		return http.StatusInternalServerError, errRecursionTooDeep
   657  	}
   658  	recursion++
   659  
   660  	// TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/
   661  	// into /A/B/ could lead to infinite recursion if not handled correctly."
   662  
   663  	srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0)
   664  	if err != nil {
   665  		if os.IsNotExist(err) {
   666  			return http.StatusNotFound, err
   667  		}
   668  		return http.StatusInternalServerError, err
   669  	}
   670  	defer srcFile.Close()
   671  	srcStat, err := srcFile.Stat()
   672  	if err != nil {
   673  		if os.IsNotExist(err) {
   674  			return http.StatusNotFound, err
   675  		}
   676  		return http.StatusInternalServerError, err
   677  	}
   678  	srcPerm := srcStat.Mode() & os.ModePerm
   679  
   680  	created := false
   681  	if _, err := fs.Stat(ctx, dst); err != nil {
   682  		if os.IsNotExist(err) {
   683  			created = true
   684  		} else {
   685  			return http.StatusForbidden, err
   686  		}
   687  	} else {
   688  		if !overwrite {
   689  			return http.StatusPreconditionFailed, os.ErrExist
   690  		}
   691  		if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) {
   692  			return http.StatusForbidden, err
   693  		}
   694  	}
   695  
   696  	if srcStat.IsDir() {
   697  		if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
   698  			return http.StatusForbidden, err
   699  		}
   700  		if depth == infiniteDepth {
   701  			children, err := srcFile.Readdir(-1)
   702  			if err != nil {
   703  				return http.StatusForbidden, err
   704  			}
   705  			for _, c := range children {
   706  				name := c.Name()
   707  				s := path.Join(src, name)
   708  				d := path.Join(dst, name)
   709  				cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion)
   710  				if cErr != nil {
   711  					// TODO: MultiStatus.
   712  					return cStatus, cErr
   713  				}
   714  			}
   715  		}
   716  
   717  	} else {
   718  		dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm)
   719  		if err != nil {
   720  			if os.IsNotExist(err) {
   721  				return http.StatusConflict, err
   722  			}
   723  			return http.StatusForbidden, err
   724  
   725  		}
   726  		_, copyErr := io.Copy(dstFile, srcFile)
   727  		propsErr := copyProps(dstFile, srcFile)
   728  		closeErr := dstFile.Close()
   729  		if copyErr != nil {
   730  			return http.StatusInternalServerError, copyErr
   731  		}
   732  		if propsErr != nil {
   733  			return http.StatusInternalServerError, propsErr
   734  		}
   735  		if closeErr != nil {
   736  			return http.StatusInternalServerError, closeErr
   737  		}
   738  	}
   739  
   740  	if created {
   741  		return http.StatusCreated, nil
   742  	}
   743  	return http.StatusNoContent, nil
   744  }
   745  
   746  // walkFS traverses filesystem fs starting at name up to depth levels.
   747  //
   748  // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
   749  // walkFS calls walkFn. If a visited file system node is a directory and
   750  // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
   751  func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
   752  	// This implementation is based on Walk's code in the standard path/filepath package.
   753  	err := walkFn(name, info, nil)
   754  	if err != nil {
   755  		if info.IsDir() && err == filepath.SkipDir {
   756  			return nil
   757  		}
   758  		return err
   759  	}
   760  	if !info.IsDir() || depth == 0 {
   761  		return nil
   762  	}
   763  	if depth == 1 {
   764  		depth = 0
   765  	}
   766  
   767  	// Read directory names.
   768  	f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
   769  	if err != nil {
   770  		return walkFn(name, info, err)
   771  	}
   772  	fileInfos, err := f.Readdir(0)
   773  	f.Close()
   774  	if err != nil {
   775  		return walkFn(name, info, err)
   776  	}
   777  
   778  	for _, fileInfo := range fileInfos {
   779  		filename := path.Join(name, fileInfo.Name())
   780  		fileInfo, err := fs.Stat(ctx, filename)
   781  		if err != nil {
   782  			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
   783  				return err
   784  			}
   785  		} else {
   786  			err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn)
   787  			if err != nil {
   788  				if !fileInfo.IsDir() || err != filepath.SkipDir {
   789  					return err
   790  				}
   791  			}
   792  		}
   793  	}
   794  	return nil
   795  }