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>
diff --git a/webdav/file.go b/webdav/file.go
index 3d95c6c..748118d 100644
--- a/webdav/file.go
+++ b/webdav/file.go
@@ -14,6 +14,8 @@
 	"strings"
 	"sync"
 	"time"
+
+	"golang.org/x/net/context"
 )
 
 // slashClean is equivalent to but slightly more efficient than
@@ -36,11 +38,11 @@
 // might apply". In particular, whether or not renaming a file or directory
 // overwriting another existing file or directory is an error is OS-dependent.
 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
@@ -76,14 +78,14 @@
 	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 == "" {
 		return os.ErrNotExist
 	}
 	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 == "" {
 		return nil, os.ErrNotExist
 	}
@@ -94,7 +96,7 @@
 	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 == "" {
 		return os.ErrNotExist
 	}
@@ -105,7 +107,7 @@
 	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 == "" {
 		return os.ErrNotExist
 	}
@@ -119,7 +121,7 @@
 	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 == "" {
 		return nil, os.ErrNotExist
 	}
@@ -237,7 +239,7 @@
 	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()
 	defer fs.mu.Unlock()
 
@@ -260,7 +262,7 @@
 	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()
 	defer fs.mu.Unlock()
 
@@ -314,7 +316,7 @@
 	}, nil
 }
 
