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.
4
5package webdav
6
7// The XML encoding is covered by Section 14.
8// http://www.webdav.org/specs/rfc4918.html#xml.element.definitions
9
10import (
11	"bytes"
12	"encoding/xml"
13	"fmt"
14	"io"
15	"net/http"
16	"time"
17
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
23	//
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.
28	//
29	// This file therefore imports both the internal and external versions, as
30	// ixml and xml, and converts between them.
31	//
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"
36)
37
38// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
39type 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"`
45}
46
47// http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner
48type owner struct {
49	InnerXML string `xml:",innerxml"`
50}
51
52func 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 {
55		if err == io.EOF {
56			if c.n == 0 {
57				// An empty body means to refresh the lock.
58				// http://www.webdav.org/specs/rfc4918.html#refreshing-locks
59				return lockInfo{}, 0, nil
60			}
61			err = errInvalidLockInfo
62		}
63		return lockInfo{}, http.StatusBadRequest, err
64	}
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
69	}
70	return li, 0, nil
71}
72
73type countingReader struct {
74	n int
75	r io.Reader
76}
77
78func (c *countingReader) Read(p []byte) (int, error) {
79	n, err := c.r.Read(p)
80	c.n += n
81	return n, err
82}
83
84func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
85	depth := "infinity"
86	if ld.ZeroDepth {
87		depth = "0"
88	}
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),
101	)
102}
103
104func escape(s string) string {
105	for i := 0; i < len(s); i++ {
106		switch s[i] {
107		case '"', '&', '\'', '<', '>':
108			b := bytes.NewBuffer(nil)
109			ixml.EscapeText(b, []byte(s))
110			return b.String()
111		}
112	}
113	return s
114}
115
116// Next returns the next token, if any, in the XML stream of d.
117// RFC 4918 requires to ignore comments, processing instructions
118// and directives.
119// http://www.webdav.org/specs/rfc4918.html#property_values
120// http://www.webdav.org/specs/rfc4918.html#xml-extensibility
121func next(d *ixml.Decoder) (ixml.Token, error) {
122	for {
123		t, err := d.Token()
124		if err != nil {
125			return t, err
126		}
127		switch t.(type) {
128		case ixml.Comment, ixml.Directive, ixml.ProcInst:
129			continue
130		default:
131			return t, nil
132		}
133	}
134}
135
136// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
137type propfindProps []xml.Name
138
139// UnmarshalXML appends the property names enclosed within start to pn.
140//
141// It returns an error if start does not contain any properties or if
142// properties contain values. Character data between properties is ignored.
143func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
144	for {
145		t, err := next(d)
146		if err != nil {
147			return err
148		}
149		switch t.(type) {
150		case ixml.EndElement:
151			if len(*pn) == 0 {
152				return fmt.Errorf("%s must not be empty", start.Name.Local)
153			}
154			return nil
155		case ixml.StartElement:
156			name := t.(ixml.StartElement).Name
157			t, err = next(d)
158			if err != nil {
159				return err
160			}
161			if _, ok := t.(ixml.EndElement); !ok {
162				return fmt.Errorf("unexpected token %T", t)
163			}
164			*pn = append(*pn, xml.Name(name))
165		}
166	}
167}
168
169// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
170type 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"`
176}
177
178func 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 {
181		if err == io.EOF {
182			if c.n == 0 {
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
186			}
187			err = errInvalidPropfind
188		}
189		return propfind{}, http.StatusBadRequest, err
190	}
191
192	if pf.Allprop == nil && pf.Include != nil {
193		return propfind{}, http.StatusBadRequest, errInvalidPropfind
194	}
195	if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) {
196		return propfind{}, http.StatusBadRequest, errInvalidPropfind
197	}
198	if pf.Prop != nil && pf.Propname != nil {
199		return propfind{}, http.StatusBadRequest, errInvalidPropfind
200	}
201	if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil {
202		return propfind{}, http.StatusBadRequest, errInvalidPropfind
203	}
204	return pf, 0, nil
205}
206
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
209type Property struct {
210	// XMLName is the fully qualified name that identifies this property.
211	XMLName xml.Name
212
213	// Lang is an optional xml:lang attribute.
214	Lang string `xml:"xml:lang,attr,omitempty"`
215
216	// InnerXML contains the XML representation of the property value.
217	// See http://www.webdav.org/specs/rfc4918.html#property_values
218	//
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"`
225}
226
227// ixmlProperty is the same as the Property type except it holds an ixml.Name
228// instead of an xml.Name.
229type ixmlProperty struct {
230	XMLName  ixml.Name
231	Lang     string `xml:"xml:lang,attr,omitempty"`
232	InnerXML []byte `xml:",innerxml"`
233}
234
235// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
236// See multistatusWriter for the "D:" namespace prefix.
237type xmlError struct {
238	XMLName  ixml.Name `xml:"D:error"`
239	InnerXML []byte    `xml:",innerxml"`
240}
241
242// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
243// See multistatusWriter for the "D:" namespace prefix.
244type 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"`
249}
250
251// ixmlPropstat is the same as the propstat type except it holds an ixml.Name
252// instead of an xml.Name.
253type 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"`
258}
259
260// MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace
261// before encoding. See multistatusWriter.
262func (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)),
266		Status:              ps.Status,
267		Error:               ps.Error,
268		ResponseDescription: ps.ResponseDescription,
269	}
270	for k, prop := range ps.Prop {
271		ixmlPs.Prop[k] = ixmlProperty{
272			XMLName:  ixml.Name(prop.XMLName),
273			Lang:     prop.Lang,
274			InnerXML: prop.InnerXML,
275		}
276	}
277
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
282		}
283	}
284	// Distinct type to avoid infinite recursion of MarshalXML.
285	type newpropstat ixmlPropstat
286	return e.EncodeElement(newpropstat(ixmlPs), start)
287}
288
289// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
290// See multistatusWriter for the "D:" namespace prefix.
291type 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"`
298}
299
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
309type 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
313	// written.
314	responseDescription string
315
316	w   http.ResponseWriter
317	enc *ixml.Encoder
318}
319
320// Write validates and emits a DAV response as part of a multistatus response
321// element.
322//
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
327// has been written.
328func (w *multistatusWriter) write(r *response) error {
329	switch len(r.Href) {
330	case 0:
331		return errInvalidResponse
332	case 1:
333		if len(r.Propstat) > 0 != (r.Status == "") {
334			return errInvalidResponse
335		}
336	default:
337		if len(r.Propstat) > 0 || r.Status == "" {
338			return errInvalidResponse
339		}
340	}
341	err := w.writeHeader()
342	if err != nil {
343		return err
344	}
345	return w.enc.Encode(r)
346}
347
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.
351func (w *multistatusWriter) writeHeader() error {
352	if w.enc != nil {
353		return nil
354	}
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"?>`)
358	if err != nil {
359		return err
360	}
361	w.enc = ixml.NewEncoder(w.w)
362	return w.enc.EncodeToken(ixml.StartElement{
363		Name: ixml.Name{
364			Space: "DAV:",
365			Local: "multistatus",
366		},
367		Attr: []ixml.Attr{{
368			Name:  ixml.Name{Space: "xmlns", Local: "D"},
369			Value: "DAV:",
370		}},
371	})
372}
373
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
377// been written.
378func (w *multistatusWriter) close() error {
379	if w.enc == nil {
380		return nil
381	}
382	var end []ixml.Token
383	if w.responseDescription != "" {
384		name := ixml.Name{Space: "DAV:", Local: "responsedescription"}
385		end = append(end,
386			ixml.StartElement{Name: name},
387			ixml.CharData(w.responseDescription),
388			ixml.EndElement{Name: name},
389		)
390	}
391	end = append(end, ixml.EndElement{
392		Name: ixml.Name{Space: "DAV:", Local: "multistatus"},
393	})
394	for _, t := range end {
395		err := w.enc.EncodeToken(t)
396		if err != nil {
397			return err
398		}
399	}
400	return w.enc.Flush()
401}
402
403var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
404
405func xmlLang(s ixml.StartElement, d string) string {
406	for _, attr := range s.Attr {
407		if attr.Name == xmlLangName {
408			return attr.Value
409		}
410	}
411	return d
412}
413
414type xmlValue []byte
415
416func (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.
421	var b bytes.Buffer
422	e := ixml.NewEncoder(&b)
423	for {
424		t, err := next(d)
425		if err != nil {
426			return err
427		}
428		if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name {
429			break
430		}
431		if err = e.EncodeToken(t); err != nil {
432			return err
433		}
434	}
435	err := e.Flush()
436	if err != nil {
437		return err
438	}
439	*v = b.Bytes()
440	return nil
441}
442
443// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch)
444type proppatchProps []Property
445
446// UnmarshalXML appends the property names and values enclosed within start
447// to ps.
448//
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.
451//
452// UnmarshalXML returns an error if start does not contain any properties or if
453// property values contain syntactically incorrect XML.
454func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
455	lang := xmlLang(start, "")
456	for {
457		t, err := next(d)
458		if err != nil {
459			return err
460		}
461		switch elem := t.(type) {
462		case ixml.EndElement:
463			if len(*ps) == 0 {
464				return fmt.Errorf("%s must not be empty", start.Name.Local)
465			}
466			return nil
467		case ixml.StartElement:
468			p := Property{
469				XMLName: xml.Name(t.(ixml.StartElement).Name),
470				Lang:    xmlLang(t.(ixml.StartElement), lang),
471			}
472			err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem)
473			if err != nil {
474				return err
475			}
476			*ps = append(*ps, p)
477		}
478	}
479}
480
481// http://www.webdav.org/specs/rfc4918.html#ELEMENT_set
482// http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove
483type setRemove struct {
484	XMLName ixml.Name
485	Lang    string         `xml:"xml:lang,attr,omitempty"`
486	Prop    proppatchProps `xml:"DAV: prop"`
487}
488
489// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate
490type propertyupdate struct {
491	XMLName   ixml.Name   `xml:"DAV: propertyupdate"`
492	Lang      string      `xml:"xml:lang,attr,omitempty"`
493	SetRemove []setRemove `xml:",any"`
494}
495
496func 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
500	}
501	for _, op := range pu.SetRemove {
502		remove := false
503		switch op.XMLName {
504		case ixml.Name{Space: "DAV:", Local: "set"}:
505			// No-op.
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
510				}
511			}
512			remove = true
513		default:
514			return nil, http.StatusBadRequest, errInvalidProppatch
515		}
516		patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
517	}
518	return patches, 0, nil
519}
520