mirror of
https://github.com/golang/net.git
synced 2026-03-31 18:37:08 +09:00
In particular, the Property and DeadPropsHolder types need to refer to the standard xml package, not the internal fork, to be usable by other packages. Inside the package, the XML marshaling and unmarshaling is still done by the etc/internal/xml package, and will remain that way until https://github.com/golang/go/issues/13400 is resolved. Fixes golang/go#15128. Change-Id: Ie7e7927d8b30d97d10b1a4a654d774fdf3e7a1e3 Reviewed-on: https://go-review.googlesource.com/21635 Reviewed-by: Andrew Gerrand <adg@golang.org>
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
|
|
}
|