Spaces:
Running
Running
// Copyright 2014 The Go Authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
package webdav | |
// The XML encoding is covered by Section 14. | |
// http://www.webdav.org/specs/rfc4918.html#xml.element.definitions | |
import ( | |
"bytes" | |
"encoding/xml" | |
"fmt" | |
"io" | |
"net/http" | |
"time" | |
// As of https://go-review.googlesource.com/#/c/12772/ which was submitted | |
// in July 2015, this package uses an internal fork of the standard | |
// library's encoding/xml package, due to changes in the way namespaces | |
// were encoded. Such changes were introduced in the Go 1.5 cycle, but were | |
// rolled back in response to https://github.com/golang/go/issues/11841 | |
// | |
// However, this package's exported API, specifically the Property and | |
// DeadPropsHolder types, need to refer to the standard library's version | |
// of the xml.Name type, as code that imports this package cannot refer to | |
// the internal version. | |
// | |
// This file therefore imports both the internal and external versions, as | |
// ixml and xml, and converts between them. | |
// | |
// In the long term, this package should use the standard library's version | |
// only, and the internal fork deleted, once | |
// https://github.com/golang/go/issues/13400 is resolved. | |
ixml "github.com/alist-org/alist/v3/server/webdav/internal/xml" | |
) | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo | |
type lockInfo struct { | |
XMLName ixml.Name `xml:"lockinfo"` | |
Exclusive *struct{} `xml:"lockscope>exclusive"` | |
Shared *struct{} `xml:"lockscope>shared"` | |
Write *struct{} `xml:"locktype>write"` | |
Owner owner `xml:"owner"` | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner | |
type owner struct { | |
InnerXML string `xml:",innerxml"` | |
} | |
func readLockInfo(r io.Reader) (li lockInfo, status int, err error) { | |
c := &countingReader{r: r} | |
if err = ixml.NewDecoder(c).Decode(&li); err != nil { | |
if err == io.EOF { | |
if c.n == 0 { | |
// An empty body means to refresh the lock. | |
// http://www.webdav.org/specs/rfc4918.html#refreshing-locks | |
return lockInfo{}, 0, nil | |
} | |
err = errInvalidLockInfo | |
} | |
return lockInfo{}, http.StatusBadRequest, err | |
} | |
// We only support exclusive (non-shared) write locks. In practice, these are | |
// the only types of locks that seem to matter. | |
if li.Exclusive == nil || li.Shared != nil || li.Write == nil { | |
return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo | |
} | |
return li, 0, nil | |
} | |
type countingReader struct { | |
n int | |
r io.Reader | |
} | |
func (c *countingReader) Read(p []byte) (int, error) { | |
n, err := c.r.Read(p) | |
c.n += n | |
return n, err | |
} | |
func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) { | |
depth := "infinity" | |
if ld.ZeroDepth { | |
depth = "0" | |
} | |
timeout := ld.Duration / time.Second | |
return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+ | |
"<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+ | |
" <D:locktype><D:write/></D:locktype>\n"+ | |
" <D:lockscope><D:exclusive/></D:lockscope>\n"+ | |
" <D:depth>%s</D:depth>\n"+ | |
" <D:owner>%s</D:owner>\n"+ | |
" <D:timeout>Second-%d</D:timeout>\n"+ | |
" <D:locktoken><D:href>%s</D:href></D:locktoken>\n"+ | |
" <D:lockroot><D:href>%s</D:href></D:lockroot>\n"+ | |
"</D:activelock></D:lockdiscovery></D:prop>", | |
depth, ld.OwnerXML, timeout, escape(token), escape(ld.Root), | |
) | |
} | |
func escape(s string) string { | |
for i := 0; i < len(s); i++ { | |
switch s[i] { | |
case '"', '&', '\'', '<', '>': | |
b := bytes.NewBuffer(nil) | |
ixml.EscapeText(b, []byte(s)) | |
return b.String() | |
} | |
} | |
return s | |
} | |
// Next returns the next token, if any, in the XML stream of d. | |
// RFC 4918 requires to ignore comments, processing instructions | |
// and directives. | |
// http://www.webdav.org/specs/rfc4918.html#property_values | |
// http://www.webdav.org/specs/rfc4918.html#xml-extensibility | |
func next(d *ixml.Decoder) (ixml.Token, error) { | |
for { | |
t, err := d.Token() | |
if err != nil { | |
return t, err | |
} | |
switch t.(type) { | |
case ixml.Comment, ixml.Directive, ixml.ProcInst: | |
continue | |
default: | |
return t, nil | |
} | |
} | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) | |
type propfindProps []xml.Name | |
// UnmarshalXML appends the property names enclosed within start to pn. | |
// | |
// It returns an error if start does not contain any properties or if | |
// properties contain values. Character data between properties is ignored. | |
func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { | |
for { | |
t, err := next(d) | |
if err != nil { | |
return err | |
} | |
switch t.(type) { | |
case ixml.EndElement: | |
if len(*pn) == 0 { | |
return fmt.Errorf("%s must not be empty", start.Name.Local) | |
} | |
return nil | |
case ixml.StartElement: | |
name := t.(ixml.StartElement).Name | |
t, err = next(d) | |
if err != nil { | |
return err | |
} | |
if _, ok := t.(ixml.EndElement); !ok { | |
return fmt.Errorf("unexpected token %T", t) | |
} | |
*pn = append(*pn, xml.Name(name)) | |
} | |
} | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind | |
type propfind struct { | |
XMLName ixml.Name `xml:"DAV: propfind"` | |
Allprop *struct{} `xml:"DAV: allprop"` | |
Propname *struct{} `xml:"DAV: propname"` | |
Prop propfindProps `xml:"DAV: prop"` | |
Include propfindProps `xml:"DAV: include"` | |
} | |
func readPropfind(r io.Reader) (pf propfind, status int, err error) { | |
c := countingReader{r: r} | |
if err = ixml.NewDecoder(&c).Decode(&pf); err != nil { | |
if err == io.EOF { | |
if c.n == 0 { | |
// An empty body means to propfind allprop. | |
// http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND | |
return propfind{Allprop: new(struct{})}, 0, nil | |
} | |
err = errInvalidPropfind | |
} | |
return propfind{}, http.StatusBadRequest, err | |
} | |
if pf.Allprop == nil && pf.Include != nil { | |
return propfind{}, http.StatusBadRequest, errInvalidPropfind | |
} | |
if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { | |
return propfind{}, http.StatusBadRequest, errInvalidPropfind | |
} | |
if pf.Prop != nil && pf.Propname != nil { | |
return propfind{}, http.StatusBadRequest, errInvalidPropfind | |
} | |
if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { | |
return propfind{}, http.StatusBadRequest, errInvalidPropfind | |
} | |
return pf, 0, nil | |
} | |
// Property represents a single DAV resource property as defined in RFC 4918. | |
// See http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties | |
type Property struct { | |
// XMLName is the fully qualified name that identifies this property. | |
XMLName xml.Name | |
// Lang is an optional xml:lang attribute. | |
Lang string `xml:"xml:lang,attr,omitempty"` | |
// InnerXML contains the XML representation of the property value. | |
// See http://www.webdav.org/specs/rfc4918.html#property_values | |
// | |
// Property values of complex type or mixed-content must have fully | |
// expanded XML namespaces or be self-contained with according | |
// XML namespace declarations. They must not rely on any XML | |
// namespace declarations within the scope of the XML document, | |
// even including the DAV: namespace. | |
InnerXML []byte `xml:",innerxml"` | |
} | |
// ixmlProperty is the same as the Property type except it holds an ixml.Name | |
// instead of an xml.Name. | |
type ixmlProperty struct { | |
XMLName ixml.Name | |
Lang string `xml:"xml:lang,attr,omitempty"` | |
InnerXML []byte `xml:",innerxml"` | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error | |
// See multistatusWriter for the "D:" namespace prefix. | |
type xmlError struct { | |
XMLName ixml.Name `xml:"D:error"` | |
InnerXML []byte `xml:",innerxml"` | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat | |
// See multistatusWriter for the "D:" namespace prefix. | |
type propstat struct { | |
Prop []Property `xml:"D:prop>_ignored_"` | |
Status string `xml:"D:status"` | |
Error *xmlError `xml:"D:error"` | |
ResponseDescription string `xml:"D:responsedescription,omitempty"` | |
} | |
// ixmlPropstat is the same as the propstat type except it holds an ixml.Name | |
// instead of an xml.Name. | |
type ixmlPropstat struct { | |
Prop []ixmlProperty `xml:"D:prop>_ignored_"` | |
Status string `xml:"D:status"` | |
Error *xmlError `xml:"D:error"` | |
ResponseDescription string `xml:"D:responsedescription,omitempty"` | |
} | |
// MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace | |
// before encoding. See multistatusWriter. | |
func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error { | |
// Convert from a propstat to an ixmlPropstat. | |
ixmlPs := ixmlPropstat{ | |
Prop: make([]ixmlProperty, len(ps.Prop)), | |
Status: ps.Status, | |
Error: ps.Error, | |
ResponseDescription: ps.ResponseDescription, | |
} | |
for k, prop := range ps.Prop { | |
ixmlPs.Prop[k] = ixmlProperty{ | |
XMLName: ixml.Name(prop.XMLName), | |
Lang: prop.Lang, | |
InnerXML: prop.InnerXML, | |
} | |
} | |
for k, prop := range ixmlPs.Prop { | |
if prop.XMLName.Space == "DAV:" { | |
prop.XMLName = ixml.Name{Space: "", Local: "D:" + prop.XMLName.Local} | |
ixmlPs.Prop[k] = prop | |
} | |
} | |
// Distinct type to avoid infinite recursion of MarshalXML. | |
type newpropstat ixmlPropstat | |
return e.EncodeElement(newpropstat(ixmlPs), start) | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response | |
// See multistatusWriter for the "D:" namespace prefix. | |
type response struct { | |
XMLName ixml.Name `xml:"D:response"` | |
Href []string `xml:"D:href"` | |
Propstat []propstat `xml:"D:propstat"` | |
Status string `xml:"D:status,omitempty"` | |
Error *xmlError `xml:"D:error"` | |
ResponseDescription string `xml:"D:responsedescription,omitempty"` | |
} | |
// MultistatusWriter marshals one or more Responses into a XML | |
// multistatus response. | |
// See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus | |
// TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as | |
// "DAV:" on this element, is prepended on the nested response, as well as on all | |
// its nested elements. All property names in the DAV: namespace are prefixed as | |
// well. This is because some versions of Mini-Redirector (on windows 7) ignore | |
// elements with a default namespace (no prefixed namespace). A less intrusive fix | |
// should be possible after golang.org/cl/11074. See https://golang.org/issue/11177 | |
type multistatusWriter struct { | |
// ResponseDescription contains the optional responsedescription | |
// of the multistatus XML element. Only the latest content before | |
// close will be emitted. Empty response descriptions are not | |
// written. | |
responseDescription string | |
w http.ResponseWriter | |
enc *ixml.Encoder | |
} | |
// Write validates and emits a DAV response as part of a multistatus response | |
// element. | |
// | |
// It sets the HTTP status code of its underlying http.ResponseWriter to 207 | |
// (Multi-Status) and populates the Content-Type header. If r is the | |
// first, valid response to be written, Write prepends the XML representation | |
// of r with a multistatus tag. Callers must call close after the last response | |
// has been written. | |
func (w *multistatusWriter) write(r *response) error { | |
switch len(r.Href) { | |
case 0: | |
return errInvalidResponse | |
case 1: | |
if len(r.Propstat) > 0 != (r.Status == "") { | |
return errInvalidResponse | |
} | |
default: | |
if len(r.Propstat) > 0 || r.Status == "" { | |
return errInvalidResponse | |
} | |
} | |
err := w.writeHeader() | |
if err != nil { | |
return err | |
} | |
return w.enc.Encode(r) | |
} | |
// writeHeader writes a XML multistatus start element on w's underlying | |
// http.ResponseWriter and returns the result of the write operation. | |
// After the first write attempt, writeHeader becomes a no-op. | |
func (w *multistatusWriter) writeHeader() error { | |
if w.enc != nil { | |
return nil | |
} | |
w.w.Header().Add("Content-Type", "text/xml; charset=utf-8") | |
w.w.WriteHeader(StatusMulti) | |
_, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`) | |
if err != nil { | |
return err | |
} | |
w.enc = ixml.NewEncoder(w.w) | |
return w.enc.EncodeToken(ixml.StartElement{ | |
Name: ixml.Name{ | |
Space: "DAV:", | |
Local: "multistatus", | |
}, | |
Attr: []ixml.Attr{{ | |
Name: ixml.Name{Space: "xmlns", Local: "D"}, | |
Value: "DAV:", | |
}}, | |
}) | |
} | |
// Close completes the marshalling of the multistatus response. It returns | |
// an error if the multistatus response could not be completed. If both the | |
// return value and field enc of w are nil, then no multistatus response has | |
// been written. | |
func (w *multistatusWriter) close() error { | |
if w.enc == nil { | |
return nil | |
} | |
var end []ixml.Token | |
if w.responseDescription != "" { | |
name := ixml.Name{Space: "DAV:", Local: "responsedescription"} | |
end = append(end, | |
ixml.StartElement{Name: name}, | |
ixml.CharData(w.responseDescription), | |
ixml.EndElement{Name: name}, | |
) | |
} | |
end = append(end, ixml.EndElement{ | |
Name: ixml.Name{Space: "DAV:", Local: "multistatus"}, | |
}) | |
for _, t := range end { | |
err := w.enc.EncodeToken(t) | |
if err != nil { | |
return err | |
} | |
} | |
return w.enc.Flush() | |
} | |
var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"} | |
func xmlLang(s ixml.StartElement, d string) string { | |
for _, attr := range s.Attr { | |
if attr.Name == xmlLangName { | |
return attr.Value | |
} | |
} | |
return d | |
} | |
type xmlValue []byte | |
func (v *xmlValue) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { | |
// The XML value of a property can be arbitrary, mixed-content XML. | |
// To make sure that the unmarshalled value contains all required | |
// namespaces, we encode all the property value XML tokens into a | |
// buffer. This forces the encoder to redeclare any used namespaces. | |
var b bytes.Buffer | |
e := ixml.NewEncoder(&b) | |
for { | |
t, err := next(d) | |
if err != nil { | |
return err | |
} | |
if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name { | |
break | |
} | |
if err = e.EncodeToken(t); err != nil { | |
return err | |
} | |
} | |
err := e.Flush() | |
if err != nil { | |
return err | |
} | |
*v = b.Bytes() | |
return nil | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) | |
type proppatchProps []Property | |
// UnmarshalXML appends the property names and values enclosed within start | |
// to ps. | |
// | |
// An xml:lang attribute that is defined either on the DAV:prop or property | |
// name XML element is propagated to the property's Lang field. | |
// | |
// UnmarshalXML returns an error if start does not contain any properties or if | |
// property values contain syntactically incorrect XML. | |
func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { | |
lang := xmlLang(start, "") | |
for { | |
t, err := next(d) | |
if err != nil { | |
return err | |
} | |
switch elem := t.(type) { | |
case ixml.EndElement: | |
if len(*ps) == 0 { | |
return fmt.Errorf("%s must not be empty", start.Name.Local) | |
} | |
return nil | |
case ixml.StartElement: | |
p := Property{ | |
XMLName: xml.Name(t.(ixml.StartElement).Name), | |
Lang: xmlLang(t.(ixml.StartElement), lang), | |
} | |
err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem) | |
if err != nil { | |
return err | |
} | |
*ps = append(*ps, p) | |
} | |
} | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_set | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove | |
type setRemove struct { | |
XMLName ixml.Name | |
Lang string `xml:"xml:lang,attr,omitempty"` | |
Prop proppatchProps `xml:"DAV: prop"` | |
} | |
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate | |
type propertyupdate struct { | |
XMLName ixml.Name `xml:"DAV: propertyupdate"` | |
Lang string `xml:"xml:lang,attr,omitempty"` | |
SetRemove []setRemove `xml:",any"` | |
} | |
func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) { | |
var pu propertyupdate | |
if err = ixml.NewDecoder(r).Decode(&pu); err != nil { | |
return nil, http.StatusBadRequest, err | |
} | |
for _, op := range pu.SetRemove { | |
remove := false | |
switch op.XMLName { | |
case ixml.Name{Space: "DAV:", Local: "set"}: | |
// No-op. | |
case ixml.Name{Space: "DAV:", Local: "remove"}: | |
for _, p := range op.Prop { | |
if len(p.InnerXML) > 0 { | |
return nil, http.StatusBadRequest, errInvalidProppatch | |
} | |
} | |
remove = true | |
default: | |
return nil, http.StatusBadRequest, errInvalidProppatch | |
} | |
patches = append(patches, Proppatch{Remove: remove, Props: op.Prop}) | |
} | |
return patches, 0, nil | |
} | |