diff --git a/build/doc/webdav/PROPFIND.md b/build/doc/webdav/PROPFIND.md
new file mode 100644
index 0000000..1c88869
--- /dev/null
+++ b/build/doc/webdav/PROPFIND.md
@@ -0,0 +1,267 @@
+#### PROPFIND 列出目录情况
+request method
+```
+PROPFIND
+```
+
+request header
+```
+Authorization=Basic YWRtaW46YWRtaW4=
+Content-Type=text/xml
+Accept-Encoding=gzip
+Depth=infinity
+```
+
+request body
+```
+
+
+
+
+
+
+
+
+
+```
+
+response body
+```
+
+
+
+ /api/dav/
+
+
+ dav
+ Tue, 16 Apr 2019 17:50:59 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/api/
+
+
+ api
+ Tue, 16 Apr 2019 17:51:03 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/api/dav/
+
+
+ dav
+ Tue, 16 Apr 2019 17:51:38 GMT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/api/dav/body.txt
+
+
+ body.txt
+ Tue, 16 Apr 2019 17:51:38 GMT
+ text/plain; charset=utf-8
+ "159605ccc1d0f3c410"
+
+
+
+
+
+
+
+
+
+
+
+ 16
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/api/dav/cat.txt
+
+
+
+ 24
+ Tue, 16 Apr 2019 17:51:19 GMT
+ text/plain; charset=utf-8
+ "159605c862b0d64c18"
+
+
+
+
+
+
+
+
+
+
+ cat.txt
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/cat/
+
+
+ Sat, 13 Apr 2019 16:55:54 GMT
+
+
+
+
+
+
+
+
+
+
+ cat
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/cat/dog/
+
+
+ Sat, 13 Apr 2019 16:55:58 GMT
+
+
+
+
+
+
+
+
+
+
+ dog
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/cat/dog/pig/
+
+
+
+
+
+ Sat, 13 Apr 2019 16:56:08 GMT
+
+
+
+
+
+
+
+
+
+
+ pig
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/cat/dog/pig/hi.txt
+
+
+ Sat, 13 Apr 2019 16:56:08 GMT
+ text/plain; charset=utf-8
+ "15951707dc1116d87"
+
+
+
+
+
+
+
+
+
+
+ hi.txt
+
+ 7
+
+ HTTP/1.1 200 OK
+
+
+
+ /api/dav/morning.txt
+
+
+
+ 13
+ morning.txt
+ Sat, 13 Apr 2019 16:52:08 GMT
+ text/plain; charset=utf-8
+ "159516cfe790beecd"
+
+
+
+
+
+
+
+
+
+
+
+ HTTP/1.1 200 OK
+
+
+
+```
\ No newline at end of file
diff --git a/rest/dav/file.go b/rest/dav/file.go
index 6e9c982..a67c7d8 100644
--- a/rest/dav/file.go
+++ b/rest/dav/file.go
@@ -17,9 +17,9 @@ import (
"time"
)
-// slashClean is equivalent to but slightly more efficient than
+// SlashClean is equivalent to but slightly more efficient than
// path.Clean("/" + name).
-func slashClean(name string) string {
+func SlashClean(name string) string {
if name == "" || name[0] != '/' {
name = "/" + name
}
@@ -74,7 +74,7 @@ func (d Dir) resolve(name string) string {
if dir == "" {
dir = "."
}
- return filepath.Join(dir, filepath.FromSlash(slashClean(name)))
+ return filepath.Join(dir, filepath.FromSlash(SlashClean(name)))
}
func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
@@ -166,7 +166,7 @@ type memFS struct {
// ends at that root node.
func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error {
original := fullname
- fullname = slashClean(fullname)
+ fullname = SlashClean(fullname)
// Strip any leading "/"s to make fullname a relative path, as the walk
// starts at fs.root.
@@ -335,8 +335,8 @@ func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error {
fs.mu.Lock()
defer fs.mu.Unlock()
- oldName = slashClean(oldName)
- newName = slashClean(newName)
+ oldName = SlashClean(oldName)
+ newName = SlashClean(newName)
if oldName == newName {
return nil
}
diff --git a/rest/dav/file_test.go b/rest/dav/file_test.go
index bbbf47b..8af8644 100644
--- a/rest/dav/file_test.go
+++ b/rest/dav/file_test.go
@@ -37,7 +37,7 @@ func TestSlashClean(t *testing.T) {
"a/b/c",
}
for _, tc := range testCases {
- got := slashClean(tc)
+ got := SlashClean(tc)
want := path.Clean("/" + tc)
if got != want {
t.Errorf("tc=%q: got %q, want %q", tc, got, want)
diff --git a/rest/dav/lock.go b/rest/dav/lock.go
index 47b8248..65d3f25 100644
--- a/rest/dav/lock.go
+++ b/rest/dav/lock.go
@@ -151,12 +151,12 @@ func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condit
var n0, n1 *memLSNode
if name0 != "" {
- if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil {
+ if n0 = m.lookup(SlashClean(name0), conditions...); n0 == nil {
return nil, ErrConfirmationFailed
}
}
if name1 != "" {
- if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil {
+ if n1 = m.lookup(SlashClean(name1), conditions...); n1 == nil {
return nil, ErrConfirmationFailed
}
}
@@ -233,7 +233,7 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
- details.Root = slashClean(details.Root)
+ details.Root = SlashClean(details.Root)
if !m.canCreate(details.Root, details.ZeroDepth) {
return "", ErrLocked
diff --git a/rest/dav/prop.go b/rest/dav/prop.go
index a1173f6..28582b0 100644
--- a/rest/dav/prop.go
+++ b/rest/dav/prop.go
@@ -336,7 +336,7 @@ loop:
return []Propstat{pstat}, nil
}
-func escapeXML(s string) string {
+func EscapeXML(s string) string {
for i := 0; i < len(s); i++ {
// As an optimization, if s contains only ASCII letters, digits or a
// few special characters, the escaped value is s itself and we don't
@@ -364,11 +364,11 @@ func findResourceType(ctx context.Context, fs FileSystem, ls LockSystem, name st
}
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.
return "", nil
}
- return escapeXML(fi.Name()), nil
+ return EscapeXML(fi.Name()), nil
}
func findContentLength(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
diff --git a/rest/dav/webdav_test.go b/rest/dav/webdav_test.go
index 51efc40..e4b13c2 100644
--- a/rest/dav/webdav_test.go
+++ b/rest/dav/webdav_test.go
@@ -208,7 +208,7 @@ func TestEscapeXML(t *testing.T) {
// These test cases aren't exhaustive, and there is more than one way to
// escape e.g. a quot (as """ or """) or an apos. We presume that
// the encoding/xml package tests xml.EscapeText more thoroughly. This test
- // here is just a sanity check for this package's escapeXML function, and
+ // here is just a sanity check for this package's EscapeXML function, and
// its attempt to provide a fast path (and avoid a bytes.Buffer allocation)
// when escaping filenames is obviously a no-op.
testCases := map[string]string{
@@ -236,7 +236,7 @@ func TestEscapeXML(t *testing.T) {
}
for in, want := range testCases {
- if got := escapeXML(in); got != want {
+ if got := EscapeXML(in); got != want {
t.Errorf("in=%q: got %q, want %q", in, got, want)
}
}
diff --git a/rest/dav_controller.go b/rest/dav_controller.go
index b9d5167..ad23ad3 100644
--- a/rest/dav_controller.go
+++ b/rest/dav_controller.go
@@ -1,6 +1,7 @@
package rest
import (
+ "fmt"
"net/http"
"regexp"
"tank/rest/dav"
@@ -80,7 +81,8 @@ func (this *DavController) HandleRoutes(writer http.ResponseWriter, request *htt
path := request.URL.Path
//匹配 /api/dav{subPath}
- reg := regexp.MustCompile(`^/api/dav(.*)$`)
+ pattern := fmt.Sprintf(`^%s(.*)$`, WEBDAV_PREFFIX)
+ reg := regexp.MustCompile(pattern)
strs := reg.FindStringSubmatch(path)
if len(strs) == 2 {
var f = func(writer http.ResponseWriter, request *http.Request) {
diff --git a/rest/dav_model.go b/rest/dav_model.go
index c9f4f37..8b88f1d 100644
--- a/rest/dav_model.go
+++ b/rest/dav_model.go
@@ -1,3 +1,102 @@
package rest
+import (
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "strconv"
+ "tank/rest/dav"
+)
+//访问前缀,这个是特殊入口
+var WEBDAV_PREFFIX = "/api/dav"
+
+//动态的文件属性
+type LiveProp struct {
+ findFn func(matter *Matter) string
+ dir bool
+}
+
+//所有的动态属性定义及其值的获取方式
+var LivePropMap = map[xml.Name]LiveProp{
+ {Space: "DAV:", Local: "resourcetype"}: {
+ findFn: func(matter *Matter) string {
+ if matter.Dir {
+ return ``
+ } else {
+ return ""
+ }
+ },
+ dir: true,
+ },
+ {Space: "DAV:", Local: "displayname"}: {
+ findFn: func(matter *Matter) string {
+ if dav.SlashClean(matter.Name) == "/" {
+ return ""
+ } else {
+ return dav.EscapeXML(matter.Name)
+ }
+ },
+ dir: true,
+ },
+ {Space: "DAV:", Local: "getcontentlength"}: {
+ findFn: func(matter *Matter) string {
+ return strconv.FormatInt(matter.Size, 10)
+ },
+ dir: false,
+ },
+ {Space: "DAV:", Local: "getlastmodified"}: {
+ findFn: func(matter *Matter) string {
+ return matter.UpdateTime.UTC().Format(http.TimeFormat)
+ },
+ // http://webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified
+ // suggests that getlastmodified should only apply to GETable
+ // resources, and this package does not support GET on directories.
+ //
+ // Nonetheless, some WebDAV clients expect child directories to be
+ // sortable by getlastmodified date, so this value is true, not false.
+ // See golang.org/issue/15334.
+ dir: true,
+ },
+ {Space: "DAV:", Local: "creationdate"}: {
+ findFn: nil,
+ dir: false,
+ },
+ {Space: "DAV:", Local: "getcontentlanguage"}: {
+ findFn: nil,
+ dir: false,
+ },
+ {Space: "DAV:", Local: "getcontenttype"}: {
+ findFn: func(matter *Matter) string {
+ if matter.Dir {
+ return ""
+ } else {
+ return dav.EscapeXML(matter.Name)
+ }
+ },
+ dir: false,
+ },
+ {Space: "DAV:", Local: "getetag"}: {
+ findFn: func(matter *Matter) string {
+ return fmt.Sprintf(`"%x%x"`, matter.UpdateTime.UnixNano(), matter.Size)
+ },
+ // findETag implements ETag as the concatenated hex values of a file's
+ // modification time and size. This is not a reliable synchronization
+ // mechanism for directories, so we do not advertise getetag for DAV
+ // collections.
+ dir: false,
+ },
+ // TODO: The lockdiscovery property requires LockSystem to list the
+ // active locks on a resource.
+ {Space: "DAV:", Local: "lockdiscovery"}: {},
+ {Space: "DAV:", Local: "supportedlock"}: {
+ findFn: func(matter *Matter) string {
+ return `` +
+ `` +
+ `` +
+ `` +
+ ``
+ },
+ dir: true,
+ },
+}
diff --git a/rest/dav_service.go b/rest/dav_service.go
index eb5a958..7f2cc86 100644
--- a/rest/dav_service.go
+++ b/rest/dav_service.go
@@ -58,6 +58,48 @@ func (this *DavService) PropNames(matter *Matter) []xml.Name {
}
+
+//从一个matter中获取其 []dav.Propstat
+func (this *DavService) Propstats(matter *Matter, propfind dav.Propfind) []dav.Propstat {
+
+ propstats := make([]dav.Propstat, 0)
+ if propfind.Propname != nil {
+ this.PanicBadRequest("propfind.Propname != nil 尚未处理")
+ } else if propfind.Allprop != nil {
+ this.PanicBadRequest("propfind.Allprop != nil 尚未处理")
+ } else {
+
+ var properties []dav.Property
+
+ for _, prop := range propfind.Prop {
+ //TODO: deadprops尚未考虑
+
+ // Otherwise, it must either be a live property or we don't know it.
+ if liveProp := LivePropMap[prop]; liveProp.findFn != nil && (liveProp.dir || !matter.Dir) {
+ innerXML := liveProp.findFn(matter)
+
+ properties = append(properties, dav.Property{
+ XMLName: prop,
+ InnerXML: []byte(innerXML),
+ })
+ } else {
+ //TODO: 某一项请求的prop没有对应的结果
+ }
+ }
+
+ if len(properties) == 0 {
+ this.PanicBadRequest("请求的属性项无法解析!")
+ }
+
+ okPropstat := dav.Propstat{Status: http.StatusOK, Props: properties}
+
+ propstats = append(propstats, okPropstat)
+ }
+
+ return propstats
+
+}
+
//处理 方法
func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, subPath string) {
@@ -84,19 +126,11 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
for _, matter := range matters {
- fmt.Printf("开始分析 %s\n", matter.Name)
+ fmt.Printf("开始分析 %s\n", matter.Path)
- var propstats []dav.Propstat
- var props = make([]dav.Property, 0)
- props = append(props, dav.Property{
- XMLName: xml.Name{Space: "DAV:"},
- })
- propstats = append(propstats, dav.Propstat{
- Props: props,
- ResponseDescription: "有点问题",
- })
-
- response := this.makePropstatResponse("/eyeblue/ready/go", propstats)
+ propstats := this.Propstats(matter, propfind)
+ path := fmt.Sprintf("%s%s", WEBDAV_PREFFIX, matter.Path)
+ response := this.makePropstatResponse(path, propstats)
err = multiStatusWriter.Write(response)
this.PanicError(err)
diff --git a/rest/matter_model.go b/rest/matter_model.go
index 6635a60..3948039 100644
--- a/rest/matter_model.go
+++ b/rest/matter_model.go
@@ -29,11 +29,19 @@ func (Matter) TableName() string {
return TABLE_PREFIX + "matter"
}
+
// 获取该Matter的绝对路径。path代表的是相对路径。
func (this *Matter) AbsolutePath() string {
return GetUserFileRootDir(this.Username) + this.Path
}
+
+// 获取该Matter的MimeType
+func (this *Matter) MimeType() string {
+ return GetMimeType(GetExtension(this.Name))
+}
+
+
//创建一个 ROOT 的matter,主要用于统一化处理移动复制等内容。
func NewRootMatter(user *User) *Matter {
matter := &Matter{}