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 năm trước cách đây
mục cha
commit
4bb47a1098
8 tập tin đã thay đổi với 184 bổ sung112 xóa
  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"
 	"sync"
 	"time"
+
+	"golang.org/x/net/context"
 )
 
 // 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
 // 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 @@ func (d Dir) resolve(name string) string {
 	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 @@ func (d Dir) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
 	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 @@ func (d Dir) RemoveAll(name string) error {
 	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 @@ func (d Dir) Rename(oldName, newName string) error {
 	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 @@ func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, 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()
 	defer fs.mu.Unlock()
 
@@ -260,7 +262,7 @@ func (fs *memFS) Mkdir(name string, perm os.FileMode) error {
 	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 @@ func (fs *memFS) OpenFile(name string, flag int, perm os.FileMode) (File, error)
 	}, 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 @@ func (fs *memFS) RemoveAll(name string) error {
 	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 @@ func (fs *memFS) Rename(oldName, newName string) error {
 	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 @@ func (f *memFile) Write(p []byte) (int, error) {
 // 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 @@ func moveFiles(fs FileSystem, src, dst string, overwrite bool) (status int, err
 		// 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 @@ func copyProps(dst, src File) error {
 // 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 @@ 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/
 	// 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 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 	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 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 		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 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 				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 @@ func copyFiles(fs FileSystem, src, dst string, overwrite bool, depth int, recurs
 		}
 
 	} 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 @@ 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,
 // 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 @@ func walkFS(fs FileSystem, depth int, name string, info os.FileInfo, walkFn file
 	}
 
 	// 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 @@ func walkFS(fs FileSystem, depth int, name string, info os.FileInfo, walkFn file
 
 	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

+ 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"
 	"strings"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 
 func TestSlashClean(t *testing.T) {
@@ -195,13 +197,15 @@ func TestWalk(t *testing.T) {
 		}},
 	}
 
+	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 @@ func TestWalk(t *testing.T) {
 // 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 @@ func find(ss []string, fs FileSystem, name string) ([]string, error) {
 			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 @@ func testFS(t *testing.T, fs FileSystem) {
 		"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 @@ func testFS(t *testing.T, fs FileSystem) {
 			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 @@ func testFS(t *testing.T, fs FileSystem) {
 			}
 
 		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 @@ func testFS(t *testing.T, fs FileSystem) {
 				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 TestMemFS(t *testing.T) {
 }
 
 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 @@ func TestMemFSRoot(t *testing.T) {
 			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 @@ func TestMemFSRoot(t *testing.T) {
 			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 @@ func TestMemFile(t *testing.T) {
 		"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 @@ func TestMemFile(t *testing.T) {
 			}
 
 		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 @@ func TestMemFile(t *testing.T) {
 			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 @@ func TestMemFileWriteAllocs(t *testing.T) {
 	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 TestMemFileWriteAllocs(t *testing.T) {
 }
 
 func BenchmarkMemFileWrite(b *testing.B) {
+	ctx := context.Background()
 	fs := NewMemFS()
 	xxx := make([]byte, 1024)
 	for i := range xxx {
@@ -820,7 +832,7 @@ func BenchmarkMemFileWrite(b *testing.B) {
 
 	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 @@ func BenchmarkMemFileWrite(b *testing.B) {
 		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 @@ func TestCopyMoveProps(t *testing.T) {
 		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 @@ func TestCopyMoveProps(t *testing.T) {
 		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 @@ func TestCopyMoveProps(t *testing.T) {
 	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 @@ func TestWalkFS(t *testing.T) {
 			"/a/b/z",
 		},
 	}}
+	ctx := context.Background()
 	for _, tc := range testCases {
 		fs, err := buildTestFS(tc.buildfs)
 		if err != nil {
@@ -1115,11 +1129,11 @@ func TestWalkFS(t *testing.T) {
 			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 TestWalkFS(t *testing.T) {
 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
 			}

+ 21 - 19
webdav/prop.go

@@ -14,6 +14,8 @@ import (
 	"os"
 	"path/filepath"
 	"strconv"
+
+	"golang.org/x/net/context"
 )
 
 // Proppatch describes a property update instruction as defined in RFC 4918.
@@ -101,7 +103,7 @@ type DeadPropsHolder interface {
 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 @@ var liveProps = map[xml.Name]struct {
 //
 // 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 @@ 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.
 		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 @@ func props(fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Prop
 }
 
 // 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 @@ func propnames(fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
 // 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 @@ func allprop(fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]P
 			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 @@ loop:
 		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 @@ func escapeXML(s string) string {
 	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 @@ func findDisplayName(fs FileSystem, ls LockSystem, name string, fi os.FileInfo)
 	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 @@ func findContentType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo)
 	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>` +

+ 11 - 8
webdav/prop_test.go

@@ -12,13 +12,16 @@ import (
 	"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 @@ func TestMemPS(t *testing.T) {
 					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 @@ func TestMemPS(t *testing.T) {
 			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 @@ func TestMemPS(t *testing.T) {
 				}
 				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 @@ type noDeadPropsFS struct {
 	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
 	}

+ 32 - 19
webdav/webdav.go

@@ -174,8 +174,9 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
 	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 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
 		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 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
 	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 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
 	}
 	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 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
 	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 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
 	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 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
 	}
 	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 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 		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 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 				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 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
 			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 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
 		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 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
 		}()
 
 		// 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 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 	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 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 		}
 		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 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 			}
 			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 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
 		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 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
 	}
 	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 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
 	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
 	}

+ 7 - 3
webdav/webdav_test.go

@@ -18,6 +18,8 @@ import (
 	"sort"
 	"strings"
 	"testing"
+
+	"golang.org/x/net/context"
 )
 
 // 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/c/",
 	}
+	ctx := context.Background()
 	for _, prefix := range prefixes {
 		fs := NewMemFS()
 		h := &Handler{
@@ -183,7 +186,7 @@ func TestPrefix(t *testing.T) {
 			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 @@ func TestFilenameEscape(t *testing.T) {
 		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)
 			}