blob: 3cd19ffbe338facc87173c2cc8114a9713ff6dee [file] [log] [blame]
Nigel Taobaf9fd42014-11-11 17:46:57 +11001// 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 (
Brad Fitzpatrick04ba8c82018-11-08 03:31:47 +00008 "context"
Nigel Taoe45385e2016-04-07 12:17:48 +10009 "encoding/xml"
Nigel Taobaf9fd42014-11-11 17:46:57 +110010 "io"
11 "net/http"
12 "os"
Nigel Tao240cea52014-11-14 11:47:41 +110013 "path"
14 "path/filepath"
billofarrell146b70c2020-10-21 17:25:16 -040015 "runtime"
Nigel Tao240cea52014-11-14 11:47:41 +110016 "strings"
Nick Cooper3eb064e2015-01-05 15:04:58 +110017 "sync"
18 "time"
Nigel Taobaf9fd42014-11-11 17:46:57 +110019)
20
Nigel Tao8b2d0ae2015-01-16 15:35:09 +110021// slashClean is equivalent to but slightly more efficient than
22// path.Clean("/" + name).
23func slashClean(name string) string {
24 if name == "" || name[0] != '/' {
25 name = "/" + name
26 }
27 return path.Clean(name)
28}
29
Nigel Tao240cea52014-11-14 11:47:41 +110030// 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.
Nigel Tao5b4754d2015-02-10 16:53:23 +110036//
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.
Nigel Taobaf9fd42014-11-11 17:46:57 +110040type FileSystem interface {
Daniel Theophanes4bb47a12016-10-31 09:36:42 -070041 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)
Nigel Taobaf9fd42014-11-11 17:46:57 +110046}
47
Nigel Tao240cea52014-11-14 11:47:41 +110048// A File is returned by a FileSystem's OpenFile method and can be served by a
49// Handler.
Nigel Tao72bfdce2015-05-19 16:32:16 +100050//
51// A File may optionally implement the DeadPropsHolder interface, if it can
52// load and save dead properties.
Nigel Taobaf9fd42014-11-11 17:46:57 +110053type File interface {
54 http.File
55 io.Writer
56}
57
Nigel Tao240cea52014-11-14 11:47:41 +110058// 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 ".".
66type Dir string
67
68func (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 }
Nigel Tao8b2d0ae2015-01-16 15:35:09 +110078 return filepath.Join(dir, filepath.FromSlash(slashClean(name)))
Nigel Tao240cea52014-11-14 11:47:41 +110079}
80
Daniel Theophanes4bb47a12016-10-31 09:36:42 -070081func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
Nigel Tao240cea52014-11-14 11:47:41 +110082 if name = d.resolve(name); name == "" {
83 return os.ErrNotExist
84 }
85 return os.Mkdir(name, perm)
86}
87
Daniel Theophanes4bb47a12016-10-31 09:36:42 -070088func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
Nigel Tao240cea52014-11-14 11:47:41 +110089 if name = d.resolve(name); name == "" {
90 return nil, os.ErrNotExist
91 }
Nigel Tao1db34d82015-01-16 10:33:09 +110092 f, err := os.OpenFile(name, flag, perm)
93 if err != nil {
94 return nil, err
95 }
96 return f, nil
Nigel Tao240cea52014-11-14 11:47:41 +110097}
98
Daniel Theophanes4bb47a12016-10-31 09:36:42 -070099func (d Dir) RemoveAll(ctx context.Context, name string) error {
Nigel Tao240cea52014-11-14 11:47:41 +1100100 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
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700110func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100111 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
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700124func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
Nigel Tao240cea52014-11-14 11:47:41 +1100125 if name = d.resolve(name); name == "" {
126 return nil, os.ErrNotExist
127 }
128 return os.Stat(name)
129}
130
Nick Cooper3eb064e2015-01-05 15:04:58 +1100131// NewMemFS returns a new in-memory FileSystem implementation.
132func 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.
150type memFS struct {
151 mu sync.Mutex
152 root memFSNode
153}
154
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100155// TODO: clean up and rationalize the walk/find code.
156
Nick Cooper3eb064e2015-01-05 15:04:58 +1100157// 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//
Nick Cooper3eb064e2015-01-05 15:04:58 +1100160// 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
Russ Cox290c4692022-04-11 13:11:24 -0400166//
Nigel Taodc4a1802015-01-13 13:19:52 +1100167// The frag argument will be empty only if dir is the root node and the walk
168// ends at that root node.
Nick Cooper3eb064e2015-01-05 15:04:58 +1100169func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100170 original := fullname
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100171 fullname = slashClean(fullname)
Nick Cooper3eb064e2015-01-05 15:04:58 +1100172
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 }
Nigel Taodc4a1802015-01-13 13:19:52 +1100187 if frag == "" && dir != &fs.root {
188 panic("webdav: empty path fragment for a clean path")
189 }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100190 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 }
Nigel Tao3bf99b62015-01-20 15:06:04 +1100208 if !child.mode.IsDir() {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100209 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
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100220// 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.
230func (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 {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100232 if !final {
233 return nil
234 }
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100235 if frag0 != "" {
236 parent, frag = parent0, frag0
Nick Cooper3eb064e2015-01-05 15:04:58 +1100237 }
238 return nil
239 })
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100240 return parent, frag, err
241}
242
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700243func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100244 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{
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100259 children: make(map[string]*memFSNode),
260 mode: perm.Perm() | os.ModeDir,
261 modTime: time.Now(),
262 }
263 return nil
Nick Cooper3eb064e2015-01-05 15:04:58 +1100264}
265
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700266func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100267 fs.mu.Lock()
268 defer fs.mu.Unlock()
Nigel Taodc4a1802015-01-13 13:19:52 +1100269
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100270 dir, frag, err := fs.find("open", name)
Nick Cooper3eb064e2015-01-05 15:04:58 +1100271 if err != nil {
272 return nil, err
273 }
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100274 var n *memFSNode
275 if dir == nil {
276 // We're opening the root.
billofarrell146b70c2020-10-21 17:25:16 -0400277 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 }
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100285 }
Nigel Tao3bf99b62015-01-20 15:06:04 +1100286 n, frag = &fs.root, "/"
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100287
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{
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100300 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))
Nigel Tao3bf99b62015-01-20 15:06:04 +1100316 for cName, c := range n.children {
317 children = append(children, c.stat(cName))
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100318 }
319 return &memFile{
320 n: n,
Nigel Tao3bf99b62015-01-20 15:06:04 +1100321 nameSnapshot: frag,
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100322 childrenSnapshot: children,
323 }, nil
Nick Cooper3eb064e2015-01-05 15:04:58 +1100324}
325
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700326func (fs *memFS) RemoveAll(ctx context.Context, name string) error {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100327 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
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700342func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100343 fs.mu.Lock()
344 defer fs.mu.Unlock()
345
346 oldName = slashClean(oldName)
347 newName = slashClean(newName)
348 if oldName == newName {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100349 return nil
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100350 }
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 }
Nigel Tao3bf99b62015-01-20 15:06:04 +1100378 if oNode.children != nil {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100379 if nNode, ok := nDir.children[nFrag]; ok {
Nigel Tao3bf99b62015-01-20 15:06:04 +1100380 if nNode.children == nil {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100381 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
Nick Cooper3eb064e2015-01-05 15:04:58 +1100391}
392
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700393func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100394 fs.mu.Lock()
395 defer fs.mu.Unlock()
396
397 dir, frag, err := fs.find("stat", name)
Nick Cooper3eb064e2015-01-05 15:04:58 +1100398 if err != nil {
399 return nil, err
400 }
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100401 if dir == nil {
402 // We're stat'ting the root.
Nigel Tao3bf99b62015-01-20 15:06:04 +1100403 return fs.root.stat("/"), nil
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100404 }
405 if n, ok := dir.children[frag]; ok {
Nigel Tao3bf99b62015-01-20 15:06:04 +1100406 return n.stat(path.Base(name)), nil
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100407 }
408 return nil, os.ErrNotExist
Nick Cooper3eb064e2015-01-05 15:04:58 +1100409}
410
411// A memFSNode represents a single entry in the in-memory filesystem and also
412// implements os.FileInfo.
413type memFSNode struct {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100414 // children is protected by memFS.mu.
Nick Cooper3eb064e2015-01-05 15:04:58 +1100415 children map[string]*memFSNode
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100416
Nigel Tao72bfdce2015-05-19 16:32:16 +1000417 mu sync.Mutex
418 data []byte
419 mode os.FileMode
420 modTime time.Time
421 deadProps map[xml.Name]Property
Nick Cooper3eb064e2015-01-05 15:04:58 +1100422}
423
Nigel Tao3bf99b62015-01-20 15:06:04 +1100424func (n *memFSNode) stat(name string) *memFileInfo {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100425 n.mu.Lock()
426 defer n.mu.Unlock()
Nigel Tao3bf99b62015-01-20 15:06:04 +1100427 return &memFileInfo{
428 name: name,
429 size: int64(len(n.data)),
430 mode: n.mode,
431 modTime: n.modTime,
432 }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100433}
434
Nigel Tao589db582015-05-29 10:09:40 +1000435func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) {
Nigel Tao72bfdce2015-05-19 16:32:16 +1000436 n.mu.Lock()
437 defer n.mu.Unlock()
438 if len(n.deadProps) == 0 {
Nigel Tao589db582015-05-29 10:09:40 +1000439 return nil, nil
Nigel Tao72bfdce2015-05-19 16:32:16 +1000440 }
441 ret := make(map[xml.Name]Property, len(n.deadProps))
442 for k, v := range n.deadProps {
443 ret[k] = v
444 }
Nigel Tao589db582015-05-29 10:09:40 +1000445 return ret, nil
Nigel Tao72bfdce2015-05-19 16:32:16 +1000446}
447
448func (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
Nigel Tao3bf99b62015-01-20 15:06:04 +1100468type memFileInfo struct {
469 name string
470 size int64
471 mode os.FileMode
472 modTime time.Time
Nick Cooper3eb064e2015-01-05 15:04:58 +1100473}
474
Nigel Tao3bf99b62015-01-20 15:06:04 +1100475func (f *memFileInfo) Name() string { return f.name }
476func (f *memFileInfo) Size() int64 { return f.size }
477func (f *memFileInfo) Mode() os.FileMode { return f.mode }
478func (f *memFileInfo) ModTime() time.Time { return f.modTime }
479func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() }
480func (f *memFileInfo) Sys() interface{} { return nil }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100481
482// A memFile is a File implementation for a memFSNode. It is a per-file (not
Nigel Tao3bf99b62015-01-20 15:06:04 +1100483// per-node) read/write position, and a snapshot of the memFS' tree structure
484// (a node's name and children) for that node.
Nick Cooper3eb064e2015-01-05 15:04:58 +1100485type memFile struct {
Nigel Tao3bf99b62015-01-20 15:06:04 +1100486 n *memFSNode
487 nameSnapshot string
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100488 childrenSnapshot []os.FileInfo
489 // pos is protected by n.mu.
Nick Cooper3eb064e2015-01-05 15:04:58 +1100490 pos int
491}
492
Nigel Tao72bfdce2015-05-19 16:32:16 +1000493// A *memFile implements the optional DeadPropsHolder interface.
494var _ DeadPropsHolder = (*memFile)(nil)
495
Nigel Tao589db582015-05-29 10:09:40 +1000496func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() }
Nigel Tao72bfdce2015-05-19 16:32:16 +1000497func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) }
498
Nick Cooper3eb064e2015-01-05 15:04:58 +1100499func (f *memFile) Close() error {
500 return nil
501}
502
503func (f *memFile) Read(p []byte) (int, error) {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100504 f.n.mu.Lock()
505 defer f.n.mu.Unlock()
Nigel Taodc4a1802015-01-13 13:19:52 +1100506 if f.n.mode.IsDir() {
507 return 0, os.ErrInvalid
508 }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100509 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
517func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
Nick Cooper3eb064e2015-01-05 15:04:58 +1100518 f.n.mu.Lock()
519 defer f.n.mu.Unlock()
Nigel Taodc4a1802015-01-13 13:19:52 +1100520 if !f.n.mode.IsDir() {
521 return nil, os.ErrInvalid
522 }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100523 old := f.pos
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100524 if old >= len(f.childrenSnapshot) {
Nigel Taodc4a1802015-01-13 13:19:52 +1100525 // 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
Nick Cooper3eb064e2015-01-05 15:04:58 +1100531 }
532 if count > 0 {
533 f.pos += count
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100534 if f.pos > len(f.childrenSnapshot) {
535 f.pos = len(f.childrenSnapshot)
Nick Cooper3eb064e2015-01-05 15:04:58 +1100536 }
537 } else {
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100538 f.pos = len(f.childrenSnapshot)
Nick Cooper3eb064e2015-01-05 15:04:58 +1100539 old = 0
540 }
Nigel Tao8b2d0ae2015-01-16 15:35:09 +1100541 return f.childrenSnapshot[old:f.pos], nil
Nick Cooper3eb064e2015-01-05 15:04:58 +1100542}
543
544func (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 {
cui fliter8be63922022-09-21 11:41:25 +0000550 case io.SeekStart:
Nick Cooper3eb064e2015-01-05 15:04:58 +1100551 npos = int(offset)
cui fliter8be63922022-09-21 11:41:25 +0000552 case io.SeekCurrent:
Nick Cooper3eb064e2015-01-05 15:04:58 +1100553 npos += int(offset)
cui fliter8be63922022-09-21 11:41:25 +0000554 case io.SeekEnd:
Nick Cooper3eb064e2015-01-05 15:04:58 +1100555 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
566func (f *memFile) Stat() (os.FileInfo, error) {
Nigel Tao3bf99b62015-01-20 15:06:04 +1100567 return f.n.stat(f.nameSnapshot), nil
Nick Cooper3eb064e2015-01-05 15:04:58 +1100568}
569
570func (f *memFile) Write(p []byte) (int, error) {
Nigel Tao0000f672015-01-07 13:32:21 +1100571 lenp := len(p)
Nick Cooper3eb064e2015-01-05 15:04:58 +1100572 f.n.mu.Lock()
573 defer f.n.mu.Unlock()
Nigel Tao0000f672015-01-07 13:32:21 +1100574
Nigel Taodc4a1802015-01-13 13:19:52 +1100575 if f.n.mode.IsDir() {
576 return 0, os.ErrInvalid
577 }
Nigel Tao0000f672015-01-07 13:32:21 +1100578 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 }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100597 }
Nigel Tao0000f672015-01-07 13:32:21 +1100598
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 }
Nick Cooper3eb064e2015-01-05 15:04:58 +1100604 f.n.modTime = time.Now()
Nigel Tao0000f672015-01-07 13:32:21 +1100605 return lenp, nil
Nick Cooper3eb064e2015-01-05 15:04:58 +1100606}
Nigel Taocd216762015-01-29 14:43:00 +1100607
Nigel Tao5b4754d2015-02-10 16:53:23 +1100608// moveFiles moves files and/or directories from src to dst.
609//
610// See section 9.9.4 for when various HTTP status codes apply.
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700611func moveFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool) (status int, err error) {
Nigel Tao5b4754d2015-02-10 16:53:23 +1100612 created := false
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700613 if _, err := fs.Stat(ctx, dst); err != nil {
Nigel Tao5b4754d2015-02-10 16:53:23 +1100614 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.
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700623 if err := fs.RemoveAll(ctx, dst); err != nil {
Nigel Tao5b4754d2015-02-10 16:53:23 +1100624 return http.StatusForbidden, err
625 }
626 } else {
627 return http.StatusPreconditionFailed, os.ErrExist
628 }
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700629 if err := fs.Rename(ctx, src, dst); err != nil {
Nigel Tao5b4754d2015-02-10 16:53:23 +1100630 return http.StatusForbidden, err
631 }
632 if created {
633 return http.StatusCreated, nil
634 }
635 return http.StatusNoContent, nil
636}
637
Nigel Tao589db582015-05-29 10:09:40 +1000638func 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
Nigel Taocd216762015-01-29 14:43:00 +1100659// copyFiles copies files and/or directories from src to dst.
660//
661// See section 9.8.5 for when various HTTP status codes apply.
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700662func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
Nigel Taocd216762015-01-29 14:43:00 +1100663 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
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700671 srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0)
Nigel Taocd216762015-01-29 14:43:00 +1100672 if err != nil {
Nigel Tao1cd7b712015-02-04 12:23:13 +1100673 if os.IsNotExist(err) {
674 return http.StatusNotFound, err
675 }
676 return http.StatusInternalServerError, err
Nigel Taocd216762015-01-29 14:43:00 +1100677 }
678 defer srcFile.Close()
679 srcStat, err := srcFile.Stat()
680 if err != nil {
Nigel Tao1cd7b712015-02-04 12:23:13 +1100681 if os.IsNotExist(err) {
682 return http.StatusNotFound, err
683 }
684 return http.StatusInternalServerError, err
Nigel Taocd216762015-01-29 14:43:00 +1100685 }
686 srcPerm := srcStat.Mode() & os.ModePerm
687
688 created := false
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700689 if _, err := fs.Stat(ctx, dst); err != nil {
Nigel Taocd216762015-01-29 14:43:00 +1100690 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 }
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700699 if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) {
Nigel Taocd216762015-01-29 14:43:00 +1100700 return http.StatusForbidden, err
701 }
702 }
703
704 if srcStat.IsDir() {
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700705 if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
Nigel Taocd216762015-01-29 14:43:00 +1100706 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)
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700717 cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion)
Nigel Taocd216762015-01-29 14:43:00 +1100718 if cErr != nil {
719 // TODO: MultiStatus.
720 return cStatus, cErr
721 }
722 }
723 }
724
725 } else {
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700726 dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm)
Nigel Taocd216762015-01-29 14:43:00 +1100727 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)
Nigel Tao589db582015-05-29 10:09:40 +1000735 propsErr := copyProps(dstFile, srcFile)
Nigel Taocd216762015-01-29 14:43:00 +1100736 closeErr := dstFile.Close()
737 if copyErr != nil {
Nigel Tao1cd7b712015-02-04 12:23:13 +1100738 return http.StatusInternalServerError, copyErr
Nigel Taocd216762015-01-29 14:43:00 +1100739 }
Nigel Tao6fbb23f2015-05-28 20:32:34 +1000740 if propsErr != nil {
741 return http.StatusInternalServerError, propsErr
742 }
Nigel Taocd216762015-01-29 14:43:00 +1100743 if closeErr != nil {
Nigel Tao1cd7b712015-02-04 12:23:13 +1100744 return http.StatusInternalServerError, closeErr
Nigel Taocd216762015-01-29 14:43:00 +1100745 }
746 }
747
748 if created {
749 return http.StatusCreated, nil
750 }
751 return http.StatusNoContent, nil
752}
Robert Stepanek7dbad502015-01-28 21:50:26 +0100753
Alex Brainman4977ec32015-04-21 15:24:55 +1000754// walkFS traverses filesystem fs starting at name up to depth levels.
Robert Stepanek7dbad502015-01-28 21:50:26 +0100755//
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.
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700759func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
Robert Stepanek7dbad502015-01-28 21:50:26 +0100760 // This implementation is based on Walk's code in the standard path/filepath package.
Alex Brainman4977ec32015-04-21 15:24:55 +1000761 err := walkFn(name, info, nil)
Robert Stepanek7dbad502015-01-28 21:50:26 +0100762 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.
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700776 f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
Robert Stepanek7dbad502015-01-28 21:50:26 +0100777 if err != nil {
Alex Brainman4977ec32015-04-21 15:24:55 +1000778 return walkFn(name, info, err)
Robert Stepanek7dbad502015-01-28 21:50:26 +0100779 }
780 fileInfos, err := f.Readdir(0)
781 f.Close()
782 if err != nil {
Alex Brainman4977ec32015-04-21 15:24:55 +1000783 return walkFn(name, info, err)
Robert Stepanek7dbad502015-01-28 21:50:26 +0100784 }
785
786 for _, fileInfo := range fileInfos {
Alex Brainman4977ec32015-04-21 15:24:55 +1000787 filename := path.Join(name, fileInfo.Name())
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700788 fileInfo, err := fs.Stat(ctx, filename)
Robert Stepanek7dbad502015-01-28 21:50:26 +0100789 if err != nil {
790 if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
791 return err
792 }
793 } else {
Daniel Theophanes4bb47a12016-10-31 09:36:42 -0700794 err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn)
Robert Stepanek7dbad502015-01-28 21:50:26 +0100795 if err != nil {
796 if !fileInfo.IsDir() || err != filepath.SkipDir {
797 return err
798 }
799 }
800 }
801 }
802 return nil
803}