// 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, "\n"+ "\n"+ " \n"+ " \n"+ " %s\n"+ " %s\n"+ " Second-%d\n"+ " %s\n"+ " %s\n"+ "", 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, ``) 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 }