Spaces:
Paused
Paused
| // 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 "github.com/alist-org/alist/v3/server/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 | |
| } | |