-func (fs *memFS) RemoveAll(name string) error {
+func (fs *memFS) RemoveAll(ctx context.Context, name string) error {
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 
@@ -330,7 +332,7 @@
 	return nil
 }
 
-func (fs *memFS) Rename(oldName, newName string) error {
+func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
 	fs.mu.Lock()
 	defer fs.mu.Unlock()
 
@@ -381,7 +383,7 @@
 	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()
 	defer fs.mu.Unlock()
 
@@ -599,9 +601,9 @@
 // moveFiles moves files and/or directories from src to dst.
 //
 // 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
-	if _, err := fs.Stat(dst); err != nil {
+	if _, err := fs.Stat(ctx, dst); err != nil {
 		if !os.IsNotExist(err) {
 			return http.StatusForbidden, err
 		}
@@ -611,13 +613,13 @@
 		// and the Overwrite header is "T", then prior to performing the move,
 		// the server must perform a DELETE with "Depth: infinity" on the
 		// destination resource.
-		if err := fs.RemoveAll(dst); err != nil {
+		if err := fs.RemoveAll(ctx, dst); err != nil {
 			return http.StatusForbidden, err
 		}
 	} else {
 		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
 	}
 	if created {
@@ -650,7 +652,7 @@
 // copyFiles copies files and/or directories from src to dst.
 //
 // 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 {
 		return http.StatusInternalServerError, errRecursionTooDeep
 	}
@@ -659,7 +661,7 @@
 	// 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."
 
-	srcFile, err := fs.OpenFile(src, os.O_RDONLY, 0)
+	srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return http.StatusNotFound, err
@@ -677,7 +679,7 @@
 	srcPerm := srcStat.Mode() & os.ModePerm
 
 	created := false
-	if _, err := fs.Stat(dst); err != nil {
+	if _, err := fs.Stat(ctx, dst); err != nil {
 		if os.IsNotExist(err) {
 			created = true
 		} else {
@@ -687,13 +689,13 @@
 		if !overwrite {
 			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
 		}
 	}
 
 	if srcStat.IsDir() {
-		if err := fs.Mkdir(dst, srcPerm); err != nil {
+		if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
 			return http.StatusForbidden, err
 		}
 		if depth == infiniteDepth {
@@ -705,7 +707,7 @@
 				name := c.Name()
 				s := path.Join(src, 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 {
 					// TODO: MultiStatus.
 					return cStatus, cErr
@@ -714,7 +716,7 @@
 		}
 
 	} 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 os.IsNotExist(err) {
 				return http.StatusConflict, err
@@ -747,7 +749,7 @@
 // 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
 // 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.
 	err := walkFn(name, info, nil)
 	if err != nil {
@@ -764,7 +766,7 @@
 	}
 
 	// 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 {
 		return walkFn(name, info, err)
 	}
@@ -776,13 +778,13 @@
 
 	for _, fileInfo := range fileInfos {
 		filename := path.Join(name, fileInfo.Name())
-		fileInfo, err := fs.Stat(filename)
+		fileInfo, err := fs.Stat(ctx, filename)
 		if err != nil {
 			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
 				return err
 			}
 		} else {
-			err = walkFS(fs, depth, filename, fileInfo, walkFn)
+			err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn)
 			if err != nil {
 				if !fileInfo.IsDir() || err != filepath.SkipDir {
 					return err
diff --git a/webdav/file_go1.6.go b/webdav/file_go1.6.go
new file mode 100644
index 0000000..fa38770
--- /dev/null
+++ b/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()
+}
diff --git a/webdav/file_go1.7.go b/webdav/file_go1.7.go
new file mode 100644
index 0000000..d1c3de8
--- /dev/null
+++ b/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()
+}
diff --git a/webdav/file_test.go b/webdav/file_test.go
index cbd0240..bfd96e1 100644
--- a/webdav/file_test.go
+++ b/webdav/file_test.go
@@ -18,6 +18,8 @@
 	"strconv"
 	"strings"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 
 func TestSlashClean(t *testing.T) {
@@ -195,13 +197,15 @@
 		}},
 	}
 
+	ctx := context.Background()
+
 	for _, tc := range testCases {
 		fs := NewMemFS().(*memFS)
 
 		parts := strings.Split(tc.dir, "/")
 		for p := 2; p < len(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)
 			}
 		}
@@ -231,14 +235,14 @@
 // analogous to the Unix find command.
 //
 // 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 {
 		return nil, err
 	}
 	ss = append(ss, name)
 	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 {
 			return nil, err
 		}
@@ -248,7 +252,7 @@
 			return nil, err
 		}
 		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 {
 				return nil, err
 			}
@@ -403,6 +407,8 @@
 		"copy__ o=F d=∞ /d/y /d/x want errExist",
 	}
 
+	ctx := context.Background()
+
 	for i, tc := range testCases {
 		tc = strings.TrimSpace(tc)
 		j := strings.IndexByte(tc, ' ')
@@ -420,7 +426,7 @@
 			if len(parts) != 4 || parts[2] != "want" {
 				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] {
 				t.Fatalf("test case #%d %q: OpenFile: got %q (%v), want %q", i, tc, got, opErr, parts[3])
 			}
@@ -434,7 +440,7 @@
 			}
 
 		case "find":
-			got, err := find(nil, fs, "/")
+			got, err := find(ctx, nil, fs, "/")
 			if err != nil {
 				t.Fatalf("test case #%d %q: find: %v", i, tc, err)
 			}
@@ -464,17 +470,17 @@
 				if parts[1] == "d=∞" {
 					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":
-				opErr = fs.Mkdir(parts[0], 0777)
+				opErr = fs.Mkdir(ctx, parts[0], 0777)
 			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":
-				opErr = fs.RemoveAll(parts[0])
+				opErr = fs.RemoveAll(ctx, parts[0])
 			case "stat":
 				var stat os.FileInfo
 				fileName := parts[0]
-				if stat, opErr = fs.Stat(fileName); opErr == nil {
+				if stat, opErr = fs.Stat(ctx, fileName); opErr == nil {
 					if stat.IsDir() {
 						got = "dir"
 					} else {
@@ -526,9 +532,10 @@
 }
 
 func TestMemFSRoot(t *testing.T) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	for i := 0; i < 5; i++ {
-		stat, err := fs.Stat("/")
+		stat, err := fs.Stat(ctx, "/")
 		if err != nil {
 			t.Fatalf("i=%d: Stat: %v", i, err)
 		}
@@ -536,7 +543,7 @@
 			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 {
 			t.Fatalf("i=%d: OpenFile: %v", i, err)
 		}
@@ -553,19 +560,20 @@
 			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)
 		}
 	}
 }
 
 func TestMemFileReaddir(t *testing.T) {
+	ctx := context.Background()
 	fs := NewMemFS()
-	if err := fs.Mkdir("/foo", 0777); err != nil {
+	if err := fs.Mkdir(ctx, "/foo", 0777); err != nil {
 		t.Fatalf("Mkdir: %v", err)
 	}
 	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 {
 			t.Fatalf("OpenFile: %v", err)
 		}
@@ -649,9 +657,11 @@
 		"seek cur -99 want err",
 	}
 
+	ctx := context.Background()
+
 	const filename = "/foo"
 	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 {
 		t.Fatalf("OpenFile: %v", err)
 	}
@@ -745,7 +755,7 @@
 			}
 
 		case "wantData":
-			g, err := fs.OpenFile(filename, os.O_RDONLY, 0666)
+			g, err := fs.OpenFile(ctx, filename, os.O_RDONLY, 0666)
 			if err != nil {
 				t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err)
 			}
@@ -771,7 +781,7 @@
 			if err != nil {
 				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 {
 				t.Fatalf("test case #%d %q: Stat: %v", i, tc, err)
 			}
@@ -789,8 +799,9 @@
 	if runtime.Compiler == "gccgo" {
 		t.Skip("gccgo allocates here")
 	}
+	ctx := context.Background()
 	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 {
 		t.Fatalf("OpenFile: %v", err)
 	}
@@ -812,6 +823,7 @@
 }
 
 func BenchmarkMemFileWrite(b *testing.B) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	xxx := make([]byte, 1024)
 	for i := range xxx {
@@ -820,7 +832,7 @@
 
 	b.ResetTimer()
 	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 {
 			b.Fatalf("OpenFile: %v", err)
 		}
@@ -830,16 +842,17 @@
 		if err := f.Close(); err != nil {
 			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)
 		}
 	}
 }
 
 func TestCopyMoveProps(t *testing.T) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	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 {
 			return err
 		}
