Browse Source

webdav: add Context argument to FileSystem interface

Currently there is no way to pass request scoped information from
the handler to the FileSytem interface. This can be important
to pass credentials or timeout parameters to the FileSystem
operations. Pipe context through the request from
http.Request.Context(). With pre-go1.7 use context.Background().

Custom FileSystem implementations will need to change, but it will
only require a new argument in each of the FileSystem methods.
The change will be trivial to update to.

Fixes golang/go#17658

Change-Id: I7491faf3467ad55db793a68081e074a9b3f9624d
Reviewed-on: https://go-review.googlesource.com/32421
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Daniel Theophanes 9 years ago
parent
commit
4bb47a1098
8 changed files with 184 additions and 112 deletions
  1. 32 30
      webdav/file.go
  2. 17 0
      webdav/file_go1.6.go
  3. 16 0
      webdav/file_go1.7.go
  4. 48 33
      webdav/file_test.go
  5. 21 19
      webdav/prop.go
  6. 11 8
      webdav/prop_test.go
  7. 32 19
      webdav/webdav.go
  8. 7 3
      webdav/webdav_test.go

+ 32 - 30
webdav/file.go

@@ -14,6 +14,8 @@ import (
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
+
+	"golang.org/x/net/context"
 )
 )
 
 
 // slashClean is equivalent to but slightly more efficient than
 // slashClean is equivalent to but slightly more efficient than
@@ -36,11 +38,11 @@ func slashClean(name string) string {
 // might apply". In particular, whether or not renaming a file or directory
 // might apply". In particular, whether or not renaming a file or directory
 // overwriting another existing file or directory is an error is OS-dependent.
 // overwriting another existing file or directory is an error is OS-dependent.
 type FileSystem interface {
 type FileSystem interface {
-	Mkdir(name string, perm os.FileMode) error
-	OpenFile(name string, flag int, perm os.FileMode) (File, error)
-	RemoveAll(name string) error
-	Rename(oldName, newName string) error
-	Stat(name string) (os.FileInfo, error)
+	Mkdir(ctx context.Context, name string, perm os.FileMode) error
+	OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error)
+	RemoveAll(ctx context.Context, name string) error
+	Rename(ctx context.Context, oldName, newName string) error
+	Stat(ctx context.Context, name string) (os.FileInfo, error)
 }
 }
 
 
 // A File is returned by a FileSystem's OpenFile method and can be served by a
 // A File is returned by a FileSystem's OpenFile method and can be served by a
@@ -76,14 +78,14 @@ func (d Dir) resolve(name string) string {
 	return filepath.Join(dir, filepath.FromSlash(slashClean(name)))
 	return filepath.Join(dir, filepath.FromSlash(slashClean(name)))
 }
 }
 
 
-func (d Dir) Mkdir(name string, perm os.FileMode) error {
+func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
 	if name = d.resolve(name); name == "" {
 	if name = d.resolve(name); name == "" {
 		return os.ErrNotExist
 		return os.ErrNotExist
 	}
 	}
 	return os.Mkdir(name, perm)
 	return os.Mkdir(name, perm)
 }
 }
 
 
-func (d Dir) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
+func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
 	if name = d.resolve(name); name == "" {
 	if name = d.resolve(name); name == "" {
 		return nil, os.ErrNotExist
 		return nil, os.ErrNotExist
 	}
 	}
@@ -94,7 +96,7 @@ func (d Dir) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
 	return f, nil
 	return f, nil
 }
 }
 
 
-func (d Dir) RemoveAll(name string) error {
+func (d Dir) RemoveAll(ctx context.Context, name string) error {
 	if name = d.resolve(name); name == "" {
 	if name = d.resolve(name); name == "" {
 		return os.ErrNotExist
 		return os.ErrNotExist
 	}
 	}
@@ -105,7 +107,7 @@ func (d Dir) RemoveAll(name string) error {
 	return os.RemoveAll(name)
 	return os.RemoveAll(name)
 }
 }
 
 
-func (d Dir) Rename(oldName, newName string) error {
+func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
 	if oldName = d.resolve(oldName); oldName == "" {
 	if oldName = d.resolve(oldName); oldName == "" {
 		return os.ErrNotExist
 		return os.ErrNotExist
 	}
 	}
@@ -119,7 +121,7 @@ func (d Dir) Rename(oldName, newName string) error {
 	return os.Rename(oldName, newName)
 	return os.Rename(oldName, newName)
 }
 }
 
 
-func (d Dir) Stat(name string) (os.FileInfo, error) {
+func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
 	if name = d.resolve(name); name == "" {
 	if name = d.resolve(name); name == "" {
 		return nil, os.ErrNotExist
 		return nil, os.ErrNotExist
 	}
 	}
@@ -237,7 +239,7 @@ func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err
 	return parent, frag, err
 	return parent, frag, err
 }
 }
 
 
-func (fs *memFS) Mkdir(name string, perm os.FileMode) error {
+func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
 	fs.mu.Lock()
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 	defer fs.mu.Unlock()
 
 
@@ -260,7 +262,7 @@ func (fs *memFS) Mkdir(name string, perm os.FileMode) error {
 	return nil
 	return nil
 }
 }
 
 
-func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
+func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
 	fs.mu.Lock()
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 	defer fs.mu.Unlock()
 
 
@@ -314,7 +316,7 @@ func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error)
 	}, nil
 	}, nil
 }
 }
 
 
