1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
7 // The XML encoding is covered by Section 14.
8 // http://www.webdav.org/specs/rfc4918.html#xml.element.definitions
18 // As of https://go-review.googlesource.com/#/c/12772/ which was submitted
19 // in July 2015, this package uses an internal fork of the standard
20 // library's encoding/xml package, due to changes in the way namespaces
21 // were encoded. Such changes were introduced in the Go 1.5 cycle, but were
22 // rolled back in response to https://github.com/golang/go/issues/11841
24 // However, this package's exported API, specifically the Property and
25 // DeadPropsHolder types, need to refer to the standard library's version
26 // of the xml.Name type, as code that imports this package cannot refer to
27 // the internal version.
29 // This file therefore imports both the internal and external versions, as
30 // ixml and xml, and converts between them.
32 // In the long term, this package should use the standard library's version
33 // only, and the internal fork deleted, once
34 // https://github.com/golang/go/issues/13400 is resolved.
35 ixml "golang.org/x/net/webdav/internal/xml"
38 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
39 type lockInfo struct {
40 XMLName ixml.Name `xml:"lockinfo"`
41 Exclusive *struct{} `xml:"lockscope>exclusive"`
42 Shared *struct{} `xml:"lockscope>shared"`
43 Write *struct{} `xml:"locktype>write"`
44 Owner owner `xml:"owner"`
47 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner
49 InnerXML string `xml:",innerxml"`
52 func readLockInfo(r io.Reader) (li lockInfo, status int, err error) {
53 c := &countingReader{r: r}
54 if err = ixml.NewDecoder(c).Decode(&li); err != nil {
57 // An empty body means to refresh the lock.
58 // http://www.webdav.org/specs/rfc4918.html#refreshing-locks
59 return lockInfo{}, 0, nil
61 err = errInvalidLockInfo
63 return lockInfo{}, http.StatusBadRequest, err
65 // We only support exclusive (non-shared) write locks. In practice, these are
66 // the only types of locks that seem to matter.
67 if li.Exclusive == nil || li.Shared != nil || li.Write == nil {
68 return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo
73 type countingReader struct {
78 func (c *countingReader) Read(p []byte) (int, error) {
84 func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
89 timeout := ld.Duration / time.Second
90 return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
91 "<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+
92 " <D:locktype><D:write/></D:locktype>\n"+
93 " <D:lockscope><D:exclusive/></D:lockscope>\n"+
94 " <D:depth>%s</D:depth>\n"+
95 " <D:owner>%s</D:owner>\n"+
96 " <D:timeout>Second-%d</D:timeout>\n"+
97 " <D:locktoken><D:href>%s</D:href></D:locktoken>\n"+
98 " <D:lockroot><D:href>%s</D:href></D:lockroot>\n"+
99 "</D:activelock></D:lockdiscovery></D:prop>",
100 depth, ld.OwnerXML, timeout, escape(token), escape(ld.Root),
104 func escape(s string) string {
105 for i := 0; i < len(s); i++ {
107 case '"', '&', '\'', '<', '>':
108 b := bytes.NewBuffer(nil)
109 ixml.EscapeText(b, []byte(s))
116 // Next returns the next token, if any, in the XML stream of d.
117 // RFC 4918 requires to ignore comments, processing instructions
119 // http://www.webdav.org/specs/rfc4918.html#property_values
120 // http://www.webdav.org/specs/rfc4918.html#xml-extensibility
121 func next(d *ixml.Decoder) (ixml.Token, error) {
128 case ixml.Comment, ixml.Directive, ixml.ProcInst:
136 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
137 type propfindProps []xml.Name
139 // UnmarshalXML appends the property names enclosed within start to pn.
141 // It returns an error if start does not contain any properties or if
142 // properties contain values. Character data between properties is ignored.
143 func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
150 case ixml.EndElement:
152 return fmt.Errorf("%s must not be empty", start.Name.Local)
155 case ixml.StartElement:
156 name := t.(ixml.StartElement).Name
161 if _, ok := t.(ixml.EndElement); !ok {
162 return fmt.Errorf("unexpected token %T", t)
164 *pn = append(*pn, xml.Name(name))
169 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
170 type propfind struct {
171 XMLName ixml.Name `xml:"DAV: propfind"`
172 Allprop *struct{} `xml:"DAV: allprop"`
173 Propname *struct{} `xml:"DAV: propname"`
174 Prop propfindProps `xml:"DAV: prop"`
175 Include propfindProps `xml:"DAV: include"`
178 func readPropfind(r io.Reader) (pf propfind, status int, err error) {
179 c := countingReader{r: r}
180 if err = ixml.NewDecoder(&c).Decode(&pf); err != nil {
183 // An empty body means to propfind allprop.
184 // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
185 return propfind{Allprop: new(struct{})}, 0, nil
187 err = errInvalidPropfind
189 return propfind{}, http.StatusBadRequest, err
192 if pf.Allprop == nil && pf.Include != nil {
193 return propfind{}, http.StatusBadRequest, errInvalidPropfind
195 if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) {
196 return propfind{}, http.StatusBadRequest, errInvalidPropfind
198 if pf.Prop != nil && pf.Propname != nil {
199 return propfind{}, http.StatusBadRequest, errInvalidPropfind
201 if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil {
202 return propfind{}, http.StatusBadRequest, errInvalidPropfind
207 // Property represents a single DAV resource property as defined in RFC 4918.
208 // See http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties
209 type Property struct {
210 // XMLName is the fully qualified name that identifies this property.
213 // Lang is an optional xml:lang attribute.
214 Lang string `xml:"xml:lang,attr,omitempty"`
216 // InnerXML contains the XML representation of the property value.
217 // See http://www.webdav.org/specs/rfc4918.html#property_values
219 // Property values of complex type or mixed-content must have fully
220 // expanded XML namespaces or be self-contained with according
221 // XML namespace declarations. They must not rely on any XML
222 // namespace declarations within the scope of the XML document,
223 // even including the DAV: namespace.
224 InnerXML []byte `xml:",innerxml"`
227 // ixmlProperty is the same as the Property type except it holds an ixml.Name
228 // instead of an xml.Name.
229 type ixmlProperty struct {
231 Lang string `xml:"xml:lang,attr,omitempty"`
232 InnerXML []byte `xml:",innerxml"`
235 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
236 // See multistatusWriter for the "D:" namespace prefix.
237 type xmlError struct {
238 XMLName ixml.Name `xml:"D:error"`
239 InnerXML []byte `xml:",innerxml"`
242 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
243 // See multistatusWriter for the "D:" namespace prefix.
244 type propstat struct {
245 Prop []Property `xml:"D:prop>_ignored_"`
246 Status string `xml:"D:status"`
247 Error *xmlError `xml:"D:error"`
248 ResponseDescription string `xml:"D:responsedescription,omitempty"`
251 // ixmlPropstat is the same as the propstat type except it holds an ixml.Name
252 // instead of an xml.Name.
253 type ixmlPropstat struct {
254 Prop []ixmlProperty `xml:"D:prop>_ignored_"`
255 Status string `xml:"D:status"`
256 Error *xmlError `xml:"D:error"`
257 ResponseDescription string `xml:"D:responsedescription,omitempty"`
260 // MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace
261 // before encoding. See multistatusWriter.
262 func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error {
263 // Convert from a propstat to an ixmlPropstat.
264 ixmlPs := ixmlPropstat{
265 Prop: make([]ixmlProperty, len(ps.Prop)),
268 ResponseDescription: ps.ResponseDescription,
270 for k, prop := range ps.Prop {
271 ixmlPs.Prop[k] = ixmlProperty{
272 XMLName: ixml.Name(prop.XMLName),
274 InnerXML: prop.InnerXML,
278 for k, prop := range ixmlPs.Prop {
279 if prop.XMLName.Space == "DAV:" {
280 prop.XMLName = ixml.Name{Space: "", Local: "D:" + prop.XMLName.Local}
281 ixmlPs.Prop[k] = prop
284 // Distinct type to avoid infinite recursion of MarshalXML.
285 type newpropstat ixmlPropstat
286 return e.EncodeElement(newpropstat(ixmlPs), start)
289 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
290 // See multistatusWriter for the "D:" namespace prefix.
291 type response struct {
292 XMLName ixml.Name `xml:"D:response"`
293 Href []string `xml:"D:href"`
294 Propstat []propstat `xml:"D:propstat"`
295 Status string `xml:"D:status,omitempty"`
296 Error *xmlError `xml:"D:error"`
297 ResponseDescription string `xml:"D:responsedescription,omitempty"`
300 // MultistatusWriter marshals one or more Responses into a XML
301 // multistatus response.
302 // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus
303 // TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as
304 // "DAV:" on this element, is prepended on the nested response, as well as on all
305 // its nested elements. All property names in the DAV: namespace are prefixed as
306 // well. This is because some versions of Mini-Redirector (on windows 7) ignore
307 // elements with a default namespace (no prefixed namespace). A less intrusive fix
308 // should be possible after golang.org/cl/11074. See https://golang.org/issue/11177
309 type multistatusWriter struct {
310 // ResponseDescription contains the optional responsedescription
311 // of the multistatus XML element. Only the latest content before
312 // close will be emitted. Empty response descriptions are not
314 responseDescription string
316 w http.ResponseWriter
320 // Write validates and emits a DAV response as part of a multistatus response
323 // It sets the HTTP status code of its underlying http.ResponseWriter to 207
324 // (Multi-Status) and populates the Content-Type header. If r is the
325 // first, valid response to be written, Write prepends the XML representation
326 // of r with a multistatus tag. Callers must call close after the last response
328 func (w *multistatusWriter) write(r *response) error {
331 return errInvalidResponse
333 if len(r.Propstat) > 0 != (r.Status == "") {
334 return errInvalidResponse
337 if len(r.Propstat) > 0 || r.Status == "" {
338 return errInvalidResponse
341 err := w.writeHeader()
345 return w.enc.Encode(r)
348 // writeHeader writes a XML multistatus start element on w's underlying
349 // http.ResponseWriter and returns the result of the write operation.
350 // After the first write attempt, writeHeader becomes a no-op.
351 func (w *multistatusWriter) writeHeader() error {
355 w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
356 w.w.WriteHeader(StatusMulti)
357 _, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
361 w.enc = ixml.NewEncoder(w.w)
362 return w.enc.EncodeToken(ixml.StartElement{
365 Local: "multistatus",
368 Name: ixml.Name{Space: "xmlns", Local: "D"},
374 // Close completes the marshalling of the multistatus response. It returns
375 // an error if the multistatus response could not be completed. If both the
376 // return value and field enc of w are nil, then no multistatus response has
378 func (w *multistatusWriter) close() error {
383 if w.responseDescription != "" {
384 name := ixml.Name{Space: "DAV:", Local: "responsedescription"}
386 ixml.StartElement{Name: name},
387 ixml.CharData(w.responseDescription),
388 ixml.EndElement{Name: name},
391 end = append(end, ixml.EndElement{
392 Name: ixml.Name{Space: "DAV:", Local: "multistatus"},
394 for _, t := range end {
395 err := w.enc.EncodeToken(t)
403 var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
405 func xmlLang(s ixml.StartElement, d string) string {
406 for _, attr := range s.Attr {
407 if attr.Name == xmlLangName {
416 func (v *xmlValue) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
417 // The XML value of a property can be arbitrary, mixed-content XML.
418 // To make sure that the unmarshalled value contains all required
419 // namespaces, we encode all the property value XML tokens into a
420 // buffer. This forces the encoder to redeclare any used namespaces.
422 e := ixml.NewEncoder(&b)
428 if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name {
431 if err = e.EncodeToken(t); err != nil {
443 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch)
444 type proppatchProps []Property
446 // UnmarshalXML appends the property names and values enclosed within start
449 // An xml:lang attribute that is defined either on the DAV:prop or property
450 // name XML element is propagated to the property's Lang field.
452 // UnmarshalXML returns an error if start does not contain any properties or if
453 // property values contain syntactically incorrect XML.
454 func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
455 lang := xmlLang(start, "")
461 switch elem := t.(type) {
462 case ixml.EndElement:
464 return fmt.Errorf("%s must not be empty", start.Name.Local)
467 case ixml.StartElement:
469 XMLName: xml.Name(t.(ixml.StartElement).Name),
470 Lang: xmlLang(t.(ixml.StartElement), lang),
472 err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem)
481 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_set
482 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove
483 type setRemove struct {
485 Lang string `xml:"xml:lang,attr,omitempty"`
486 Prop proppatchProps `xml:"DAV: prop"`
489 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate
490 type propertyupdate struct {
491 XMLName ixml.Name `xml:"DAV: propertyupdate"`
492 Lang string `xml:"xml:lang,attr,omitempty"`
493 SetRemove []setRemove `xml:",any"`
496 func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) {
497 var pu propertyupdate
498 if err = ixml.NewDecoder(r).Decode(&pu); err != nil {
499 return nil, http.StatusBadRequest, err
501 for _, op := range pu.SetRemove {
504 case ixml.Name{Space: "DAV:", Local: "set"}:
506 case ixml.Name{Space: "DAV:", Local: "remove"}:
507 for _, p := range op.Prop {
508 if len(p.InnerXML) > 0 {
509 return nil, http.StatusBadRequest, errInvalidProppatch
514 return nil, http.StatusBadRequest, errInvalidProppatch
516 patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
518 return patches, 0, nil