go-webdav/internal/server.go

317 lines
7.5 KiB
Go
Raw Permalink Normal View History

2020-01-17 10:30:42 +00:00
package internal
import (
2020-01-17 10:41:44 +00:00
"encoding/xml"
"errors"
2020-01-17 10:30:42 +00:00
"fmt"
"io"
2020-01-17 10:30:42 +00:00
"mime"
2020-01-17 10:41:44 +00:00
"net/http"
2020-01-22 10:43:36 +00:00
"net/url"
2020-01-17 10:41:44 +00:00
"strings"
2020-01-17 10:30:42 +00:00
)
func ServeError(w http.ResponseWriter, err error) {
code := http.StatusInternalServerError
var httpErr *HTTPError
if errors.As(err, &httpErr) {
code = httpErr.Code
}
var errElt *Error
if errors.As(err, &errElt) {
w.WriteHeader(code)
ServeXML(w).Encode(errElt)
return
}
http.Error(w, err.Error(), code)
}
func isContentXML(h http.Header) bool {
t, _, _ := mime.ParseMediaType(h.Get("Content-Type"))
return t == "application/xml" || t == "text/xml"
}
func DecodeXMLRequest(r *http.Request, v interface{}) error {
if !isContentXML(r.Header) {
return HTTPErrorf(http.StatusBadRequest, "webdav: expected application/xml request")
}
if err := xml.NewDecoder(r.Body).Decode(v); err != nil {
return &HTTPError{http.StatusBadRequest, err}
}
return nil
}
2024-02-07 16:22:41 +00:00
func IsRequestBodyEmpty(r *http.Request) bool {
_, err := r.Body.Read(nil)
return err == io.EOF
}
func ServeXML(w http.ResponseWriter) *xml.Encoder {
w.Header().Add("Content-Type", "application/xml; charset=\"utf-8\"")
w.Write([]byte(xml.Header))
return xml.NewEncoder(w)
}
func ServeMultiStatus(w http.ResponseWriter, ms *MultiStatus) error {
// TODO: streaming
w.WriteHeader(http.StatusMultiStatus)
return ServeXML(w).Encode(ms)
}
2020-01-17 10:30:42 +00:00
type Backend interface {
Options(r *http.Request) (caps []string, allow []string, err error)
2020-01-17 10:30:42 +00:00
HeadGet(w http.ResponseWriter, r *http.Request) error
PropFind(r *http.Request, pf *PropFind, depth Depth) (*MultiStatus, error)
PropPatch(r *http.Request, pu *PropertyUpdate) (*Response, error)
Put(w http.ResponseWriter, r *http.Request) error
2020-01-21 20:46:01 +00:00
Delete(r *http.Request) error
2020-01-21 21:05:59 +00:00
Mkcol(r *http.Request) error
2020-01-22 12:00:42 +00:00
Copy(r *http.Request, dest *Href, recursive, overwrite bool) (created bool, err error)
2020-01-22 10:43:36 +00:00
Move(r *http.Request, dest *Href, overwrite bool) (created bool, err error)
2020-01-17 10:30:42 +00:00
}
type Handler struct {
Backend Backend
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var err error
if h.Backend == nil {
err = fmt.Errorf("webdav: no backend available")
} else {
switch r.Method {
case http.MethodOptions:
err = h.handleOptions(w, r)
case http.MethodGet, http.MethodHead:
err = h.Backend.HeadGet(w, r)
2020-01-21 20:19:44 +00:00
case http.MethodPut:
err = h.Backend.Put(w, r)
2020-01-21 20:46:01 +00:00
case http.MethodDelete:
// TODO: send a multistatus in case of partial failure
err = h.Backend.Delete(r)
if err == nil {
w.WriteHeader(http.StatusNoContent)
}
2020-01-17 10:30:42 +00:00
case "PROPFIND":
err = h.handlePropfind(w, r)
case "PROPPATCH":
err = h.handleProppatch(w, r)
2020-01-21 21:05:59 +00:00
case "MKCOL":
err = h.Backend.Mkcol(r)
if err == nil {
w.WriteHeader(http.StatusCreated)
}
2020-01-22 12:00:42 +00:00
case "COPY", "MOVE":
err = h.handleCopyMove(w, r)
2020-01-17 10:30:42 +00:00
default:
err = HTTPErrorf(http.StatusMethodNotAllowed, "webdav: unsupported method")
}
}
if err != nil {
ServeError(w, err)
2020-01-17 10:30:42 +00:00
}
}
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) error {
caps, allow, err := h.Backend.Options(r)
2020-01-17 10:41:44 +00:00
if err != nil {
return err
}
caps = append([]string{"1", "3"}, caps...)
2020-01-17 10:41:44 +00:00
w.Header().Add("DAV", strings.Join(caps, ", "))
w.Header().Add("Allow", strings.Join(allow, ", "))
2020-01-17 10:30:42 +00:00
w.WriteHeader(http.StatusNoContent)
return nil
}
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error {
var propfind PropFind
if isContentXML(r.Header) {
if err := DecodeXMLRequest(r, &propfind); err != nil {
return err
}
} else {
var b [1]byte
if _, err := r.Body.Read(b[:]); err != io.EOF {
return HTTPErrorf(http.StatusBadRequest, "webdav: unsupported request body")
}
propfind.AllProp = &struct{}{}
2020-01-17 10:30:42 +00:00
}
depth := DepthInfinity
if s := r.Header.Get("Depth"); s != "" {
var err error
2020-01-17 10:30:42 +00:00
depth, err = ParseDepth(s)
if err != nil {
return &HTTPError{http.StatusBadRequest, err}
}
}
ms, err := h.Backend.PropFind(r, &propfind, depth)
2020-01-17 10:30:42 +00:00
if err != nil {
return err
}
return ServeMultiStatus(w, ms)
2020-01-17 10:30:42 +00:00
}
type PropFindFunc func(raw *RawXMLValue) (interface{}, error)
func PropFindValue(value interface{}) PropFindFunc {
return func(raw *RawXMLValue) (interface{}, error) {
return value, nil
}
}
func NewPropFindResponse(path string, propfind *PropFind, props map[xml.Name]PropFindFunc) (*Response, error) {
resp := &Response{Hrefs: []Href{Href{Path: path}}}
2020-01-17 16:09:44 +00:00
if _, ok := props[ResourceTypeName]; !ok {
props[ResourceTypeName] = PropFindValue(NewResourceType())
2020-01-17 16:09:44 +00:00
}
if propfind.PropName != nil {
for xmlName, _ := range props {
emptyVal := NewRawXMLElement(xmlName, nil, nil)
if err := resp.EncodeProp(http.StatusOK, emptyVal); err != nil {
return nil, err
}
}
} else if propfind.AllProp != nil {
// TODO: add support for propfind.Include
for xmlName, f := range props {
emptyVal := NewRawXMLElement(xmlName, nil, nil)
val, err := f(emptyVal)
code := http.StatusOK
if err != nil {
// TODO: don't throw away error message here
code = HTTPErrorFromError(err).Code
val = emptyVal
}
if err := resp.EncodeProp(code, val); err != nil {
return nil, err
}
}
} else if prop := propfind.Prop; prop != nil {
for _, raw := range prop.Raw {
xmlName, ok := raw.XMLName()
if !ok {
continue
}
emptyVal := NewRawXMLElement(xmlName, nil, nil)
var code int
var val interface{} = emptyVal
f, ok := props[xmlName]
if ok {
if v, err := f(&raw); err != nil {
// TODO: don't throw away error message here
code = HTTPErrorFromError(err).Code
} else {
code = http.StatusOK
val = v
}
} else {
code = http.StatusNotFound
}
if err := resp.EncodeProp(code, val); err != nil {
return nil, err
}
}
} else {
return nil, HTTPErrorf(http.StatusBadRequest, "webdav: request missing propname, allprop or prop element")
}
return resp, nil
}
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) error {
var update PropertyUpdate
if err := DecodeXMLRequest(r, &update); err != nil {
return err
}
resp, err := h.Backend.PropPatch(r, &update)
if err != nil {
return err
}
ms := NewMultiStatus(*resp)
return ServeMultiStatus(w, ms)
}
2020-01-22 10:43:36 +00:00
func parseDestination(h http.Header) (*Href, error) {
destHref := h.Get("Destination")
if destHref == "" {
return nil, HTTPErrorf(http.StatusBadRequest, "webdav: missing Destination header in MOVE request")
}
dest, err := url.Parse(destHref)
if err != nil {
return nil, HTTPErrorf(http.StatusBadRequest, "webdav: marlformed Destination header in MOVE request: %v", err)
}
return (*Href)(dest), nil
}
2020-01-22 12:00:42 +00:00
func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) error {
2020-01-22 10:43:36 +00:00
dest, err := parseDestination(r.Header)
if err != nil {
return err
}
overwrite := true
if s := r.Header.Get("Overwrite"); s != "" {
overwrite, err = ParseOverwrite(s)
if err != nil {
return err
}
}
2020-01-22 12:00:42 +00:00
depth := DepthInfinity
if s := r.Header.Get("Depth"); s != "" {
depth, err = ParseDepth(s)
if err != nil {
return err
}
}
var created bool
if r.Method == "COPY" {
var recursive bool
switch depth {
case DepthZero:
recursive = false
case DepthOne:
return HTTPErrorf(http.StatusBadRequest, `webdav: "Depth: 1" is not supported in COPY request`)
case DepthInfinity:
recursive = true
}
created, err = h.Backend.Copy(r, dest, recursive, overwrite)
} else {
if depth != DepthInfinity {
return HTTPErrorf(http.StatusBadRequest, `webdav: only "Depth: infinity" is accepted in MOVE request`)
}
created, err = h.Backend.Move(r, dest, overwrite)
}
2020-01-22 10:43:36 +00:00
if err != nil {
return err
}
2020-01-22 12:00:42 +00:00
2020-01-22 10:43:36 +00:00
if created {
w.WriteHeader(http.StatusCreated)
} else {
w.WriteHeader(http.StatusNoContent)
}
return nil
}