@@ -851,7 +864,7 @@
 		return cErr
 	}
 	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 {
 			return err
 		}
@@ -863,7 +876,7 @@
 		return cErr
 	}
 	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 {
 			return nil, err
 		}
@@ -901,10 +914,10 @@
 	if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
 		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)
 	}
-	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)
 	}
 	if err := patch("/src", Proppatch{Props: []Property{p0}, Remove: true}); err != nil {
@@ -1099,6 +1112,7 @@
 			"/a/b/z",
 		},
 	}}
+	ctx := context.Background()
 	for _, tc := range testCases {
 		fs, err := buildTestFS(tc.buildfs)
 		if err != nil {
@@ -1115,11 +1129,11 @@
 			got = append(got, path)
 			return nil
 		}
-		fi, err := fs.Stat(tc.startAt)
+		fi, err := fs.Stat(ctx, tc.startAt)
 		if err != nil {
 			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 {
 			t.Errorf("%s:\ngot error %v, want nil", tc.desc, err)
 			continue
@@ -1136,23 +1150,24 @@
 func buildTestFS(buildfs []string) (FileSystem, error) {
 	// TODO: Could this be merged with the build logic in TestFS?
 
+	ctx := context.Background()
 	fs := NewMemFS()
 	for _, b := range buildfs {
 		op := strings.Split(b, " ")
 		switch op[0] {
 		case "mkdir":
-			err := fs.Mkdir(op[1], os.ModeDir|0777)
+			err := fs.Mkdir(ctx, op[1], os.ModeDir|0777)
 			if err != nil {
 				return nil, err
 			}
 		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 {
 				return nil, err
 			}
 			f.Close()
 		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 {
 				return nil, err
 			}
diff --git a/webdav/prop.go b/webdav/prop.go
index 83fbfa8..00a82fb 100644
--- a/webdav/prop.go
+++ b/webdav/prop.go
@@ -14,6 +14,8 @@
 	"os"
 	"path/filepath"
 	"strconv"
+
+	"golang.org/x/net/context"
 )
 
 // Proppatch describes a property update instruction as defined in RFC 4918.
@@ -101,7 +103,7 @@
 var liveProps = map[xml.Name]struct {
 	// findFn implements the propfind function of this property. If nil,
 	// 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 bool
 }{
@@ -164,8 +166,8 @@
 //
 // Each Propstat has a unique status and each property name will only be part
 // 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 {
 		return nil, err
 	}
@@ -194,7 +196,7 @@
 		}
 		// Otherwise, it must either be a live property or we don't know it.
 		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 {
 				return nil, err
 			}
@@ -212,8 +214,8 @@
 }
 
 // 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 {
 		return nil, err
 	}
@@ -252,8 +254,8 @@
 // returned if they are named in 'include'.
 //
 // 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 {
 		return nil, err
 	}
@@ -267,12 +269,12 @@
 			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
 // 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
 loop:
 	for _, patch := range patches {
@@ -303,7 +305,7 @@
 		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 {
 		return nil, err
 	}
@@ -354,14 +356,14 @@
 	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() {
 		return `<D:collection xmlns:D="DAV:"/>`, 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) == "/" {
 		// Hide the real name of a possibly prefixed root directory.
 		return "", nil
@@ -369,16 +371,16 @@
 	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
 }
 
-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
 }
 
-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 {
 		return "", err
 	}
@@ -400,14 +402,14 @@
 	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
 	// modification time and size of a file. We replicate the heuristic
 	// with nanosecond granularity.
 	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 `` +
 		`<D:lockentry xmlns:D="DAV:">` +
 		`<D:lockscope><D:exclusive/></D:lockscope>` +
diff --git a/webdav/prop_test.go b/webdav/prop_test.go
index 0834dc9..57d0e82 100644
--- a/webdav/prop_test.go
+++ b/webdav/prop_test.go
@@ -12,13 +12,16 @@
 	"reflect"
 	"sort"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 
 func TestMemPS(t *testing.T) {
+	ctx := context.Background()
 	// calcProps calculates the getlastmodified and getetag DAV: property
 	// values in pstats for resource name in file-system fs.
 	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 {
 			return err
 		}
@@ -32,7 +35,7 @@
 					if fi.IsDir() {
 						continue
 					}
-					etag, err := findETag(fs, ls, name, fi)
+					etag, err := findETag(ctx, fs, ls, name, fi)
 					if err != nil {
 						return err
 					}
@@ -519,7 +522,7 @@
 			var propstats []Propstat
 			switch op.op {
 			case "propname":
-				pnames, err := propnames(fs, ls, op.name)
+				pnames, err := propnames(ctx, fs, ls, op.name)
 				if err != nil {
 					t.Errorf("%s: got error %v, want nil", desc, err)
 					continue
@@ -531,11 +534,11 @@
 				}
 				continue
 			case "allprop":
-				propstats, err = allprop(fs, ls, op.name, op.pnames)
+				propstats, err = allprop(ctx, fs, ls, op.name, op.pnames)
 			case "propfind":
-				propstats, err = props(fs, ls, op.name, op.pnames)
+				propstats, err = props(ctx, fs, ls, op.name, op.pnames)
 			case "proppatch":
-				propstats, err = patch(fs, ls, op.name, op.patches)
+				propstats, err = patch(ctx, fs, ls, op.name, op.patches)
 			default:
 				t.Fatalf("%s: %s not implemented", desc, op.op)
 			}
@@ -588,8 +591,8 @@
 	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 {
 		return nil, err
 	}
diff --git a/webdav/webdav.go b/webdav/webdav.go
index 4ce0972..7b56687 100644
--- a/webdav/webdav.go
+++ b/webdav/webdav.go
@@ -174,8 +174,9 @@
 	if err != nil {
 		return status, err
 	}
+	ctx := getContext(r)
 	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() {
 			allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
 		} else {
@@ -196,7 +197,8 @@
 		return status, err
 	}
 	// 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 {
 		return http.StatusNotFound, err
 	}
@@ -208,7 +210,7 @@
 	if fi.IsDir() {
 		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 {
 		return http.StatusInternalServerError, err
 	}
@@ -229,18 +231,20 @@
 	}
 	defer release()
 
+	ctx := getContext(r)
+
 	// TODO: return MultiStatus where appropriate.
 
 	// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
 	// returns nil (no error)." WebDAV semantics are that it should return a
 	// "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) {
 			return http.StatusNotFound, 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.StatusNoContent, nil
@@ -258,8 +262,9 @@
 	defer release()
 	// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
 	// 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 {
 		return http.StatusNotFound, err
 	}
