520 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			520 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // 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 | ||
|  | } |