feat: webdav handle
This commit is contained in:
@ -10,11 +10,10 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -103,7 +102,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, LockSystem, string, model.Obj) (string, error)
|
||||
// dir is true if the property applies to directories.
|
||||
dir bool
|
||||
}{
|
||||
@ -166,25 +165,26 @@ 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, ls LockSystem, fi model.Obj, 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
|
||||
//}
|
||||
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
|
||||
}
|
||||
}
|
||||
// ??? what is this for?
|
||||
//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 +196,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, ls, fi.GetName(), fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -214,25 +214,26 @@ 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, ls LockSystem, fi model.Obj) ([]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
|
||||
//}
|
||||
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
|
||||
}
|
||||
}
|
||||
// ??? what is this for?
|
||||
//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 {
|
||||
@ -254,8 +255,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, ls LockSystem, fi model.Obj, include []xml.Name) ([]Propstat, error) {
|
||||
pnames, err := propnames(ctx, ls, fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -269,12 +270,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, ls, fi, 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, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
|
||||
conflict := false
|
||||
loop:
|
||||
for _, patch := range patches {
|
||||
@ -305,26 +306,29 @@ 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
|
||||
}
|
||||
// ------------------------------------------------------------
|
||||
//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 +360,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, ls LockSystem, name string, fi model.Obj) (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, ls LockSystem, name string, fi model.Obj) (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, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
return strconv.FormatInt(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, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
return fi.ModTime().UTC().Format(http.TimeFormat), nil
|
||||
}
|
||||
|
||||
@ -400,33 +404,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()
|
||||
func findContentType(ctx context.Context, ls LockSystem, name string, fi model.Obj) (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))
|
||||
ctype := mime.TypeByExtension(path.Ext(name))
|
||||
if ctype != "" {
|
||||
return ctype, nil
|
||||
}
|
||||
return "application/octet-stream", 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
|
||||
//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
|
||||
}
|
||||
|
||||
// ETager is an optional interface for the os.FileInfo objects
|
||||
@ -447,7 +452,7 @@ 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) {
|
||||
func findETag(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
if do, ok := fi.(ETager); ok {
|
||||
etag, err := do.ETag(ctx)
|
||||
if err != ErrNotImplemented {
|
||||
@ -457,10 +462,10 @@ func findETag(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi
|
||||
// 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
|
||||
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, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
return `` +
|
||||
`<D:lockentry xmlns:D="DAV:">` +
|
||||
`<D:lockscope><D:exclusive/></D:lockscope>` +
|
||||
|
Reference in New Issue
Block a user