From c673e7c7e7446a1f0c089621e8fbd8c6a14bda43 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 21 Jan 2020 22:36:42 +0100 Subject: [PATCH] webdav: replace os.FileInfo with our own type --- client.go | 57 ++++++++++------------------------------------------- fs_local.go | 31 +++++++++++++++++++++++++---- server.go | 44 ++++++++++++++++++++--------------------- webdav.go | 13 ++++++++++++ 4 files changed, 71 insertions(+), 74 deletions(-) diff --git a/client.go b/client.go index 8b7542e..bd4be82 100644 --- a/client.go +++ b/client.go @@ -4,8 +4,6 @@ import ( "fmt" "io" "net/http" - "os" - "path" "time" "github.com/emersion/go-webdav/internal" @@ -47,27 +45,20 @@ func (c *Client) FindCurrentUserPrincipal() (string, error) { return prop.Href, nil } -type fileInfo struct { - filename string - size int64 - modTime time.Time - isDir bool -} - -func fileInfoFromResponse(resp *internal.Response) (*fileInfo, error) { +func fileInfoFromResponse(resp *internal.Response) (*FileInfo, error) { href, err := resp.Href() if err != nil { return nil, err } - filename, _ := path.Split(href) - fi := &fileInfo{filename: filename} + + fi := &FileInfo{Href: href} var resType internal.ResourceType if err := resp.DecodeProp(&resType); err != nil { return nil, err } if resType.Is(internal.CollectionName) { - fi.isDir = true + fi.IsDir = true } else { var getLen internal.GetContentLength var getMod internal.GetLastModified @@ -75,41 +66,13 @@ func fileInfoFromResponse(resp *internal.Response) (*fileInfo, error) { return nil, err } - fi.size = getLen.Length - fi.modTime = time.Time(getMod.LastModified) + fi.Size = getLen.Length + fi.ModTime = time.Time(getMod.LastModified) } return fi, nil } -func (fi *fileInfo) Name() string { - return fi.filename -} - -func (fi *fileInfo) Size() int64 { - return fi.size -} - -func (fi *fileInfo) Mode() os.FileMode { - if fi.isDir { - return os.ModePerm | os.ModeDir - } else { - return os.ModePerm - } -} - -func (fi *fileInfo) ModTime() time.Time { - return fi.modTime -} - -func (fi *fileInfo) IsDir() bool { - return fi.isDir -} - -func (fi *fileInfo) Sys() interface{} { - return nil -} - // TODO: getetag, getcontenttype var fileInfoPropfind = internal.NewPropNamePropfind( internal.ResourceTypeName, @@ -117,7 +80,7 @@ var fileInfoPropfind = internal.NewPropNamePropfind( internal.GetLastModifiedName, ) -func (c *Client) Stat(name string) (os.FileInfo, error) { +func (c *Client) Stat(name string) (*FileInfo, error) { resp, err := c.ic.PropfindFlat(name, fileInfoPropfind) if err != nil { return nil, err @@ -139,7 +102,7 @@ func (c *Client) Open(name string) (io.ReadCloser, error) { return resp.Body, nil } -func (c *Client) Readdir(name string) ([]os.FileInfo, error) { +func (c *Client) Readdir(name string) ([]FileInfo, error) { // TODO: filter out the directory we're listing ms, err := c.ic.Propfind(name, internal.DepthOne, fileInfoPropfind) @@ -147,13 +110,13 @@ func (c *Client) Readdir(name string) ([]os.FileInfo, error) { return nil, err } - l := make([]os.FileInfo, 0, len(ms.Responses)) + l := make([]FileInfo, 0, len(ms.Responses)) for _, resp := range ms.Responses { fi, err := fileInfoFromResponse(&resp) if err != nil { return l, err } - l = append(l, fi) + l = append(l, *fi) } return l, nil diff --git a/fs_local.go b/fs_local.go index a6d9b2d..9620deb 100644 --- a/fs_local.go +++ b/fs_local.go @@ -32,15 +32,28 @@ func (fs LocalFileSystem) Open(name string) (io.ReadCloser, error) { return os.Open(p) } -func (fs LocalFileSystem) Stat(name string) (os.FileInfo, error) { +func fileInfoFromOS(href string, fi os.FileInfo) *FileInfo { + return &FileInfo{ + Href: href, + Size: fi.Size(), + ModTime: fi.ModTime(), + IsDir: fi.IsDir(), + } +} + +func (fs LocalFileSystem) Stat(name string) (*FileInfo, error) { p, err := fs.path(name) if err != nil { return nil, err } - return os.Stat(p) + fi, err := os.Stat(p) + if err != nil { + return nil, err + } + return fileInfoFromOS(name, fi), nil } -func (fs LocalFileSystem) Readdir(name string) ([]os.FileInfo, error) { +func (fs LocalFileSystem) Readdir(name string) ([]FileInfo, error) { p, err := fs.path(name) if err != nil { return nil, err @@ -50,7 +63,17 @@ func (fs LocalFileSystem) Readdir(name string) ([]os.FileInfo, error) { return nil, err } defer f.Close() - return f.Readdir(-1) + + fis, err := f.Readdir(-1) + if err != nil { + return nil, err + } + + l := make([]FileInfo, len(fis)) + for i, fi := range fis { + l[i] = *fileInfoFromOS(path.Join(name, fi.Name()), fi) + } + return l, nil } func (fs LocalFileSystem) Create(name string) (io.WriteCloser, error) { diff --git a/server.go b/server.go index 2f9bdc7..4fc3b00 100644 --- a/server.go +++ b/server.go @@ -5,7 +5,6 @@ import ( "io" "mime" "net/http" - "net/url" "os" "path" "strconv" @@ -16,8 +15,8 @@ import ( // FileSystem is a WebDAV server backend. type FileSystem interface { Open(name string) (io.ReadCloser, error) - Stat(name string) (os.FileInfo, error) - Readdir(name string) ([]os.FileInfo, error) + Stat(name string) (*FileInfo, error) + Readdir(name string) ([]FileInfo, error) Create(name string) (io.WriteCloser, error) RemoveAll(name string) error Mkdir(name string) error @@ -53,7 +52,7 @@ func (b *backend) Options(r *http.Request) ([]string, error) { return nil, err } - if fi.IsDir() { + if fi.IsDir { return []string{ http.MethodOptions, http.MethodDelete, @@ -79,7 +78,7 @@ func (b *backend) HeadGet(w http.ResponseWriter, r *http.Request) error { } else if err != nil { return err } - if fi.IsDir() { + if fi.IsDir { return &internal.HTTPError{Code: http.StatusMethodNotAllowed} } @@ -91,7 +90,7 @@ func (b *backend) HeadGet(w http.ResponseWriter, r *http.Request) error { if rs, ok := f.(io.ReadSeeker); ok { // If it's an io.Seeker, use http.ServeContent which supports ranges - http.ServeContent(w, r, r.URL.Path, fi.ModTime(), rs) + http.ServeContent(w, r, r.URL.Path, fi.ModTime, rs) } else { // TODO: fallback to http.DetectContentType t := mime.TypeByExtension(path.Ext(r.URL.Path)) @@ -99,11 +98,11 @@ func (b *backend) HeadGet(w http.ResponseWriter, r *http.Request) error { w.Header().Set("Content-Type", t) } - if modTime := fi.ModTime(); !modTime.IsZero() { - w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) + if !fi.ModTime.IsZero() { + w.Header().Set("Last-Modified", fi.ModTime.UTC().Format(http.TimeFormat)) } - w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10)) + w.Header().Set("Content-Length", strconv.FormatInt(fi.Size, 10)) if r.Method != http.MethodHead { io.Copy(w, f) @@ -121,35 +120,35 @@ func (b *backend) Propfind(r *http.Request, propfind *internal.Propfind, depth i } var resps []internal.Response - if err := b.propfind(propfind, r.URL.Path, fi, depth, &resps); err != nil { + if err := b.propfind(propfind, fi, depth, &resps); err != nil { return nil, err } return internal.NewMultistatus(resps...), nil } -func (b *backend) propfind(propfind *internal.Propfind, name string, fi os.FileInfo, depth internal.Depth, resps *[]internal.Response) error { +func (b *backend) propfind(propfind *internal.Propfind, fi *FileInfo, depth internal.Depth, resps *[]internal.Response) error { // TODO: use partial error Response on error - resp, err := b.propfindFile(propfind, name, fi) + resp, err := b.propfindFile(propfind, fi) if err != nil { return err } *resps = append(*resps, *resp) - if depth != internal.DepthZero && fi.IsDir() { + if depth != internal.DepthZero && fi.IsDir { childDepth := depth if depth == internal.DepthOne { childDepth = internal.DepthZero } - children, err := b.FileSystem.Readdir(name) + children, err := b.FileSystem.Readdir(fi.Href) if err != nil { return err } for _, child := range children { - if err := b.propfind(propfind, path.Join(name, child.Name()), child, childDepth, resps); err != nil { + if err := b.propfind(propfind, &child, childDepth, resps); err != nil { return err } } @@ -158,23 +157,23 @@ func (b *backend) propfind(propfind *internal.Propfind, name string, fi os.FileI return nil } -func (b *backend) propfindFile(propfind *internal.Propfind, name string, fi os.FileInfo) (*internal.Response, error) { +func (b *backend) propfindFile(propfind *internal.Propfind, fi *FileInfo) (*internal.Response, error) { props := make(map[xml.Name]internal.PropfindFunc) props[internal.ResourceTypeName] = func(*internal.RawXMLValue) (interface{}, error) { var types []xml.Name - if fi.IsDir() { + if fi.IsDir { types = append(types, internal.CollectionName) } return internal.NewResourceType(types...), nil } - if !fi.IsDir() { + if !fi.IsDir { props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) { - return &internal.GetContentLength{Length: fi.Size()}, nil + return &internal.GetContentLength{Length: fi.Size}, nil } props[internal.GetContentTypeName] = func(*internal.RawXMLValue) (interface{}, error) { - t := mime.TypeByExtension(path.Ext(name)) + t := mime.TypeByExtension(path.Ext(fi.Href)) if t == "" { // TODO: use http.DetectContentType return nil, &internal.HTTPError{Code: http.StatusNotFound} @@ -182,13 +181,12 @@ func (b *backend) propfindFile(propfind *internal.Propfind, name string, fi os.F return &internal.GetContentType{Type: t}, nil } props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) { - return &internal.GetLastModified{LastModified: internal.Time(fi.ModTime())}, nil + return &internal.GetLastModified{LastModified: internal.Time(fi.ModTime)}, nil } // TODO: getetag } - u := url.URL{Path: name} - return internal.NewPropfindResponse(u.String(), propfind, props) + return internal.NewPropfindResponse(fi.Href, propfind, props) } func (b *backend) Put(r *http.Request) error { diff --git a/webdav.go b/webdav.go index 0699e62..ac90ba9 100644 --- a/webdav.go +++ b/webdav.go @@ -2,3 +2,16 @@ // // WebDAV is defined in RFC 4918. package webdav + +import ( + "time" +) + +// TODO: add ETag, MIMEType to FileInfo + +type FileInfo struct { + Href string + Size int64 + ModTime time.Time + IsDir bool +}