瀏覽代碼

x/net/webdav: add a Dir type, analogous to http.Dir.

LGTM=nmvc, dave
R=dave, nmvc
CC=bradfitz, dr.volker.dobler, golang-codereviews, robert.stepanek
https://golang.org/cl/173100044
Nigel Tao 11 年之前
父節點
當前提交
240cea5a06
共有 3 個文件被更改,包括 187 次插入8 次删除
  1. 70 7
      webdav/file.go
  2. 117 0
      webdav/file_test.go
  3. 0 1
      webdav/webdav.go

+ 70 - 7
webdav/file.go

@@ -8,21 +8,84 @@ import (
 	"io"
 	"net/http"
 	"os"
+	"path"
+	"path/filepath"
+	"strings"
 )
 
-// TODO: comment that paths are always "/"-separated, even for Windows servers.
-
+// A FileSystem implements access to a collection of named files. The elements
+// in a file path are separated by slash ('/', U+002F) characters, regardless
+// of host operating system convention.
+//
+// Each method has the same semantics as the os package's function of the same
+// name.
 type FileSystem interface {
-	Mkdir(path string, perm os.FileMode) error
-	OpenFile(path string, flag int, perm os.FileMode) (File, error)
-	RemoveAll(path string) error
-	Stat(path string) (os.FileInfo, error)
+	Mkdir(name string, perm os.FileMode) error
+	OpenFile(name string, flag int, perm os.FileMode) (File, error)
+	RemoveAll(name string) error
+	Stat(name string) (os.FileInfo, error)
 }
 
+// A File is returned by a FileSystem's OpenFile method and can be served by a
+// Handler.
 type File interface {
 	http.File
 	io.Writer
 }
 
+// A Dir implements FileSystem using the native file system restricted to a
+// specific directory tree.
+//
+// While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's
+// string value is a filename on the native file system, not a URL, so it is
+// separated by filepath.Separator, which isn't necessarily '/'.
+//
+// An empty Dir is treated as ".".
+type Dir string
+
+func (d Dir) resolve(name string) string {
+	// This implementation is based on Dir.Open's code in the standard net/http package.
+	if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
+		strings.Contains(name, "\x00") {
+		return ""
+	}
+	dir := string(d)
+	if dir == "" {
+		dir = "."
+	}
+	return filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
+}
+
+func (d Dir) Mkdir(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) {
+	if name = d.resolve(name); name == "" {
+		return nil, os.ErrNotExist
+	}
+	return os.OpenFile(name, flag, perm)
+}
+
+func (d Dir) RemoveAll(name string) error {
+	if name = d.resolve(name); name == "" {
+		return os.ErrNotExist
+	}
+	if name == filepath.Clean(string(d)) {
+		// Prohibit removing the virtual root directory.
+		return os.ErrInvalid
+	}
+	return os.RemoveAll(name)
+}
+
+func (d Dir) Stat(name string) (os.FileInfo, error) {
+	if name = d.resolve(name); name == "" {
+		return nil, os.ErrNotExist
+	}
+	return os.Stat(name)
+}
+
 // TODO: a MemFS implementation.
-// TODO: a RealFS implementation, backed by the real, OS-provided file system.

+ 117 - 0
webdav/file_test.go

