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