mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-10-31 00:27:31 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			521 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			521 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"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/lock"
 | |
| 	"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 "github.com/cloudreve/Cloudreve/v4/pkg/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 lock.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.Owner.Application.InnerXML, 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
 | |
| }
 | 
