Feat: adapt major methods of WebDAV for Cloudreve file system

This commit is contained in:
HFO4
2019-12-17 12:02:50 +08:00
parent 1dac66e632
commit 9fdf2fe7ab
10 changed files with 327 additions and 258 deletions

View File

@@ -10,14 +10,19 @@ import (
"encoding/xml"
"errors"
"fmt"
"io"
"mime"
"github.com/HFO4/cloudreve/pkg/filesystem"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
)
type FileInfo interface {
GetSize() uint64
GetName() string
ModTime() time.Time
IsDir() bool
}
// Proppatch describes a property update instruction as defined in RFC 4918.
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH
type Proppatch struct {
@@ -103,7 +108,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(context.Context, FileSystem, LockSystem, string, os.FileInfo) (string, error)
findFn func(context.Context, *filesystem.FileSystem, LockSystem, string, FileInfo) (string, error)
// dir is true if the property applies to directories.
dir bool
}{
@@ -166,25 +171,10 @@ 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(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
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
func props(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo, pnames []xml.Name) ([]Propstat, error) {
isDir := fi.IsDir()
var deadProps map[xml.Name]Property
if dph, ok := f.(DeadPropsHolder); ok {
deadProps, err = dph.DeadProps()
if err != nil {
return nil, err
}
}
pstatOK := Propstat{Status: http.StatusOK}
pstatNotFound := Propstat{Status: http.StatusNotFound}
@@ -196,7 +186,7 @@ func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pname
}
// 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(ctx, fs, ls, name, fi)
innerXML, err := prop.findFn(ctx, fs, ls, "", fi)
if err != nil {
return nil, err
}
@@ -214,25 +204,10 @@ func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pname
}
// Propnames returns the property names defined for resource name.
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
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
func propnames(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo) ([]xml.Name, error) {
isDir := fi.IsDir()
var deadProps map[xml.Name]Property
if dph, ok := f.(DeadPropsHolder); ok {
deadProps, err = dph.DeadProps()
if err != nil {
return nil, err
}
}
pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
for pn, prop := range liveProps {
@@ -240,9 +215,6 @@ func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) (
pnames = append(pnames, pn)
}
}
for pn := range deadProps {
pnames = append(pnames, pn)
}
return pnames, nil
}
@@ -254,8 +226,8 @@ func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) (
// returned if they are named in 'include'.
//
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
pnames, err := propnames(ctx, fs, ls, name)
func allprop(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, info FileInfo, include []xml.Name) ([]Propstat, error) {
pnames, err := propnames(ctx, fs, ls, info)
if err != nil {
return nil, err
}
@@ -269,12 +241,12 @@ func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, inc
pnames = append(pnames, pn)
}
}
return props(ctx, fs, ls, name, pnames)
return props(ctx, fs, ls, info, pnames)
}
// Patch patches the properties of resource name. The return values are
// constrained in the same manner as DeadPropsHolder.Patch.
func patch(ctx context.Context, fs FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
func patch(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
conflict := false
loop:
for _, patch := range patches {
@@ -305,26 +277,6 @@ loop:
return makePropstats(pstatForbidden, pstatFailedDep), nil
}
f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0)
if err != nil {
return nil, err
}
defer f.Close()
if dph, ok := f.(DeadPropsHolder); ok {
ret, err := dph.Patch(patches)
if err != nil {
return nil, err
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat says that
// "The contents of the prop XML element must only list the names of
// properties to which the result in the status element applies."
for _, pstat := range ret {
for i, p := range pstat.Props {
pstat.Props[i] = Property{XMLName: p.XMLName}
}
}
return ret, nil
}
// The file doesn't implement the optional DeadPropsHolder interface, so
// all patches are forbidden.
pstat := Propstat{Status: http.StatusForbidden}
@@ -356,26 +308,26 @@ func escapeXML(s string) string {
return s
}
func findResourceType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
func findResourceType(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
if fi.IsDir() {
return `<D:collection xmlns:D="DAV:"/>`, nil
}
return "", nil
}
func findDisplayName(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
func findDisplayName(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
if slashClean(name) == "/" {
// Hide the real name of a possibly prefixed root directory.
return "", nil
}
return escapeXML(fi.Name()), nil
return escapeXML(fi.GetName()), nil
}
func findContentLength(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
return strconv.FormatInt(fi.Size(), 10), nil
func findContentLength(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
return strconv.FormatUint(fi.GetSize(), 10), nil
}
func findLastModified(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
func findLastModified(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
return fi.ModTime().UTC().Format(http.TimeFormat), nil
}
@@ -400,33 +352,34 @@ type ContentTyper interface {
ContentType(ctx context.Context) (string, error)
}
func findContentType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
if do, ok := fi.(ContentTyper); ok {
ctype, err := do.ContentType(ctx)
if err != ErrNotImplemented {
return ctype, err
}
}
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
if err != nil {
return "", err
}
defer f.Close()
// This implementation is based on serveContent's code in the standard net/http package.
ctype := mime.TypeByExtension(filepath.Ext(name))
if ctype != "" {
return ctype, nil
}
// Read a chunk to decide between utf-8 text and binary.
var buf [512]byte
n, err := io.ReadFull(f, buf[:])
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return "", err
}
ctype = http.DetectContentType(buf[:n])
// Rewind file.
_, err = f.Seek(0, os.SEEK_SET)
return ctype, err
func findContentType(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
//if do, ok := fi.(ContentTyper); ok {
// ctype, err := do.ContentType(ctx)
// if err != ErrNotImplemented {
// return ctype, err
// }
//}
//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
//if err != nil {
// return "", err
//}
//defer f.Close()
//// This implementation is based on serveContent's code in the standard net/http package.
//ctype := mime.TypeByExtension(filepath.Ext(name))
//if ctype != "" {
// return ctype, nil
//}
//// Read a chunk to decide between utf-8 text and binary.
//var buf [512]byte
//n, err := io.ReadFull(f, buf[:])
//if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
// return "", err
//}
//ctype = http.DetectContentType(buf[:n])
//// Rewind file.
//_, err = f.Seek(0, os.SEEK_SET)
//return ctype, err
return "", nil
}
// ETager is an optional interface for the os.FileInfo objects
@@ -447,20 +400,11 @@ type ETager interface {
ETag(ctx context.Context) (string, error)
}
func findETag(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
if do, ok := fi.(ETager); ok {
etag, err := do.ETag(ctx)
if err != ErrNotImplemented {
return etag, err
}
}
// 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 findETag(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, reqPath string, fi FileInfo) (string, error) {
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.GetSize()), nil
}
func findSupportedLock(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
func findSupportedLock(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
return `` +
`<D:lockentry xmlns:D="DAV:">` +
`<D:lockscope><D:exclusive/></D:lockscope>` +