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
5package webdav
6
7import (
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).
22func 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.
39type 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.
52type 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 ".".
65type Dir string
66
67func (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
80func (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
87func (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
98func (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
109func (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
123func (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.
131func 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.
149type 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.
167func (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.
228func (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
241func (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
264func (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
318func (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
334func (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
385func (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.
405type 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
416func (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
427func (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
440func (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
460type memFileInfo struct {
461	name    string
462	size    int64
463	mode    os.FileMode
464	modTime time.Time
465}
466
467func (f *memFileInfo) Name() string       { return f.name }
468func (f *memFileInfo) Size() int64        { return f.size }
469func (f *memFileInfo) Mode() os.FileMode  { return f.mode }
470func (f *memFileInfo) ModTime() time.Time { return f.modTime }
471func (f *memFileInfo) IsDir() bool        { return f.mode.IsDir() }
472func (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.
477type 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.
486var _ DeadPropsHolder = (*memFile)(nil)
487
488func (f *memFile) DeadProps() (map[xml.Name]Property, error)     { return f.n.DeadProps() }
489func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) }
490
491func (f *memFile) Close() error {
492	return nil
493}
494
495func (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
509func (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
536func (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
558func (f *memFile) Stat() (os.FileInfo, error) {
559	return f.n.stat(f.nameSnapshot), nil
560}
561
562func (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.
603func 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
630func 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.
654func 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.
751func 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}
796