server/webdav/xml.go

390 lines
12 KiB
Go
Raw Permalink Normal View History

// 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"
)
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 escape(s string) string {
for i := 0; i < len(s); i++ {
switch s[i] {
case '"', '&', '\'', '<', '>':
b := bytes.NewBuffer(nil)
2021-09-20 13:43:00 +00:00
xml.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
2021-09-20 13:43:00 +00:00
func next(d *xml.Decoder) (xml.Token, error) {
for {
t, err := d.Token()
if err != nil {
return t, err
}
switch t.(type) {
2021-09-20 13:43:00 +00:00
case xml.Comment, xml.Directive, xml.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.
2021-09-20 13:43:00 +00:00
func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := next(d)
if err != nil {
return err
}
switch t.(type) {
2021-09-20 13:43:00 +00:00
case xml.EndElement:
if len(*pn) == 0 {
return fmt.Errorf("%s must not be empty", start.Name.Local)
}
return nil
2021-09-20 13:43:00 +00:00
case xml.StartElement:
name := t.(xml.StartElement).Name
t, err = next(d)
if err != nil {
return err
}
2021-09-20 13:43:00 +00:00
if _, ok := t.(xml.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 {
2021-09-20 13:43:00 +00:00
XMLName xml.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}
2021-09-20 13:43:00 +00:00
if err = xml.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"`
}
2021-09-20 13:43:00 +00:00
// xmlProperty is the same as the Property type except it holds an xml.Name
// instead of an xml.Name.
2021-09-20 13:43:00 +00:00
type xmlProperty struct {
XMLName xml.Name
Lang string `xml:"xml:lang,attr,omitempty"`
InnerXML []byte `xml:",innerxml"`
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
2021-10-04 10:06:42 +00:00
// See multistatusWriter for the "d:" namespace prefix.
type xmlError struct {
2021-10-04 10:06:42 +00:00
XMLName xml.Name `xml:"d:error"`
InnerXML []byte `xml:",innerxml"`
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
2021-10-04 10:06:42 +00:00
// See multistatusWriter for the "d:" namespace prefix.
type propstat struct {
2021-10-04 10:06:42 +00:00
Prop []Property `xml:"d:prop>_ignored_"`
Status string `xml:"d:status"`
Error *xmlError `xml:"d:error"`
ResponseDescription string `xml:"d:responsedescription,omitempty"`
}
2021-09-20 13:43:00 +00:00
// xmlPropstat is the same as the propstat type except it holds an xml.Name
// instead of an xml.Name.
2021-09-20 13:43:00 +00:00
type xmlPropstat struct {
2021-10-04 10:06:42 +00:00
Prop []xmlProperty `xml:"d:prop>_ignored_"`
Status string `xml:"d:status"`
Error *xmlError `xml:"d:error"`
ResponseDescription string `xml:"d:responsedescription,omitempty"`
}
2021-10-04 10:06:42 +00:00
// MarshalXML prepends the "d:" namespace prefix on properties in the DAV: namespace
// before encoding. See multistatusWriter.
2021-09-20 13:43:00 +00:00
func (ps propstat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
// Convert from a propstat to an xmlPropstat.
xmlPs := xmlPropstat{
Prop: make([]xmlProperty, len(ps.Prop)),
Status: ps.Status,
Error: ps.Error,
ResponseDescription: ps.ResponseDescription,
}
for k, prop := range ps.Prop {
2021-09-20 13:43:00 +00:00
xmlPs.Prop[k] = xmlProperty{
XMLName: xml.Name(prop.XMLName),
Lang: prop.Lang,
InnerXML: prop.InnerXML,
}
}
2021-09-20 13:43:00 +00:00
for k, prop := range xmlPs.Prop {
if prop.XMLName.Space == "DAV:" {
2021-10-04 10:06:42 +00:00
prop.XMLName = xml.Name{Space: "", Local: "d:" + prop.XMLName.Local}
xmlPs.Prop[k] = prop
} else if prop.XMLName.Space == "http://owncloud.org/ns" {
prop.XMLName = xml.Name{Space: "", Local: "oc:" + prop.XMLName.Local}
xmlPs.Prop[k] = prop
} else if prop.XMLName.Space == "http://nextcloud.org/ns" {
prop.XMLName = xml.Name{Space: "", Local: "nc:" + prop.XMLName.Local}
xmlPs.Prop[k] = prop
} else if prop.XMLName.Space == "http://open-collaboration-services.org/ns" {
prop.XMLName = xml.Name{Space: "http://open-collaboration-services.org/ns", Local: "x1:" + prop.XMLName.Local}
xmlPs.Prop[k] = prop
} else if prop.XMLName.Space == "http://open-cloud-mesh.org/ns" {
prop.XMLName = xml.Name{Space: "http://open-cloud-mesh.org/ns", Local: "x2:" + prop.XMLName.Local}
2021-09-20 13:43:00 +00:00
xmlPs.Prop[k] = prop
}
}
// Distinct type to avoid infinite recursion of MarshalXML.
2021-09-20 13:43:00 +00:00
type newpropstat xmlPropstat
return e.EncodeElement(newpropstat(xmlPs), start)
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
2021-10-04 10:06:42 +00:00
// See multistatusWriter for the "d:" namespace prefix.
type response struct {
2021-10-04 10:06:42 +00:00
XMLName xml.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
2021-10-04 10:06:42 +00:00
// 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
2021-09-20 13:43:00 +00:00
enc *xml.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
}
2021-10-04 10:06:42 +00:00
w.w.Header().Add("Content-Type", "application/xml; charset=utf-8")
w.w.Header().Set("DAV", "1, 3, extended-mkcol")
2021-09-20 13:43:00 +00:00
w.w.WriteHeader(http.StatusMultiStatus)
_, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
if err != nil {
return err
}
2021-09-20 13:43:00 +00:00
w.enc = xml.NewEncoder(w.w)
return w.enc.EncodeToken(xml.StartElement{
Name: xml.Name{
2021-10-02 08:30:39 +00:00
Space: "",
2021-10-04 10:06:42 +00:00
Local: "d:multistatus",
},
2021-09-20 13:43:00 +00:00
Attr: []xml.Attr{{
2021-10-04 10:06:42 +00:00
Name: xml.Name{Space: "", Local: "xmlns:d"},
Value: "DAV:",
2021-10-04 10:06:42 +00:00
},{
Name: xml.Name{Space: "", Local: "xmlns:s"},
Value: "http://sabredav.org/ns",
},{
Name: xml.Name{Space: "", Local: "xmlns:oc"},
Value: "http://owncloud.org/ns",
},{
Name: xml.Name{Space: "", Local: "xmlns:nc"},
Value: "http://nextcloud.org/ns",
},
},
})
}
// 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
}
2021-09-20 13:43:00 +00:00
var end []xml.Token
if w.responseDescription != "" {
2021-09-20 13:43:00 +00:00
name := xml.Name{Space: "DAV:", Local: "responsedescription"}
end = append(end,
2021-09-20 13:43:00 +00:00
xml.StartElement{Name: name},
xml.CharData(w.responseDescription),
xml.EndElement{Name: name},
)
}
2021-09-20 13:43:00 +00:00
end = append(end, xml.EndElement{
2021-10-04 10:06:42 +00:00
Name: xml.Name{Space: "", Local: "d:multistatus"},
})
for _, t := range end {
err := w.enc.EncodeToken(t)
if err != nil {
return err
}
}
return w.enc.Flush()
}
2021-09-20 13:43:00 +00:00
var xmlLangName = xml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
2021-09-20 13:43:00 +00:00
func xmlLang(s xml.StartElement, d string) string {
for _, attr := range s.Attr {
if attr.Name == xmlLangName {
return attr.Value
}
}
return d
}
type xmlValue []byte
2021-09-20 13:43:00 +00:00
func (v *xmlValue) UnmarshalXML(d *xml.Decoder, start xml.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
2021-09-20 13:43:00 +00:00
e := xml.NewEncoder(&b)
for {
t, err := next(d)
if err != nil {
return err
}
2021-09-20 13:43:00 +00:00
if e, ok := t.(xml.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
}