-func (fs *memFS) RemoveAll(name string) error {
+func (fs *memFS) RemoveAll(ctx context.Context, name string) error {
 	fs.mu.Lock()
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 	defer fs.mu.Unlock()
 
 
@@ -330,7 +332,7 @@ func (fs *memFS) RemoveAll(name string) error {
 	return nil
 	return nil
 }
 }
 
 
-func (fs *memFS) Rename(oldName, newName string) error {
+func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
 	fs.mu.Lock()
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 	defer fs.mu.Unlock()
 
 
@@ -381,7 +383,7 @@ func (fs *memFS) Rename(oldName, newName string) error {
 	return nil
 	return nil
 }
 }
 
 
-func (fs *memFS) Stat(name string) (os.FileInfo, error) {
+func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
 	fs.mu.Lock()
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 	defer fs.mu.Unlock()
 
 
@@ -599,9 +601,9 @@ func (f *memFile) Write(p []byte) (int, error) {
 // moveFiles moves files and/or directories from src to dst.
 // moveFiles moves files and/or directories from src to dst.
 //
 //
 // See section 9.9.4 for when various HTTP status codes apply.
 // See section 9.9.4 for when various HTTP status codes apply.
-func moveFiles(fs FileSystem, src, dst string, overwrite bool) (status int, err error) {
+func moveFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool) (status int, err error) {
 	created := false
 	created := false
-	if _, err := fs.Stat(dst); err != nil {
+	if _, err := fs.Stat(ctx, dst); err != nil {
 		if !os.IsNotExist(err) {
 		if !os.IsNotExist(err) {
 			return http.StatusForbidden, err
 			return http.StatusForbidden, err
 		}
 		}
@@ -611,13 +613,13 @@ func moveFiles(fs FileSystem, src, dst string, overwrite bool) (status int, err
 		// and the Overwrite header is "T", then prior to performing the move,
 		// and the Overwrite header is "T", then prior to performing the move,
 		// the server must perform a DELETE with "Depth: infinity" on the
 		// the server must perform a DELETE with "Depth: infinity" on the
 		// destination resource.
 		// destination resource.
-		if err := fs.RemoveAll(dst); err != nil {
+		if err := fs.RemoveAll(ctx, dst); err != nil {
 			return http.StatusForbidden, err
 			return http.StatusForbidden, err
 		}
 		}
 	} else {
 	} else {
 		return http.StatusPreconditionFailed, os.ErrExist
 		return http.StatusPreconditionFailed, os.ErrExist
 	}
 	}
-	if err := fs.Rename(src, dst); err != nil {
+	if err := fs.Rename(ctx, src, dst); err != nil {
 		return http.StatusForbidden, err
 		return http.StatusForbidden, err
 	}
 	}
 	if created {
 	if created {
@@ -650,7 +652,7 @@ func copyProps(dst, src File) error {
 // copyFiles copies files and/or directories from src to dst.
 // copyFiles copies files and/or directories from src to dst.
 //
 //
 // See section 9.8.5 for when various HTTP status codes apply.
 // See section 9.8.5 for when various HTTP status codes apply.
-func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
+func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
 	if recursion == 1000 {
 	if recursion == 1000 {
 		return http.StatusInternalServerError, errRecursionTooDeep
 		return http.StatusInternalServerError, errRecursionTooDeep
 	}
 	}
@@ -659,7 +661,7 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 	// TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/
 	// TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/
 	// into /A/B/ could lead to infinite recursion if not handled correctly."
 	// into /A/B/ could lead to infinite recursion if not handled correctly."
 
 
-	srcFile, err := fs.OpenFile(src, os.O_RDONLY, 0)
+	srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0)
 	if err != nil {
 	if err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return http.StatusNotFound, err
 			return http.StatusNotFound, err
@@ -677,7 +679,7 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 	srcPerm := srcStat.Mode() & os.ModePerm
 	srcPerm := srcStat.Mode() & os.ModePerm
 
 
 	created := false
 	created := false
-	if _, err := fs.Stat(dst); err != nil {
+	if _, err := fs.Stat(ctx, dst); err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			created = true
 			created = true
 		} else {
 		} else {
@@ -687,13 +689,13 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 		if !overwrite {
 		if !overwrite {
 			return http.StatusPreconditionFailed, os.ErrExist
 			return http.StatusPreconditionFailed, os.ErrExist
 		}
 		}
-		if err := fs.RemoveAll(dst); err != nil && !os.IsNotExist(err) {
+		if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) {
 			return http.StatusForbidden, err
 			return http.StatusForbidden, err
 		}
 		}
 	}
 	}
 
 
 	if srcStat.IsDir() {
 	if srcStat.IsDir() {
-		if err := fs.Mkdir(dst, srcPerm); err != nil {
+		if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
 			return http.StatusForbidden, err
 			return http.StatusForbidden, err
 		}
 		}
 		if depth == infiniteDepth {
 		if depth == infiniteDepth {
@@ -705,7 +707,7 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 				name := c.Name()
 				name := c.Name()
 				s := path.Join(src, name)
 				s := path.Join(src, name)
 				d := path.Join(dst, name)
 				d := path.Join(dst, name)
-				cStatus, cErr := copyFiles(fs, s, d, overwrite, depth, recursion)
+				cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion)
 				if cErr != nil {
 				if cErr != nil {
 					// TODO: MultiStatus.
 					// TODO: MultiStatus.
 					return cStatus, cErr
 					return cStatus, cErr
@@ -714,7 +716,7 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 		}
 		}
 
 
 	} else {
 	} else {
-		dstFile, err := fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm)
+		dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm)
 		if err != nil {
 		if err != nil {
 			if os.IsNotExist(err) {
 			if os.IsNotExist(err) {
 				return http.StatusConflict, err
 				return http.StatusConflict, err
@@ -747,7 +749,7 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
 // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
 // walkFS calls walkFn. If a visited file system node is a directory and
 // walkFS calls walkFn. If a visited file system node is a directory and
 // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
 // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
-func walkFS(fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
+func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
 	// This implementation is based on Walk's code in the standard path/filepath package.
 	// This implementation is based on Walk's code in the standard path/filepath package.
 	err := walkFn(name, info, nil)
 	err := walkFn(name, info, nil)
 	if err != nil {
 	if err != nil {
@@ -764,7 +766,7 @@ func walkFS(fs FileSystem, depth int, name string, info os.FileInfo, walkFn file
 	}
 	}
 
 
 	// Read directory names.
 	// Read directory names.
-	f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+	f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
 	if err != nil {
 	if err != nil {
 		return walkFn(name, info, err)
 		return walkFn(name, info, err)
 	}
 	}
@@ -776,13 +778,13 @@ func walkFS(fs FileSystem, depth int, name string, info os.FileInfo, walkFn file
 
 
 	for _, fileInfo := range fileInfos {
 	for _, fileInfo := range fileInfos {
 		filename := path.Join(name, fileInfo.Name())
 		filename := path.Join(name, fileInfo.Name())
-		fileInfo, err := fs.Stat(filename)
+		fileInfo, err := fs.Stat(ctx, filename)
 		if err != nil {
 		if err != nil {
 			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
 			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
 				return err
 				return err
 			}
 			}
 		} else {
 		} else {
-			err = walkFS(fs, depth, filename, fileInfo, walkFn)
+			err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn)
 			if err != nil {
 			if err != nil {
 				if !fileInfo.IsDir() || err != filepath.SkipDir {
 				if !fileInfo.IsDir() || err != filepath.SkipDir {
 					return err
 					return err

+ 17 - 0
webdav/file_go1.6.go

@@ -0,0 +1,17 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.7
+
+package webdav
+
+import (
+	"net/http"
+
+	"golang.org/x/net/context"
+)
+
+func getContext(r *http.Request) context.Context {
+	return context.Background()
+}

+ 16 - 0
webdav/file_go1.7.go

@@ -0,0 +1,16 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.7
+
+package webdav
+
+import (
+	"context"
+	"net/http"
+)
+
+func getContext(r *http.Request) context.Context {
+	return r.Context()
+}

+ 48 - 33
webdav/file_test.go

@@ -18,6 +18,8 @@ import (
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 )
 
 
 func TestSlashClean(t *testing.T) {
 func TestSlashClean(t *testing.T) {
@@ -195,13 +197,15 @@ func TestWalk(t *testing.T) {
 		}},
 		}},
 	}
 	}
 
 
+	ctx := context.Background()
+
 	for _, tc := range testCases {
 	for _, tc := range testCases {
 		fs := NewMemFS().(*memFS)
 		fs := NewMemFS().(*memFS)
 
 
 		parts := strings.Split(tc.dir, "/")
 		parts := strings.Split(tc.dir, "/")
 		for p := 2; p < len(parts); p++ {
 		for p := 2; p < len(parts); p++ {
 			d := strings.Join(parts[:p], "/")
 			d := strings.Join(parts[:p], "/")
-			if err := fs.Mkdir(d, 0666); err != nil {
+			if err := fs.Mkdir(ctx, d, 0666); err != nil {
 				t.Errorf("tc.dir=%q: mkdir: %q: %v", tc.dir, d, err)
 				t.Errorf("tc.dir=%q: mkdir: %q: %v", tc.dir, d, err)
 			}
 			}
 		}
 		}
@@ -231,14 +235,14 @@ func TestWalk(t *testing.T) {
 // analogous to the Unix find command.
 // analogous to the Unix find command.
 //
 //
 // The returned strings are not guaranteed to be in any particular order.
 // The returned strings are not guaranteed to be in any particular order.
-func find(ss []string, fs FileSystem, name string) ([]string, error) {
-	stat, err := fs.Stat(name)
+func find(ctx context.Context, ss []string, fs FileSystem, name string) ([]string, error) {
+	stat, err := fs.Stat(ctx, name)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	ss = append(ss, name)
 	ss = append(ss, name)
 	if stat.IsDir() {
 	if stat.IsDir() {
-		f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+		f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -248,7 +252,7 @@ func find(ss []string, fs FileSystem, name string) ([]string, error) {
 			return nil, err
 			return nil, err
 		}
 		}
 		for _, c := range children {
 		for _, c := range children {
-			ss, err = find(ss, fs, path.Join(name, c.Name()))
+			ss, err = find(ctx, ss, fs, path.Join(name, c.Name()))
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -403,6 +407,8 @@ func testFS(t *testing.T, fs FileSystem) {
 		"copy__ o=F d=∞ /d/y /d/x want errExist",
 		"copy__ o=F d=∞ /d/y /d/x want errExist",
 	}
 	}
 
 
+	ctx := context.Background()
+
 	for i, tc := range testCases {
 	for i, tc := range testCases {
 		tc = strings.TrimSpace(tc)
 		tc = strings.TrimSpace(tc)
 		j := strings.IndexByte(tc, ' ')
 		j := strings.IndexByte(tc, ' ')
@@ -420,7 +426,7 @@ func testFS(t *testing.T, fs FileSystem) {
 			if len(parts) != 4 || parts[2] != "want" {
 			if len(parts) != 4 || parts[2] != "want" {
 				t.Fatalf("test case #%d %q: invalid write", i, tc)
 				t.Fatalf("test case #%d %q: invalid write", i, tc)
 			}
 			}
-			f, opErr := fs.OpenFile(parts[0], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			f, opErr := fs.OpenFile(ctx, parts[0], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 			if got := errStr(opErr); got != parts[3] {
 			if got := errStr(opErr); got != parts[3] {
 				t.Fatalf("test case #%d %q: OpenFile: got %q (%v), want %q", i, tc, got, opErr, parts[3])
 				t.Fatalf("test case #%d %q: OpenFile: got %q (%v), want %q", i, tc, got, opErr, parts[3])
 			}
 			}
@@ -434,7 +440,7 @@ func testFS(t *testing.T, fs FileSystem) {
 			}
 			}
 
 
 		case "find":
 		case "find":
-			got, err := find(nil, fs, "/")
+			got, err := find(ctx, nil, fs, "/")
 			if err != nil {
 			if err != nil {
 				t.Fatalf("test case #%d %q: find: %v", i, tc, err)
 				t.Fatalf("test case #%d %q: find: %v", i, tc, err)
 			}
 			}
@@ -464,17 +470,17 @@ func testFS(t *testing.T, fs FileSystem) {
 				if parts[1] == "d=∞" {
 				if parts[1] == "d=∞" {
 					depth = infiniteDepth
 					depth = infiniteDepth
 				}
 				}
-				_, opErr = copyFiles(fs, parts[2], parts[3], parts[0] == "o=T", depth, 0)
+				_, opErr = copyFiles(ctx, fs, parts[2], parts[3], parts[0] == "o=T", depth, 0)
 			case "mk-dir":
 			case "mk-dir":
-				opErr = fs.Mkdir(parts[0], 0777)
+				opErr = fs.Mkdir(ctx, parts[0], 0777)
 			case "move__":
 			case "move__":
-				_, opErr = moveFiles(fs, parts[1], parts[2], parts[0] == "o=T")
+				_, opErr = moveFiles(ctx, fs, parts[1], parts[2], parts[0] == "o=T")
 			case "rm-all":
 			case "rm-all":
-				opErr = fs.RemoveAll(parts[0])
+				opErr = fs.RemoveAll(ctx, parts[0])
 			case "stat":
 			case "stat":
 				var stat os.FileInfo
 				var stat os.FileInfo
 				fileName := parts[0]
 				fileName := parts[0]
-				if stat, opErr = fs.Stat(fileName); opErr == nil {
+				if stat, opErr = fs.Stat(ctx, fileName); opErr == nil {
 					if stat.IsDir() {
 					if stat.IsDir() {
 						got = "dir"
 						got = "dir"
 					} else {
 					} else {
@@ -526,9 +532,10 @@ func TestMemFS(t *testing.T) {
 }
 }
 
 
 func TestMemFSRoot(t *testing.T) {
 func TestMemFSRoot(t *testing.T) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
 	for i := 0; i < 5; i++ {
 	for i := 0; i < 5; i++ {
-		stat, err := fs.Stat("/")
+		stat, err := fs.Stat(ctx, "/")
 		if err != nil {
 		if err != nil {
 			t.Fatalf("i=%d: Stat: %v", i, err)
 			t.Fatalf("i=%d: Stat: %v", i, err)
 		}
 		}
@@ -536,7 +543,7 @@ func TestMemFSRoot(t *testing.T) {
 			t.Fatalf("i=%d: Stat.IsDir is false, want true", i)
 			t.Fatalf("i=%d: Stat.IsDir is false, want true", i)
 		}
 		}
 
 
-		f, err := fs.OpenFile("/", os.O_RDONLY, 0)
+		f, err := fs.OpenFile(ctx, "/", os.O_RDONLY, 0)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("i=%d: OpenFile: %v", i, err)
 			t.Fatalf("i=%d: OpenFile: %v", i, err)
 		}
 		}
@@ -553,19 +560,20 @@ func TestMemFSRoot(t *testing.T) {
 			t.Fatalf("i=%d: Write: got nil error, want non-nil", i)
 			t.Fatalf("i=%d: Write: got nil error, want non-nil", i)
 		}
 		}
 
 
-		if err := fs.Mkdir(fmt.Sprintf("/dir%d", i), 0777); err != nil {
+		if err := fs.Mkdir(ctx, fmt.Sprintf("/dir%d", i), 0777); err != nil {
 			t.Fatalf("i=%d: Mkdir: %v", i, err)
 			t.Fatalf("i=%d: Mkdir: %v", i, err)
 		}
 		}
 	}
 	}
 }
 }
 
 
 func TestMemFileReaddir(t *testing.T) {
 func TestMemFileReaddir(t *testing.T) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
-	if err := fs.Mkdir("/foo", 0777); err != nil {
+	if err := fs.Mkdir(ctx, "/foo", 0777); err != nil {
 		t.Fatalf("Mkdir: %v", err)
 		t.Fatalf("Mkdir: %v", err)
 	}
 	}
 	readdir := func(count int) ([]os.FileInfo, error) {
 	readdir := func(count int) ([]os.FileInfo, error) {
-		f, err := fs.OpenFile("/foo", os.O_RDONLY, 0)
+		f, err := fs.OpenFile(ctx, "/foo", os.O_RDONLY, 0)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("OpenFile: %v", err)
 			t.Fatalf("OpenFile: %v", err)
 		}
 		}
@@ -649,9 +657,11 @@ func TestMemFile(t *testing.T) {
 		"seek cur -99 want err",
 		"seek cur -99 want err",
 	}
 	}
 
 
+	ctx := context.Background()
+
 	const filename = "/foo"
 	const filename = "/foo"
 	fs := NewMemFS()
 	fs := NewMemFS()
-	f, err := fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+	f, err := fs.OpenFile(ctx, filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("OpenFile: %v", err)
 		t.Fatalf("OpenFile: %v", err)
 	}
 	}
@@ -745,7 +755,7 @@ func TestMemFile(t *testing.T) {
 			}
 			}
 
 
 		case "wantData":
 		case "wantData":
-			g, err := fs.OpenFile(filename, os.O_RDONLY, 0666)
+			g, err := fs.OpenFile(ctx, filename, os.O_RDONLY, 0666)
 			if err != nil {
 			if err != nil {
 				t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err)
 				t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err)
 			}
 			}
@@ -771,7 +781,7 @@ func TestMemFile(t *testing.T) {
 			if err != nil {
 			if err != nil {
 				t.Fatalf("test case #%d %q: invalid size %q", i, tc, arg)
 				t.Fatalf("test case #%d %q: invalid size %q", i, tc, arg)
 			}
 			}
-			fi, err := fs.Stat(filename)
+			fi, err := fs.Stat(ctx, filename)
 			if err != nil {
 			if err != nil {
 				t.Fatalf("test case #%d %q: Stat: %v", i, tc, err)
 				t.Fatalf("test case #%d %q: Stat: %v", i, tc, err)
 			}
 			}
@@ -789,8 +799,9 @@ func TestMemFileWriteAllocs(t *testing.T) {
 	if runtime.Compiler == "gccgo" {
 	if runtime.Compiler == "gccgo" {
 		t.Skip("gccgo allocates here")
 		t.Skip("gccgo allocates here")
 	}
 	}
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
-	f, err := fs.OpenFile("/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+	f, err := fs.OpenFile(ctx, "/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("OpenFile: %v", err)
 		t.Fatalf("OpenFile: %v", err)
 	}
 	}
@@ -812,6 +823,7 @@ func TestMemFileWriteAllocs(t *testing.T) {
 }
 }
 
 
 func BenchmarkMemFileWrite(b *testing.B) {
 func BenchmarkMemFileWrite(b *testing.B) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
 	xxx := make([]byte, 1024)
 	xxx := make([]byte, 1024)
 	for i := range xxx {
 	for i := range xxx {
@@ -820,7 +832,7 @@ func BenchmarkMemFileWrite(b *testing.B) {
 
 
 	b.ResetTimer()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 	for i := 0; i < b.N; i++ {
-		f, err := fs.OpenFile("/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+		f, err := fs.OpenFile(ctx, "/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 		if err != nil {
 		if err != nil {
 			b.Fatalf("OpenFile: %v", err)
 			b.Fatalf("OpenFile: %v", err)
 		}
 		}
@@ -830,16 +842,17 @@ func BenchmarkMemFileWrite(b *testing.B) {
 		if err := f.Close(); err != nil {
 		if err := f.Close(); err != nil {
 			b.Fatalf("Close: %v", err)
 			b.Fatalf("Close: %v", err)
 		}
 		}
-		if err := fs.RemoveAll("/xxx"); err != nil {
+		if err := fs.RemoveAll(ctx, "/xxx"); err != nil {
 			b.Fatalf("RemoveAll: %v", err)
 			b.Fatalf("RemoveAll: %v", err)
 		}
 		}
 	}
 	}
 }
 }
 
 
 func TestCopyMoveProps(t *testing.T) {
 func TestCopyMoveProps(t *testing.T) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
 	create := func(name string) error {
 	create := func(name string) error {
-		f, err := fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+		f, err := fs.OpenFile(ctx, name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -851,7 +864,7 @@ func TestCopyMoveProps(t *testing.T) {
 		return cErr
 		return cErr
 	}
 	}
 	patch := func(name string, patches ...Proppatch) error {
 	patch := func(name string, patches ...Proppatch) error {
-		f, err := fs.OpenFile(name, os.O_RDWR, 0666)
+		f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0666)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -863,7 +876,7 @@ func TestCopyMoveProps(t *testing.T) {
 		return cErr
 		return cErr
 	}
 	}
 	props := func(name string) (map[xml.Name]Property, error) {
 	props := func(name string) (map[xml.Name]Property, error) {
-		f, err := fs.OpenFile(name, os.O_RDWR, 0666)
+		f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0666)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -901,10 +914,10 @@ func TestCopyMoveProps(t *testing.T) {
 	if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
 	if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
 		t.Fatalf("patch /src +p0 +p1: %v", err)
 		t.Fatalf("patch /src +p0 +p1: %v", err)
 	}
 	}
-	if _, err := copyFiles(fs, "/src", "/tmp", true, infiniteDepth, 0); err != nil {
+	if _, err := copyFiles(ctx, fs, "/src", "/tmp", true, infiniteDepth, 0); err != nil {
 		t.Fatalf("copyFiles /src /tmp: %v", err)
 		t.Fatalf("copyFiles /src /tmp: %v", err)
 	}
 	}
-	if _, err := moveFiles(fs, "/tmp", "/dst", true); err != nil {
+	if _, err := moveFiles(ctx, fs, "/tmp", "/dst", true); err != nil {
 		t.Fatalf("moveFiles /tmp /dst: %v", err)
 		t.Fatalf("moveFiles /tmp /dst: %v", err)
 	}
 	}
 	if err := patch("/src", Proppatch{Props: []Property{p0}, Remove: true}); err != nil {
 	if err := patch("/src", Proppatch{Props: []Property{p0}, Remove: true}); err != nil {
@@ -1099,6 +1112,7 @@ func TestWalkFS(t *testing.T) {
 			"/a/b/z",
 			"/a/b/z",
 		},
 		},
 	}}
 	}}
+	ctx := context.Background()
 	for _, tc := range testCases {
 	for _, tc := range testCases {
 		fs, err := buildTestFS(tc.buildfs)
 		fs, err := buildTestFS(tc.buildfs)
 		if err != nil {
 		if err != nil {
@@ -1115,11 +1129,11 @@ func TestWalkFS(t *testing.T) {
 			got = append(got, path)
 			got = append(got, path)
 			return nil
 			return nil
 		}
 		}
-		fi, err := fs.Stat(tc.startAt)
+		fi, err := fs.Stat(ctx, tc.startAt)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("%s: cannot stat: %v", tc.desc, err)
 			t.Fatalf("%s: cannot stat: %v", tc.desc, err)
 		}
 		}
-		err = walkFS(fs, tc.depth, tc.startAt, fi, traceFn)
+		err = walkFS(ctx, fs, tc.depth, tc.startAt, fi, traceFn)
 		if err != nil {
 		if err != nil {
 			t.Errorf("%s:\ngot error %v, want nil", tc.desc, err)
 			t.Errorf("%s:\ngot error %v, want nil", tc.desc, err)
 			continue
 			continue
@@ -1136,23 +1150,24 @@ func TestWalkFS(t *testing.T) {
 func buildTestFS(buildfs []string) (FileSystem, error) {
 func buildTestFS(buildfs []string) (FileSystem, error) {
 	// TODO: Could this be merged with the build logic in TestFS?
 	// TODO: Could this be merged with the build logic in TestFS?
 
 
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
 	for _, b := range buildfs {
 	for _, b := range buildfs {
 		op := strings.Split(b, " ")
 		op := strings.Split(b, " ")
 		switch op[0] {
 		switch op[0] {
 		case "mkdir":
 		case "mkdir":
-			err := fs.Mkdir(op[1], os.ModeDir|0777)
+			err := fs.Mkdir(ctx, op[1], os.ModeDir|0777)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 		case "touch":
 		case "touch":
-			f, err := fs.OpenFile(op[1], os.O_RDWR|os.O_CREATE, 0666)
+			f, err := fs.OpenFile(ctx, op[1], os.O_RDWR|os.O_CREATE, 0666)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 			f.Close()
 			f.Close()
 		case "write":
 		case "write":
-			f, err := fs.OpenFile(op[1], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			f, err := fs.OpenFile(ctx, op[1], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}

+ 21 - 19
webdav/prop.go

@@ -14,6 +14,8 @@ import (
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"strconv"
 	"strconv"
+
+	"golang.org/x/net/context"
 )
 )
 
 
 // Proppatch describes a property update instruction as defined in RFC 4918.
 // Proppatch describes a property update instruction as defined in RFC 4918.
@@ -101,7 +103,7 @@ type DeadPropsHolder interface {
 var liveProps = map[xml.Name]struct {
 var liveProps = map[xml.Name]struct {
 	// findFn implements the propfind function of this property. If nil,
 	// findFn implements the propfind function of this property. If nil,
 	// it indicates a hidden property.
 	// it indicates a hidden property.
-	findFn func(FileSystem, LockSystem, string, os.FileInfo) (string, error)
+	findFn func(context.Context, FileSystem, LockSystem, string, os.FileInfo) (string, error)
 	// dir is true if the property applies to directories.
 	// dir is true if the property applies to directories.
 	dir bool
 	dir bool
 }{
 }{
@@ -164,8 +166,8 @@ var liveProps = map[xml.Name]struct {
 //
 //
 // Each Propstat has a unique status and each property name will only be part
 // Each Propstat has a unique status and each property name will only be part
 // of one Propstat element.
 // of one Propstat element.
-func props(fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
-	f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
+	f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -194,7 +196,7 @@ func props(fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Prop
 		}
 		}
 		// Otherwise, it must either be a live property or we don't know it.
 		// Otherwise, it must either be a live property or we don't know it.
 		if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
 		if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
-			innerXML, err := prop.findFn(fs, ls, name, fi)
+			innerXML, err := prop.findFn(ctx, fs, ls, name, fi)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -212,8 +214,8 @@ func props(fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Prop
 }
 }
 
 
 // Propnames returns the property names defined for resource name.
 // Propnames returns the property names defined for resource name.
-func propnames(fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
-	f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
+	f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -252,8 +254,8 @@ func propnames(fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
 // returned if they are named in 'include'.
 // returned if they are named in 'include'.
 //
 //
 // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
 // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
-func allprop(fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
-	pnames, err := propnames(fs, ls, name)
+func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
+	pnames, err := propnames(ctx, fs, ls, name)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -267,12 +269,12 @@ func allprop(fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]P
 			pnames = append(pnames, pn)
 			pnames = append(pnames, pn)
 		}
 		}
 	}
 	}
-	return props(fs, ls, name, pnames)
+	return props(ctx, fs, ls, name, pnames)
 }
 }
 
 
 // Patch patches the properties of resource name. The return values are
 // Patch patches the properties of resource name. The return values are
 // constrained in the same manner as DeadPropsHolder.Patch.
 // constrained in the same manner as DeadPropsHolder.Patch.
-func patch(fs FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
+func patch(ctx context.Context, fs FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
 	conflict := false
 	conflict := false
 loop:
 loop:
 	for _, patch := range patches {
 	for _, patch := range patches {
@@ -303,7 +305,7 @@ loop:
 		return makePropstats(pstatForbidden, pstatFailedDep), nil
 		return makePropstats(pstatForbidden, pstatFailedDep), nil
 	}
 	}
 
 
-	f, err := fs.OpenFile(name, os.O_RDWR, 0)
+	f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -354,14 +356,14 @@ func escapeXML(s string) string {
 	return s
 	return s
 }
 }
 
 
-func findResourceType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+func findResourceType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	if fi.IsDir() {
 	if fi.IsDir() {
 		return `<D:collection xmlns:D="DAV:"/>`, nil
 		return `<D:collection xmlns:D="DAV:"/>`, nil
 	}
 	}
 	return "", nil
 	return "", nil
 }
 }
 
 
-func findDisplayName(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+func findDisplayName(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	if slashClean(name) == "/" {
 	if slashClean(name) == "/" {
 		// Hide the real name of a possibly prefixed root directory.
 		// Hide the real name of a possibly prefixed root directory.
 		return "", nil
 		return "", nil
@@ -369,16 +371,16 @@ func findDisplayName(fs FileSystem, ls LockSystem, name string, fi os.FileInfo)
 	return escapeXML(fi.Name()), nil
 	return escapeXML(fi.Name()), nil
 }
 }
 
 
-func findContentLength(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+func findContentLength(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	return strconv.FormatInt(fi.Size(), 10), nil
 	return strconv.FormatInt(fi.Size(), 10), nil
 }
 }
 
 
-func findLastModified(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+func findLastModified(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	return fi.ModTime().Format(http.TimeFormat), nil
 	return fi.ModTime().Format(http.TimeFormat), nil
 }
 }
 
 
-func findContentType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
-	f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+func findContentType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+	f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -400,14 +402,14 @@ func findContentType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo)
 	return ctype, err
 	return ctype, err
 }
 }
 
 
-func findETag(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+func findETag(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	// The Apache http 2.4 web server by default concatenates the
 	// The Apache http 2.4 web server by default concatenates the
 	// modification time and size of a file. We replicate the heuristic
 	// modification time and size of a file. We replicate the heuristic
 	// with nanosecond granularity.
 	// with nanosecond granularity.
 	return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil
 	return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil
 }
 }
 
 
-func findSupportedLock(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
+func findSupportedLock(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	return `` +
 	return `` +
 		`<D:lockentry xmlns:D="DAV:">` +
 		`<D:lockentry xmlns:D="DAV:">` +
 		`<D:lockscope><D:exclusive/></D:lockscope>` +
 		`<D:lockscope><D:exclusive/></D:lockscope>` +

+ 11 - 8
webdav/prop_test.go

@@ -12,13 +12,16 @@ import (
 	"reflect"
 	"reflect"
 	"sort"
 	"sort"
 	"testing"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 )
 
 
 func TestMemPS(t *testing.T) {
 func TestMemPS(t *testing.T) {
+	ctx := context.Background()
 	// calcProps calculates the getlastmodified and getetag DAV: property
 	// calcProps calculates the getlastmodified and getetag DAV: property
 	// values in pstats for resource name in file-system fs.
 	// values in pstats for resource name in file-system fs.
 	calcProps := func(name string, fs FileSystem, ls LockSystem, pstats []Propstat) error {
 	calcProps := func(name string, fs FileSystem, ls LockSystem, pstats []Propstat) error {
-		fi, err := fs.Stat(name)
+		fi, err := fs.Stat(ctx, name)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -32,7 +35,7 @@ func TestMemPS(t *testing.T) {
 					if fi.IsDir() {
 					if fi.IsDir() {
 						continue
 						continue
 					}
 					}
-					etag, err := findETag(fs, ls, name, fi)
+					etag, err := findETag(ctx, fs, ls, name, fi)
 					if err != nil {
 					if err != nil {
 						return err
 						return err
 					}
 					}
@@ -519,7 +522,7 @@ func TestMemPS(t *testing.T) {
 			var propstats []Propstat
 			var propstats []Propstat
 			switch op.op {
 			switch op.op {
 			case "propname":
 			case "propname":
-				pnames, err := propnames(fs, ls, op.name)
+				pnames, err := propnames(ctx, fs, ls, op.name)
 				if err != nil {
 				if err != nil {
 					t.Errorf("%s: got error %v, want nil", desc, err)
 					t.Errorf("%s: got error %v, want nil", desc, err)
 					continue
 					continue
@@ -531,11 +534,11 @@ func TestMemPS(t *testing.T) {
 				}
 				}
 				continue
 				continue
 			case "allprop":
 			case "allprop":
-				propstats, err = allprop(fs, ls, op.name, op.pnames)
+				propstats, err = allprop(ctx, fs, ls, op.name, op.pnames)
 			case "propfind":
 			case "propfind":
-				propstats, err = props(fs, ls, op.name, op.pnames)
+				propstats, err = props(ctx, fs, ls, op.name, op.pnames)
 			case "proppatch":
 			case "proppatch":
-				propstats, err = patch(fs, ls, op.name, op.patches)
+				propstats, err = patch(ctx, fs, ls, op.name, op.patches)
 			default:
 			default:
 				t.Fatalf("%s: %s not implemented", desc, op.op)
 				t.Fatalf("%s: %s not implemented", desc, op.op)
 			}
 			}
@@ -588,8 +591,8 @@ type noDeadPropsFS struct {
 	FileSystem
 	FileSystem
 }
 }
 
 
-func (fs noDeadPropsFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
-	f, err := fs.FileSystem.OpenFile(name, flag, perm)
+func (fs noDeadPropsFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
+	f, err := fs.FileSystem.OpenFile(ctx, name, flag, perm)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 32 - 19
webdav/webdav.go

@@ -174,8 +174,9 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
 	if err != nil {
 	if err != nil {
 		return status, err
 		return status, err
 	}
 	}
+	ctx := getContext(r)
 	allow := "OPTIONS, LOCK, PUT, MKCOL"
 	allow := "OPTIONS, LOCK, PUT, MKCOL"
-	if fi, err := h.FileSystem.Stat(reqPath); err == nil {
+	if fi, err := h.FileSystem.Stat(ctx, reqPath); err == nil {
 		if fi.IsDir() {
 		if fi.IsDir() {
 			allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
 			allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
 		} else {
 		} else {
@@ -196,7 +197,8 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
 		return status, err
 		return status, err
 	}
 	}
 	// TODO: check locks for read-only access??
 	// TODO: check locks for read-only access??
-	f, err := h.FileSystem.OpenFile(reqPath, os.O_RDONLY, 0)
+	ctx := getContext(r)
+	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDONLY, 0)
 	if err != nil {
 	if err != nil {
 		return http.StatusNotFound, err
 		return http.StatusNotFound, err
 	}
 	}
@@ -208,7 +210,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
 	if fi.IsDir() {
 	if fi.IsDir() {
 		return http.StatusMethodNotAllowed, nil
 		return http.StatusMethodNotAllowed, nil
 	}
 	}
-	etag, err := findETag(h.FileSystem, h.LockSystem, reqPath, fi)
+	etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
 	if err != nil {
 	if err != nil {
 		return http.StatusInternalServerError, err
 		return http.StatusInternalServerError, err
 	}
 	}
@@ -229,18 +231,20 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
 	}
 	}
 	defer release()
 	defer release()
 
 
+	ctx := getContext(r)
+
 	// TODO: return MultiStatus where appropriate.
 	// TODO: return MultiStatus where appropriate.
 
 
 	// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
 	// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
 	// returns nil (no error)." WebDAV semantics are that it should return a
 	// returns nil (no error)." WebDAV semantics are that it should return a
 	// "404 Not Found". We therefore have to Stat before we RemoveAll.
 	// "404 Not Found". We therefore have to Stat before we RemoveAll.
-	if _, err := h.FileSystem.Stat(reqPath); err != nil {
+	if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return http.StatusNotFound, err
 			return http.StatusNotFound, err
 		}
 		}
 		return http.StatusMethodNotAllowed, err
 		return http.StatusMethodNotAllowed, err
 	}
 	}
-	if err := h.FileSystem.RemoveAll(reqPath); err != nil {
+	if err := h.FileSystem.RemoveAll(ctx, reqPath); err != nil {
 		return http.StatusMethodNotAllowed, err
 		return http.StatusMethodNotAllowed, err
 	}
 	}
 	return http.StatusNoContent, nil
 	return http.StatusNoContent, nil
@@ -258,8 +262,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
 	defer release()
 	defer release()
 	// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
 	// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
 	// comments in http.checkEtag.
 	// comments in http.checkEtag.
+	ctx := getContext(r)
 
 
-	f, err := h.FileSystem.OpenFile(reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 	if err != nil {
 	if err != nil {
 		return http.StatusNotFound, err
 		return http.StatusNotFound, err
 	}
 	}
@@ -276,7 +281,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
 	if closeErr != nil {
 	if closeErr != nil {
 		return http.StatusMethodNotAllowed, closeErr
 		return http.StatusMethodNotAllowed, closeErr
 	}
 	}
-	etag, err := findETag(h.FileSystem, h.LockSystem, reqPath, fi)
+	etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
 	if err != nil {
 	if err != nil {
 		return http.StatusInternalServerError, err
 		return http.StatusInternalServerError, err
 	}
 	}
@@ -295,10 +300,12 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
 	}
 	}
 	defer release()
 	defer release()
 
 
+	ctx := getContext(r)
+
 	if r.ContentLength > 0 {
 	if r.ContentLength > 0 {
 		return http.StatusUnsupportedMediaType, nil
 		return http.StatusUnsupportedMediaType, nil
 	}
 	}
-	if err := h.FileSystem.Mkdir(reqPath, 0777); err != nil {
+	if err := h.FileSystem.Mkdir(ctx, reqPath, 0777); err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return http.StatusConflict, err
 			return http.StatusConflict, err
 		}
 		}
@@ -337,6 +344,8 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 		return http.StatusForbidden, errDestinationEqualsSource
 		return http.StatusForbidden, errDestinationEqualsSource
 	}
 	}
 
 
+	ctx := getContext(r)
+
 	if r.Method == "COPY" {
 	if r.Method == "COPY" {
 		// Section 7.5.1 says that a COPY only needs to lock the destination,
 		// Section 7.5.1 says that a COPY only needs to lock the destination,
 		// not both destination and source. Strictly speaking, this is racy,
 		// not both destination and source. Strictly speaking, this is racy,
@@ -360,7 +369,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 				return http.StatusBadRequest, errInvalidDepth
 				return http.StatusBadRequest, errInvalidDepth
 			}
 			}
 		}
 		}
-		return copyFiles(h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
+		return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
 	}
 	}
 
 
 	release, status, err := h.confirmLocks(r, src, dst)
 	release, status, err := h.confirmLocks(r, src, dst)
@@ -377,7 +386,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 			return http.StatusBadRequest, errInvalidDepth
 			return http.StatusBadRequest, errInvalidDepth
 		}
 		}
 	}
 	}
-	return moveFiles(h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
+	return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
 }
 }
 
 
 func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
 func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
@@ -390,6 +399,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
 		return status, err
 		return status, err
 	}
 	}
 
 
+	ctx := getContext(r)
 	token, ld, now, created := "", LockDetails{}, time.Now(), false
 	token, ld, now, created := "", LockDetails{}, time.Now(), false
 	if li == (lockInfo{}) {
 	if li == (lockInfo{}) {
 		// An empty lockInfo means to refresh the lock.
 		// An empty lockInfo means to refresh the lock.
@@ -447,8 +457,8 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
 		}()
 		}()
 
 
 		// Create the resource if it didn't previously exist.
 		// Create the resource if it didn't previously exist.
-		if _, err := h.FileSystem.Stat(reqPath); err != nil {
-			f, err := h.FileSystem.OpenFile(reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+		if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
+			f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 			if err != nil {
 			if err != nil {
 				// TODO: detect missing intermediate dirs and return http.StatusConflict?
 				// TODO: detect missing intermediate dirs and return http.StatusConflict?
 				return http.StatusInternalServerError, err
 				return http.StatusInternalServerError, err
@@ -501,7 +511,8 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 	if err != nil {
 	if err != nil {
 		return status, err
 		return status, err
 	}
 	}
-	fi, err := h.FileSystem.Stat(reqPath)
+	ctx := getContext(r)
+	fi, err := h.FileSystem.Stat(ctx, reqPath)
 	if err != nil {
 	if err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return http.StatusNotFound, err
 			return http.StatusNotFound, err
@@ -528,7 +539,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 		}
 		}
 		var pstats []Propstat
 		var pstats []Propstat
 		if pf.Propname != nil {
 		if pf.Propname != nil {
-			pnames, err := propnames(h.FileSystem, h.LockSystem, reqPath)
+			pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
@@ -538,9 +549,9 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 			}
 			}
 			pstats = append(pstats, pstat)
 			pstats = append(pstats, pstat)
 		} else if pf.Allprop != nil {
 		} else if pf.Allprop != nil {
-			pstats, err = allprop(h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+			pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
 		} else {
 		} else {
-			pstats, err = props(h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+			pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
 		}
 		}
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -548,7 +559,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 		return mw.write(makePropstatResponse(path.Join(h.Prefix, reqPath), pstats))
 		return mw.write(makePropstatResponse(path.Join(h.Prefix, reqPath), pstats))
 	}
 	}
 
 
-	walkErr := walkFS(h.FileSystem, depth, reqPath, fi, walkFn)
+	walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
 	closeErr := mw.close()
 	closeErr := mw.close()
 	if walkErr != nil {
 	if walkErr != nil {
 		return http.StatusInternalServerError, walkErr
 		return http.StatusInternalServerError, walkErr
@@ -570,7 +581,9 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
 	}
 	}
 	defer release()
 	defer release()
 
 
-	if _, err := h.FileSystem.Stat(reqPath); err != nil {
+	ctx := getContext(r)
+
+	if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return http.StatusNotFound, err
 			return http.StatusNotFound, err
 		}
 		}
@@ -580,7 +593,7 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
 	if err != nil {
 	if err != nil {
 		return status, err
 		return status, err
 	}
 	}
-	pstats, err := patch(h.FileSystem, h.LockSystem, reqPath, patches)
+	pstats, err := patch(ctx, h.FileSystem, h.LockSystem, reqPath, patches)
 	if err != nil {
 	if err != nil {
 		return http.StatusInternalServerError, err
 		return http.StatusInternalServerError, err
 	}
 	}

+ 7 - 3
webdav/webdav_test.go

@@ -18,6 +18,8 @@ import (
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 )
 
 
 // TODO: add tests to check XML responses with the expected prefix path
 // TODO: add tests to check XML responses with the expected prefix path
@@ -65,6 +67,7 @@ func TestPrefix(t *testing.T) {
 		"/a/b/",
 		"/a/b/",
 		"/a/b/c/",
 		"/a/b/c/",
 	}
 	}
+	ctx := context.Background()
 	for _, prefix := range prefixes {
 	for _, prefix := range prefixes {
 		fs := NewMemFS()
 		fs := NewMemFS()
 		h := &Handler{
 		h := &Handler{
@@ -183,7 +186,7 @@ func TestPrefix(t *testing.T) {
 			continue
 			continue
 		}
 		}
 
 
-		got, err := find(nil, fs, "/")
+		got, err := find(ctx, nil, fs, "/")
 		if err != nil {
 		if err != nil {
 			t.Errorf("prefix=%-9q find: %v", prefix, err)
 			t.Errorf("prefix=%-9q find: %v", prefix, err)
 			continue
 			continue
@@ -297,14 +300,15 @@ func TestFilenameEscape(t *testing.T) {
 		wantHref:        `/go%3Clang`,
 		wantHref:        `/go%3Clang`,
 		wantDisplayName: `go&lt;lang`,
 		wantDisplayName: `go&lt;lang`,
 	}}
 	}}
+	ctx := context.Background()
 	fs := NewMemFS()
 	fs := NewMemFS()
 	for _, tc := range testCases {
 	for _, tc := range testCases {
 		if strings.HasSuffix(tc.name, "/") {
 		if strings.HasSuffix(tc.name, "/") {
-			if err := fs.Mkdir(tc.name, 0755); err != nil {
+			if err := fs.Mkdir(ctx, tc.name, 0755); err != nil {
 				t.Fatalf("name=%q: Mkdir: %v", tc.name, err)
 				t.Fatalf("name=%q: Mkdir: %v", tc.name, err)
 			}
 			}
 		} else {
 		} else {
-			f, err := fs.OpenFile(tc.name, os.O_CREATE, 0644)
+			f, err := fs.OpenFile(ctx, tc.name, os.O_CREATE, 0644)
 			if err != nil {
 			if err != nil {
 				t.Fatalf("name=%q: OpenFile: %v", tc.name, err)
 				t.Fatalf("name=%q: OpenFile: %v", tc.name, err)
 			}
 			}