|
|
|
|
|
|
|
|
|
|
|
package webdav |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"fmt" |
|
"net/http" |
|
"net/url" |
|
"os" |
|
"path" |
|
"strings" |
|
"time" |
|
|
|
"github.com/alist-org/alist/v3/internal/stream" |
|
|
|
"github.com/alist-org/alist/v3/internal/errs" |
|
"github.com/alist-org/alist/v3/internal/fs" |
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/internal/sign" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
"github.com/alist-org/alist/v3/server/common" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
type Handler struct { |
|
|
|
Prefix string |
|
|
|
LockSystem LockSystem |
|
|
|
|
|
Logger func(*http.Request, error) |
|
} |
|
|
|
func (h *Handler) stripPrefix(p string) (string, int, error) { |
|
if h.Prefix == "" { |
|
return p, http.StatusOK, nil |
|
} |
|
if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) { |
|
return r, http.StatusOK, nil |
|
} |
|
return p, http.StatusNotFound, errPrefixMismatch |
|
} |
|
|
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
|
status, err := http.StatusBadRequest, errUnsupportedMethod |
|
brw := newBufferedResponseWriter() |
|
useBufferedWriter := true |
|
if h.LockSystem == nil { |
|
status, err = http.StatusInternalServerError, errNoLockSystem |
|
} else { |
|
switch r.Method { |
|
case "OPTIONS": |
|
status, err = h.handleOptions(brw, r) |
|
case "GET", "HEAD", "POST": |
|
useBufferedWriter = false |
|
status, err = h.handleGetHeadPost(w, r) |
|
case "DELETE": |
|
status, err = h.handleDelete(brw, r) |
|
case "PUT": |
|
status, err = h.handlePut(brw, r) |
|
case "MKCOL": |
|
status, err = h.handleMkcol(brw, r) |
|
case "COPY", "MOVE": |
|
status, err = h.handleCopyMove(brw, r) |
|
case "LOCK": |
|
status, err = h.handleLock(brw, r) |
|
case "UNLOCK": |
|
status, err = h.handleUnlock(brw, r) |
|
case "PROPFIND": |
|
status, err = h.handlePropfind(brw, r) |
|
|
|
if err != nil { |
|
status = http.StatusNotFound |
|
} |
|
case "PROPPATCH": |
|
status, err = h.handleProppatch(brw, r) |
|
} |
|
} |
|
|
|
if status != 0 { |
|
w.WriteHeader(status) |
|
if status != http.StatusNoContent { |
|
w.Write([]byte(StatusText(status))) |
|
} |
|
} else if useBufferedWriter { |
|
brw.WriteToResponse(w) |
|
} |
|
if h.Logger != nil && err != nil { |
|
h.Logger(r, err) |
|
} |
|
} |
|
|
|
func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) { |
|
token, err = h.LockSystem.Create(now, LockDetails{ |
|
Root: root, |
|
Duration: infiniteTimeout, |
|
ZeroDepth: true, |
|
}) |
|
if err != nil { |
|
if err == ErrLocked { |
|
return "", StatusLocked, err |
|
} |
|
return "", http.StatusInternalServerError, err |
|
} |
|
return token, 0, nil |
|
} |
|
|
|
func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) { |
|
hdr := r.Header.Get("If") |
|
if hdr == "" { |
|
|
|
|
|
|
|
|
|
|
|
now, srcToken, dstToken := time.Now(), "", "" |
|
if src != "" { |
|
srcToken, status, err = h.lock(now, src) |
|
if err != nil { |
|
return nil, status, err |
|
} |
|
} |
|
if dst != "" { |
|
dstToken, status, err = h.lock(now, dst) |
|
if err != nil { |
|
if srcToken != "" { |
|
h.LockSystem.Unlock(now, srcToken) |
|
} |
|
return nil, status, err |
|
} |
|
} |
|
|
|
return func() { |
|
if dstToken != "" { |
|
h.LockSystem.Unlock(now, dstToken) |
|
} |
|
if srcToken != "" { |
|
h.LockSystem.Unlock(now, srcToken) |
|
} |
|
}, 0, nil |
|
} |
|
|
|
ih, ok := parseIfHeader(hdr) |
|
if !ok { |
|
return nil, http.StatusBadRequest, errInvalidIfHeader |
|
} |
|
|
|
for _, l := range ih.lists { |
|
lsrc := l.resourceTag |
|
if lsrc == "" { |
|
lsrc = src |
|
} else { |
|
u, err := url.Parse(lsrc) |
|
if err != nil { |
|
continue |
|
} |
|
if u.Host != r.Host { |
|
continue |
|
} |
|
lsrc, status, err = h.stripPrefix(u.Path) |
|
if err != nil { |
|
return nil, status, err |
|
} |
|
} |
|
release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...) |
|
if err == ErrConfirmationFailed { |
|
continue |
|
} |
|
if err != nil { |
|
return nil, http.StatusInternalServerError, err |
|
} |
|
return release, 0, nil |
|
} |
|
|
|
|
|
|
|
|
|
return nil, http.StatusPreconditionFailed, ErrLocked |
|
} |
|
|
|
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return 403, err |
|
} |
|
allow := "OPTIONS, LOCK, PUT, MKCOL" |
|
if fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err == nil { |
|
if fi.IsDir() { |
|
allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND" |
|
} else { |
|
allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT" |
|
} |
|
} |
|
w.Header().Set("Allow", allow) |
|
|
|
w.Header().Set("DAV", "1, 2") |
|
|
|
w.Header().Set("MS-Author-Via", "DAV") |
|
return 0, nil |
|
} |
|
|
|
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return http.StatusForbidden, err |
|
} |
|
fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{}) |
|
if err != nil { |
|
return http.StatusNotFound, err |
|
} |
|
etag, err := findETag(ctx, h.LockSystem, reqPath, fi) |
|
if err != nil { |
|
return http.StatusInternalServerError, err |
|
} |
|
w.Header().Set("ETag", etag) |
|
if r.Method == http.MethodHead { |
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.GetSize())) |
|
return http.StatusOK, nil |
|
} |
|
if fi.IsDir() { |
|
return http.StatusMethodNotAllowed, nil |
|
} |
|
|
|
storage, _ := fs.GetStorage(reqPath, &fs.GetStoragesArgs{}) |
|
downProxyUrl := storage.GetStorage().DownProxyUrl |
|
if storage.GetStorage().WebdavNative() || (storage.GetStorage().WebdavProxy() && downProxyUrl == "") { |
|
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header, HttpReq: r}) |
|
if err != nil { |
|
return http.StatusInternalServerError, err |
|
} |
|
err = common.Proxy(w, r, link, fi) |
|
if err != nil { |
|
log.Errorf("webdav proxy error: %+v", err) |
|
return http.StatusInternalServerError, err |
|
} |
|
} else if storage.GetStorage().WebdavProxy() && downProxyUrl != "" { |
|
u := fmt.Sprintf("%s%s?sign=%s", |
|
strings.Split(downProxyUrl, "\n")[0], |
|
utils.EncodePath(reqPath, true), |
|
sign.Sign(reqPath)) |
|
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate") |
|
http.Redirect(w, r, u, http.StatusFound) |
|
} else { |
|
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{IP: utils.ClientIP(r), Header: r.Header, HttpReq: r}) |
|
if err != nil { |
|
return http.StatusInternalServerError, err |
|
} |
|
http.Redirect(w, r, link.URL, http.StatusFound) |
|
} |
|
return 0, nil |
|
} |
|
|
|
func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
release, status, err := h.confirmLocks(r, reqPath, "") |
|
if err != nil { |
|
return status, err |
|
} |
|
defer release() |
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return 403, err |
|
} |
|
|
|
|
|
|
|
|
|
|
|
if _, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err != nil { |
|
if errs.IsObjectNotFound(err) { |
|
return http.StatusNotFound, err |
|
} |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
if err := fs.Remove(ctx, reqPath); err != nil { |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
|
|
return http.StatusNoContent, nil |
|
} |
|
|
|
func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
if reqPath == "" { |
|
return http.StatusMethodNotAllowed, nil |
|
} |
|
release, status, err := h.confirmLocks(r, reqPath, "") |
|
if err != nil { |
|
return status, err |
|
} |
|
defer release() |
|
|
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return http.StatusForbidden, err |
|
} |
|
obj := model.Object{ |
|
Name: path.Base(reqPath), |
|
Size: r.ContentLength, |
|
Modified: h.getModTime(r), |
|
Ctime: h.getCreateTime(r), |
|
} |
|
stream := &stream.FileStream{ |
|
Obj: &obj, |
|
Reader: r.Body, |
|
Mimetype: r.Header.Get("Content-Type"), |
|
} |
|
if stream.Mimetype == "" { |
|
stream.Mimetype = utils.GetMimeType(reqPath) |
|
} |
|
err = fs.PutDirectly(ctx, path.Dir(reqPath), stream) |
|
if errs.IsNotFoundError(err) { |
|
return http.StatusNotFound, err |
|
} |
|
|
|
_ = r.Body.Close() |
|
_ = stream.Close() |
|
|
|
if err != nil { |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{}) |
|
if err != nil { |
|
fi = &obj |
|
} |
|
etag, err := findETag(ctx, h.LockSystem, reqPath, fi) |
|
if err != nil { |
|
return http.StatusInternalServerError, err |
|
} |
|
w.Header().Set("ETag", etag) |
|
return http.StatusCreated, nil |
|
} |
|
|
|
func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
release, status, err := h.confirmLocks(r, reqPath, "") |
|
if err != nil { |
|
return status, err |
|
} |
|
defer release() |
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return 403, err |
|
} |
|
|
|
if r.ContentLength > 0 { |
|
return http.StatusUnsupportedMediaType, nil |
|
} |
|
|
|
|
|
|
|
if _, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err == nil { |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
|
|
|
|
reqDir := path.Dir(reqPath) |
|
if _, err := fs.Get(ctx, reqDir, &fs.GetArgs{}); err != nil { |
|
if errs.IsObjectNotFound(err) { |
|
return http.StatusConflict, err |
|
} |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
if err := fs.MakeDir(ctx, reqPath); err != nil { |
|
if os.IsNotExist(err) { |
|
return http.StatusConflict, err |
|
} |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
return http.StatusCreated, nil |
|
} |
|
|
|
func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
hdr := r.Header.Get("Destination") |
|
if hdr == "" { |
|
return http.StatusBadRequest, errInvalidDestination |
|
} |
|
u, err := url.Parse(hdr) |
|
if err != nil { |
|
return http.StatusBadRequest, errInvalidDestination |
|
} |
|
if u.Host != "" && u.Host != r.Host { |
|
return http.StatusBadGateway, errInvalidDestination |
|
} |
|
|
|
src, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
|
|
dst, status, err := h.stripPrefix(u.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
|
|
if dst == "" { |
|
return http.StatusBadGateway, errInvalidDestination |
|
} |
|
if dst == src { |
|
return http.StatusForbidden, errDestinationEqualsSource |
|
} |
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
src, err = user.JoinPath(src) |
|
if err != nil { |
|
return 403, err |
|
} |
|
dst, err = user.JoinPath(dst) |
|
if err != nil { |
|
return 403, err |
|
} |
|
|
|
if r.Method == "COPY" { |
|
|
|
|
|
|
|
|
|
|
|
release, status, err := h.confirmLocks(r, "", dst) |
|
if err != nil { |
|
return status, err |
|
} |
|
defer release() |
|
|
|
|
|
|
|
depth := infiniteDepth |
|
if hdr := r.Header.Get("Depth"); hdr != "" { |
|
depth = parseDepth(hdr) |
|
if depth != 0 && depth != infiniteDepth { |
|
|
|
|
|
return http.StatusBadRequest, errInvalidDepth |
|
} |
|
} |
|
return copyFiles(ctx, src, dst, r.Header.Get("Overwrite") != "F") |
|
} |
|
|
|
release, status, err := h.confirmLocks(r, src, dst) |
|
if err != nil { |
|
return status, err |
|
} |
|
defer release() |
|
|
|
|
|
|
|
|
|
if hdr := r.Header.Get("Depth"); hdr != "" { |
|
if parseDepth(hdr) != infiniteDepth { |
|
return http.StatusBadRequest, errInvalidDepth |
|
} |
|
} |
|
return moveFiles(ctx, src, dst, r.Header.Get("Overwrite") == "T") |
|
} |
|
|
|
func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) { |
|
duration, err := parseTimeout(r.Header.Get("Timeout")) |
|
if err != nil { |
|
return http.StatusBadRequest, err |
|
} |
|
li, status, err := readLockInfo(r.Body) |
|
if err != nil { |
|
return status, err |
|
} |
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
token, ld, now, created := "", LockDetails{}, time.Now(), false |
|
if li == (lockInfo{}) { |
|
|
|
ih, ok := parseIfHeader(r.Header.Get("If")) |
|
if !ok { |
|
return http.StatusBadRequest, errInvalidIfHeader |
|
} |
|
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 { |
|
token = ih.lists[0].conditions[0].Token |
|
} |
|
if token == "" { |
|
return http.StatusBadRequest, errInvalidLockToken |
|
} |
|
ld, err = h.LockSystem.Refresh(now, token, duration) |
|
if err != nil { |
|
if err == ErrNoSuchLock { |
|
return http.StatusPreconditionFailed, err |
|
} |
|
return http.StatusInternalServerError, err |
|
} |
|
|
|
} else { |
|
|
|
|
|
depth := infiniteDepth |
|
if hdr := r.Header.Get("Depth"); hdr != "" { |
|
depth = parseDepth(hdr) |
|
if depth != 0 && depth != infiniteDepth { |
|
|
|
|
|
return http.StatusBadRequest, errInvalidDepth |
|
} |
|
} |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return 403, err |
|
} |
|
ld = LockDetails{ |
|
Root: reqPath, |
|
Duration: duration, |
|
OwnerXML: li.Owner.InnerXML, |
|
ZeroDepth: depth == 0, |
|
} |
|
token, err = h.LockSystem.Create(now, ld) |
|
if err != nil { |
|
if err == ErrLocked { |
|
return StatusLocked, err |
|
} |
|
return http.StatusInternalServerError, err |
|
} |
|
defer func() { |
|
if retErr != nil { |
|
h.LockSystem.Unlock(now, token) |
|
} |
|
}() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Lock-Token", "<"+token+">") |
|
} |
|
|
|
w.Header().Set("Content-Type", "application/xml; charset=utf-8") |
|
if created { |
|
|
|
|
|
|
|
w.WriteHeader(http.StatusCreated) |
|
} |
|
writeLockInfo(w, token, ld) |
|
return 0, nil |
|
} |
|
|
|
func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
|
|
|
|
t := r.Header.Get("Lock-Token") |
|
if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' { |
|
return http.StatusBadRequest, errInvalidLockToken |
|
} |
|
t = t[1 : len(t)-1] |
|
|
|
switch err = h.LockSystem.Unlock(time.Now(), t); err { |
|
case nil: |
|
return http.StatusNoContent, err |
|
case ErrForbidden: |
|
return http.StatusForbidden, err |
|
case ErrLocked: |
|
return StatusLocked, err |
|
case ErrNoSuchLock: |
|
return http.StatusConflict, err |
|
default: |
|
return http.StatusInternalServerError, err |
|
} |
|
} |
|
|
|
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
ctx := r.Context() |
|
userAgent := r.Header.Get("User-Agent") |
|
ctx = context.WithValue(ctx, "userAgent", userAgent) |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return 403, err |
|
} |
|
fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{}) |
|
if err != nil { |
|
if errs.IsNotFoundError(err) { |
|
return http.StatusNotFound, err |
|
} |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
depth := infiniteDepth |
|
if hdr := r.Header.Get("Depth"); hdr != "" { |
|
depth = parseDepth(hdr) |
|
if depth == invalidDepth { |
|
return http.StatusBadRequest, errInvalidDepth |
|
} |
|
} |
|
pf, status, err := readPropfind(r.Body) |
|
if err != nil { |
|
return status, err |
|
} |
|
|
|
mw := multistatusWriter{w: w} |
|
|
|
walkFn := func(reqPath string, info model.Obj, err error) error { |
|
if err != nil { |
|
return err |
|
} |
|
var pstats []Propstat |
|
if pf.Propname != nil { |
|
pnames, err := propnames(ctx, h.LockSystem, info) |
|
if err != nil { |
|
return err |
|
} |
|
pstat := Propstat{Status: http.StatusOK} |
|
for _, xmlname := range pnames { |
|
pstat.Props = append(pstat.Props, Property{XMLName: xmlname}) |
|
} |
|
pstats = append(pstats, pstat) |
|
} else if pf.Allprop != nil { |
|
pstats, err = allprop(ctx, h.LockSystem, info, pf.Prop) |
|
} else { |
|
pstats, err = props(ctx, h.LockSystem, info, pf.Prop) |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
href := path.Join(h.Prefix, strings.TrimPrefix(reqPath, user.BasePath)) |
|
if href != "/" && info.IsDir() { |
|
href += "/" |
|
} |
|
return mw.write(makePropstatResponse(href, pstats)) |
|
} |
|
|
|
walkErr := walkFS(ctx, depth, reqPath, fi, walkFn) |
|
closeErr := mw.close() |
|
if walkErr != nil { |
|
return http.StatusInternalServerError, walkErr |
|
} |
|
if closeErr != nil { |
|
return http.StatusInternalServerError, closeErr |
|
} |
|
return 0, nil |
|
} |
|
|
|
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) { |
|
reqPath, status, err := h.stripPrefix(r.URL.Path) |
|
if err != nil { |
|
return status, err |
|
} |
|
release, status, err := h.confirmLocks(r, reqPath, "") |
|
if err != nil { |
|
return status, err |
|
} |
|
defer release() |
|
|
|
ctx := r.Context() |
|
user := ctx.Value("user").(*model.User) |
|
reqPath, err = user.JoinPath(reqPath) |
|
if err != nil { |
|
return 403, err |
|
} |
|
if _, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err != nil { |
|
if errs.IsObjectNotFound(err) { |
|
return http.StatusNotFound, err |
|
} |
|
return http.StatusMethodNotAllowed, err |
|
} |
|
patches, status, err := readProppatch(r.Body) |
|
if err != nil { |
|
return status, err |
|
} |
|
pstats, err := patch(ctx, h.LockSystem, reqPath, patches) |
|
if err != nil { |
|
return http.StatusInternalServerError, err |
|
} |
|
mw := multistatusWriter{w: w} |
|
writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats)) |
|
closeErr := mw.close() |
|
if writeErr != nil { |
|
return http.StatusInternalServerError, writeErr |
|
} |
|
if closeErr != nil { |
|
return http.StatusInternalServerError, closeErr |
|
} |
|
return 0, nil |
|
} |
|
|
|
func makePropstatResponse(href string, pstats []Propstat) *response { |
|
resp := response{ |
|
Href: []string{(&url.URL{Path: href}).EscapedPath()}, |
|
Propstat: make([]propstat, 0, len(pstats)), |
|
} |
|
for _, p := range pstats { |
|
var xmlErr *xmlError |
|
if p.XMLError != "" { |
|
xmlErr = &xmlError{InnerXML: []byte(p.XMLError)} |
|
} |
|
resp.Propstat = append(resp.Propstat, propstat{ |
|
Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)), |
|
Prop: p.Props, |
|
ResponseDescription: p.ResponseDescription, |
|
Error: xmlErr, |
|
}) |
|
} |
|
return &resp |
|
} |
|
|
|
const ( |
|
infiniteDepth = -1 |
|
invalidDepth = -2 |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func parseDepth(s string) int { |
|
switch s { |
|
case "0": |
|
return 0 |
|
case "1": |
|
return 1 |
|
case "infinity": |
|
return infiniteDepth |
|
} |
|
return invalidDepth |
|
} |
|
|
|
|
|
const ( |
|
StatusMulti = 207 |
|
StatusUnprocessableEntity = 422 |
|
StatusLocked = 423 |
|
StatusFailedDependency = 424 |
|
StatusInsufficientStorage = 507 |
|
) |
|
|
|
func StatusText(code int) string { |
|
switch code { |
|
case StatusMulti: |
|
return "Multi-Status" |
|
case StatusUnprocessableEntity: |
|
return "Unprocessable Entity" |
|
case StatusLocked: |
|
return "Locked" |
|
case StatusFailedDependency: |
|
return "Failed Dependency" |
|
case StatusInsufficientStorage: |
|
return "Insufficient Storage" |
|
} |
|
return http.StatusText(code) |
|
} |
|
|
|
var ( |
|
errDestinationEqualsSource = errors.New("webdav: destination equals source") |
|
errDirectoryNotEmpty = errors.New("webdav: directory not empty") |
|
errInvalidDepth = errors.New("webdav: invalid depth") |
|
errInvalidDestination = errors.New("webdav: invalid destination") |
|
errInvalidIfHeader = errors.New("webdav: invalid If header") |
|
errInvalidLockInfo = errors.New("webdav: invalid lock info") |
|
errInvalidLockToken = errors.New("webdav: invalid lock token") |
|
errInvalidPropfind = errors.New("webdav: invalid propfind") |
|
errInvalidProppatch = errors.New("webdav: invalid proppatch") |
|
errInvalidResponse = errors.New("webdav: invalid response") |
|
errInvalidTimeout = errors.New("webdav: invalid timeout") |
|
errNoFileSystem = errors.New("webdav: no file system") |
|
errNoLockSystem = errors.New("webdav: no lock system") |
|
errNotADirectory = errors.New("webdav: not a directory") |
|
errPrefixMismatch = errors.New("webdav: prefix mismatch") |
|
errRecursionTooDeep = errors.New("webdav: recursion too deep") |
|
errUnsupportedLockInfo = errors.New("webdav: unsupported lock info") |
|
errUnsupportedMethod = errors.New("webdav: unsupported method") |
|
) |
|
|