mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-22 16:24:14 +00:00
226 lines
5.3 KiB
Go
226 lines
5.3 KiB
Go
package internal
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"mime"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type HTTPError struct {
|
|
Code int
|
|
Err error
|
|
}
|
|
|
|
func HTTPErrorFromError(err error) *HTTPError {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if httpErr, ok := err.(*HTTPError); ok {
|
|
return httpErr
|
|
} else {
|
|
return &HTTPError{http.StatusInternalServerError, err}
|
|
}
|
|
}
|
|
|
|
func IsNotFound(err error) bool {
|
|
return HTTPErrorFromError(err).Code == http.StatusNotFound
|
|
}
|
|
|
|
func HTTPErrorf(code int, format string, a ...interface{}) *HTTPError {
|
|
return &HTTPError{code, fmt.Errorf(format, a...)}
|
|
}
|
|
|
|
func (err *HTTPError) Error() string {
|
|
s := fmt.Sprintf("%v %v", err.Code, http.StatusText(err.Code))
|
|
if err.Err != nil {
|
|
return fmt.Sprintf("%v: %v", s, err.Err)
|
|
} else {
|
|
return s
|
|
}
|
|
}
|
|
|
|
func ServeError(w http.ResponseWriter, err error) {
|
|
code := http.StatusInternalServerError
|
|
if httpErr, ok := err.(*HTTPError); ok {
|
|
code = httpErr.Code
|
|
}
|
|
http.Error(w, err.Error(), code)
|
|
}
|
|
|
|
func DecodeXMLRequest(r *http.Request, v interface{}) error {
|
|
t, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
if t != "application/xml" && t != "text/xml" {
|
|
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
|
|
}
|
|
|
|
func ServeXML(w http.ResponseWriter) *xml.Encoder {
|
|
w.Header().Add("Content-Type", "text/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)
|
|
}
|
|
|
|
type Backend interface {
|
|
Options(r *http.Request) ([]string, error)
|
|
HeadGet(w http.ResponseWriter, r *http.Request) error
|
|
Put(r *http.Request) error
|
|
Propfind(r *http.Request, pf *Propfind, depth Depth) (*Multistatus, error)
|
|
}
|
|
|
|
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)
|
|
case http.MethodPut:
|
|
err = h.Backend.Put(r)
|
|
if err == nil {
|
|
// TODO: Last-Modified, ETag, Content-Type if the request has
|
|
// been copied verbatim
|
|
// TODO: Location if the server has mutated the href
|
|
w.WriteHeader(http.StatusCreated)
|
|
}
|
|
case "PROPFIND":
|
|
err = h.handlePropfind(w, r)
|
|
default:
|
|
err = HTTPErrorf(http.StatusMethodNotAllowed, "webdav: unsupported method")
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
code := http.StatusInternalServerError
|
|
if httpErr, ok := err.(*HTTPError); ok {
|
|
code = httpErr.Code
|
|
}
|
|
http.Error(w, err.Error(), code)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) error {
|
|
methods, err := h.Backend.Options(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.Header().Add("Allow", strings.Join(methods, ", "))
|
|
w.Header().Add("DAV", "1, 3")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return nil
|
|
}
|
|
|
|
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error {
|
|
var propfind Propfind
|
|
if err := DecodeXMLRequest(r, &propfind); err != nil {
|
|
return err
|
|
}
|
|
|
|
depth := DepthInfinity
|
|
if s := r.Header.Get("Depth"); s != "" {
|
|
var err error
|
|
depth, err = ParseDepth(s)
|
|
if err != nil {
|
|
return &HTTPError{http.StatusBadRequest, err}
|
|
}
|
|
}
|
|
|
|
ms, err := h.Backend.Propfind(r, &propfind, depth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ServeMultistatus(w, ms)
|
|
}
|
|
|
|
type PropfindFunc func(raw *RawXMLValue) (interface{}, error)
|
|
|
|
func NewPropfindResponse(href string, propfind *Propfind, props map[xml.Name]PropfindFunc) (*Response, error) {
|
|
resp := NewOKResponse(href)
|
|
|
|
if _, ok := props[ResourceTypeName]; !ok {
|
|
props[ResourceTypeName] = func(*RawXMLValue) (interface{}, error) {
|
|
return NewResourceType(), nil
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|