@@ -0,0 +1,117 @@
+// Copyright 2014 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.
+
+package webdav
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+func TestDir(t *testing.T) {
+	testCases := []struct {
+		dir, name, want string
+	}{
+		{"/", "", "/"},
+		{"/", "/", "/"},
+		{"/", ".", "/"},
+		{"/", "./a", "/a"},
+		{"/", "..", "/"},
+		{"/", "..", "/"},
+		{"/", "../", "/"},
+		{"/", "../.", "/"},
+		{"/", "../a", "/a"},
+		{"/", "../..", "/"},
+		{"/", "../bar/a", "/bar/a"},
+		{"/", "../baz/a", "/baz/a"},
+		{"/", "...", "/..."},
+		{"/", ".../a", "/.../a"},
+		{"/", ".../..", "/"},
+		{"/", "a", "/a"},
+		{"/", "a/./b", "/a/b"},
+		{"/", "a/../../b", "/b"},
+		{"/", "a/../b", "/b"},
+		{"/", "a/b", "/a/b"},
+		{"/", "a/b/c/../../d", "/a/d"},
+		{"/", "a/b/c/../../../d", "/d"},
+		{"/", "a/b/c/../../../../d", "/d"},
+		{"/", "a/b/c/d", "/a/b/c/d"},
+
+		{"/foo/bar", "", "/foo/bar"},
+		{"/foo/bar", "/", "/foo/bar"},
+		{"/foo/bar", ".", "/foo/bar"},
+		{"/foo/bar", "./a", "/foo/bar/a"},
+		{"/foo/bar", "..", "/foo/bar"},
+		{"/foo/bar", "../", "/foo/bar"},
+		{"/foo/bar", "../.", "/foo/bar"},
+		{"/foo/bar", "../a", "/foo/bar/a"},
+		{"/foo/bar", "../..", "/foo/bar"},
+		{"/foo/bar", "../bar/a", "/foo/bar/bar/a"},
+		{"/foo/bar", "../baz/a", "/foo/bar/baz/a"},
+		{"/foo/bar", "...", "/foo/bar/..."},
+		{"/foo/bar", ".../a", "/foo/bar/.../a"},
+		{"/foo/bar", ".../..", "/foo/bar"},
+		{"/foo/bar", "a", "/foo/bar/a"},
+		{"/foo/bar", "a/./b", "/foo/bar/a/b"},
+		{"/foo/bar", "a/../../b", "/foo/bar/b"},
+		{"/foo/bar", "a/../b", "/foo/bar/b"},
+		{"/foo/bar", "a/b", "/foo/bar/a/b"},
+		{"/foo/bar", "a/b/c/../../d", "/foo/bar/a/d"},
+		{"/foo/bar", "a/b/c/../../../d", "/foo/bar/d"},
+		{"/foo/bar", "a/b/c/../../../../d", "/foo/bar/d"},
+		{"/foo/bar", "a/b/c/d", "/foo/bar/a/b/c/d"},
+
+		{"/foo/bar/", "", "/foo/bar"},
+		{"/foo/bar/", "/", "/foo/bar"},
+		{"/foo/bar/", ".", "/foo/bar"},
+		{"/foo/bar/", "./a", "/foo/bar/a"},
+		{"/foo/bar/", "..", "/foo/bar"},
+
+		{"/foo//bar///", "", "/foo/bar"},
+		{"/foo//bar///", "/", "/foo/bar"},
+		{"/foo//bar///", ".", "/foo/bar"},
+		{"/foo//bar///", "./a", "/foo/bar/a"},
+		{"/foo//bar///", "..", "/foo/bar"},
+
+		{"/x/y/z", "ab/c\x00d/ef", ""},
+
+		{".", "", "."},
+		{".", "/", "."},
+		{".", ".", "."},
+		{".", "./a", "a"},
+		{".", "..", "."},
+		{".", "..", "."},
+		{".", "../", "."},
+		{".", "../.", "."},
+		{".", "../a", "a"},
+		{".", "../..", "."},
+		{".", "../bar/a", "bar/a"},
+		{".", "../baz/a", "baz/a"},
+		{".", "...", "..."},
+		{".", ".../a", ".../a"},
+		{".", ".../..", "."},
+		{".", "a", "a"},
+		{".", "a/./b", "a/b"},
+		{".", "a/../../b", "b"},
+		{".", "a/../b", "b"},
+		{".", "a/b", "a/b"},
+		{".", "a/b/c/../../d", "a/d"},
+		{".", "a/b/c/../../../d", "d"},
+		{".", "a/b/c/../../../../d", "d"},
+		{".", "a/b/c/d", "a/b/c/d"},
+
+		{"", "", "."},
+		{"", "/", "."},
+		{"", ".", "."},
+		{"", "./a", "a"},
+		{"", "..", "."},
+	}
+
+	for _, tc := range testCases {
+		d := Dir(filepath.FromSlash(tc.dir))
+		if got := filepath.ToSlash(d.resolve(tc.name)); got != tc.want {
+			t.Errorf("dir=%q, name=%q: got %q, want %q", tc.dir, tc.name, got, tc.want)
+		}
+	}
+}

+ 0 - 1
webdav/webdav.go

@@ -6,7 +6,6 @@
 package webdav
 
 // TODO: ETag, properties.
-// TODO: figure out what/when is responsible for path cleaning: no "../../etc/passwd"s.
 
 import (
 	"errors"