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