|
|
// 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 "golang.org/x/net/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 }
|