@@ -276,7 +281,7 @@
 	if closeErr != nil {
 		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 {
 		return http.StatusInternalServerError, err
 	}
@@ -295,10 +300,12 @@
 	}
 	defer release()
 
+	ctx := getContext(r)
+
 	if r.ContentLength > 0 {
 		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) {
 			return http.StatusConflict, err
 		}
@@ -337,6 +344,8 @@
 		return http.StatusForbidden, errDestinationEqualsSource
 	}
 
+	ctx := getContext(r)
+
 	if r.Method == "COPY" {
 		// Section 7.5.1 says that a COPY only needs to lock the destination,
 		// not both destination and source. Strictly speaking, this is racy,
@@ -360,7 +369,7 @@
 				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)
@@ -377,7 +386,7 @@
 			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) {
@@ -390,6 +399,7 @@
 		return status, err
 	}
 
+	ctx := getContext(r)
 	token, ld, now, created := "", LockDetails{}, time.Now(), false
 	if li == (lockInfo{}) {
 		// An empty lockInfo means to refresh the lock.
@@ -447,8 +457,8 @@
 		}()
 
 		// 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 {
 				// TODO: detect missing intermediate dirs and return http.StatusConflict?
 				return http.StatusInternalServerError, err
@@ -501,7 +511,8 @@
 	if err != nil {
 		return status, err
 	}
-	fi, err := h.FileSystem.Stat(reqPath)
+	ctx := getContext(r)
+	fi, err := h.FileSystem.Stat(ctx, reqPath)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return http.StatusNotFound, err
@@ -528,7 +539,7 @@
 		}
 		var pstats []Propstat
 		if pf.Propname != nil {
-			pnames, err := propnames(h.FileSystem, h.LockSystem, reqPath)
+			pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
 			if err != nil {
 				return err
 			}
@@ -538,9 +549,9 @@
 			}
 			pstats = append(pstats, pstat)
 		} 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 {
-			pstats, err = props(h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+			pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
 		}
 		if err != nil {
 			return err
@@ -548,7 +559,7 @@
 		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()
 	if walkErr != nil {
 		return http.StatusInternalServerError, walkErr
@@ -570,7 +581,9 @@
 	}
 	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) {
 			return http.StatusNotFound, err
 		}
@@ -580,7 +593,7 @@
 	if err != nil {
 		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 {
 		return http.StatusInternalServerError, err
 	}
diff --git a/webdav/webdav_test.go b/webdav/webdav_test.go
index ca6abf8..25e0d54 100644
--- a/webdav/webdav_test.go
+++ b/webdav/webdav_test.go
@@ -18,6 +18,8 @@
 	"sort"
 	"strings"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 
 // TODO: add tests to check XML responses with the expected prefix path
@@ -65,6 +67,7 @@
 		"/a/b/",
 		"/a/b/c/",
 	}
+	ctx := context.Background()
 	for _, prefix := range prefixes {
 		fs := NewMemFS()
 		h := &Handler{
@@ -183,7 +186,7 @@
 			continue
 		}
 
-		got, err := find(nil, fs, "/")
+		got, err := find(ctx, nil, fs, "/")
 		if err != nil {
 			t.Errorf("prefix=%-9q find: %v", prefix, err)
 			continue
@@ -297,14 +300,15 @@
 		wantHref:        `/go%3Clang`,
 		wantDisplayName: `go&lt;lang`,
 	}}
+	ctx := context.Background()
 	fs := NewMemFS()
 	for _, tc := range testCases {
 		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)
 			}
 		} 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 {
 				t.Fatalf("name=%q: OpenFile: %v", tc.name, err)
 			}