Add the first test case.
This commit is contained in:
451
rest/dav/prop.go
451
rest/dav/prop.go
@ -7,7 +7,11 @@ package dav
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"tank/rest/dav/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proppatch describes a property update instruction as defined in RFC 4918.
|
// Proppatch describes a property update instruction as defined in RFC 4918.
|
||||||
@ -113,3 +117,448 @@ type ContentTyper interface {
|
|||||||
ContentType(ctx context.Context) (string, error)
|
ContentType(ctx context.Context) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
|
||||||
|
const (
|
||||||
|
StatusMulti = 207
|
||||||
|
StatusUnprocessableEntity = 422
|
||||||
|
StatusLocked = 423
|
||||||
|
StatusFailedDependency = 424
|
||||||
|
StatusInsufficientStorage = 507
|
||||||
|
)
|
||||||
|
|
||||||
|
func StatusText(code int) string {
|
||||||
|
switch code {
|
||||||
|
case StatusMulti:
|
||||||
|
return "Multi-Status"
|
||||||
|
case StatusUnprocessableEntity:
|
||||||
|
return "Unprocessable Entity"
|
||||||
|
case StatusLocked:
|
||||||
|
return "Locked"
|
||||||
|
case StatusFailedDependency:
|
||||||
|
return "Failed Dependency"
|
||||||
|
case StatusInsufficientStorage:
|
||||||
|
return "Insufficient Storage"
|
||||||
|
}
|
||||||
|
return http.StatusText(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidPropfind = errors.New("webdav: invalid propfind")
|
||||||
|
errInvalidResponse = errors.New("webdav: invalid response")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
|
||||||
|
type LockInfo struct {
|
||||||
|
XMLName xml.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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//这是一个带字节计数器的Reader,可以知道总共读取了多少个字节。
|
||||||
|
type CountingReader struct {
|
||||||
|
n int
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CountingReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := c.reader.Read(p)
|
||||||
|
c.n += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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 *xml.Decoder) (xml.Token, error) {
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
switch t.(type) {
|
||||||
|
case xml.Comment, xml.Directive, xml.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 *xml.Decoder, start xml.StartElement) error {
|
||||||
|
for {
|
||||||
|
t, err := next(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch t.(type) {
|
||||||
|
case xml.EndElement:
|
||||||
|
if len(*pn) == 0 {
|
||||||
|
return fmt.Errorf("%s must not be empty", start.Name.Local)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case xml.StartElement:
|
||||||
|
name := t.(xml.StartElement).Name
|
||||||
|
t, err = next(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := t.(xml.EndElement); !ok {
|
||||||
|
return fmt.Errorf("unexpected token %T", t)
|
||||||
|
}
|
||||||
|
*pn = append(*pn, xml.Name(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
|
||||||
|
// <!ELEMENT propfind ( propname | (allprop, include?) | prop ) >
|
||||||
|
type Propfind struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: propfind"`
|
||||||
|
Allprop *struct{} `xml:"DAV: allprop"`
|
||||||
|
Propname *struct{} `xml:"DAV: propname"`
|
||||||
|
Prop PropfindProps `xml:"DAV: prop"`
|
||||||
|
Include PropfindProps `xml:"DAV: include"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//从request中读出需要的属性。比如:getcontentlength 大小 creationdate 创建时间
|
||||||
|
func ReadPropfind(reader io.Reader) (propfind Propfind, status int, err error) {
|
||||||
|
c := CountingReader{reader: reader}
|
||||||
|
if err = xml.NewDecoder(&c).Decode(&propfind); 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 propfind.Allprop == nil && propfind.Include != nil {
|
||||||
|
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
||||||
|
}
|
||||||
|
if propfind.Allprop != nil && (propfind.Prop != nil || propfind.Propname != nil) {
|
||||||
|
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
||||||
|
}
|
||||||
|
if propfind.Prop != nil && propfind.Propname != nil {
|
||||||
|
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
||||||
|
}
|
||||||
|
if propfind.Propname == nil && propfind.Allprop == nil && propfind.Prop == nil {
|
||||||
|
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
||||||
|
}
|
||||||
|
return propfind, 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 xml.Name
|
||||||
|
// instead of an xml.Name.
|
||||||
|
type IxmlProperty struct {
|
||||||
|
XMLName xml.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 xml.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 SubPropstat 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 xml.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 SubPropstat) MarshalXML(e *xml.Encoder, start xml.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: xml.Name(prop.XMLName),
|
||||||
|
Lang: prop.Lang,
|
||||||
|
InnerXML: prop.InnerXML,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, prop := range ixmlPs.Prop {
|
||||||
|
if prop.XMLName.Space == "DAV:" {
|
||||||
|
prop.XMLName = xml.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 xml.Name `xml:"D:response"`
|
||||||
|
Href []string `xml:"D:href"`
|
||||||
|
Propstat []SubPropstat `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
|
||||||
|
|
||||||
|
Writer http.ResponseWriter
|
||||||
|
Encoder *xml.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 (this *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 := this.writeHeader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return this.Encoder.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 (this *MultiStatusWriter) writeHeader() error {
|
||||||
|
if this.Encoder != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
this.Writer.Header().Add("Content-Type", "text/xml; charset=utf-8")
|
||||||
|
this.Writer.WriteHeader(StatusMulti)
|
||||||
|
_, err := fmt.Fprintf(this.Writer, `<?xml version="1.0" encoding="UTF-8"?>`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
this.Encoder = xml.NewEncoder(this.Writer)
|
||||||
|
return this.Encoder.EncodeToken(xml.StartElement{
|
||||||
|
Name: xml.Name{
|
||||||
|
Space: "DAV:",
|
||||||
|
Local: "multistatus",
|
||||||
|
},
|
||||||
|
Attr: []xml.Attr{{
|
||||||
|
Name: xml.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 Encoder of w are nil, then no multistatus response has
|
||||||
|
// been written.
|
||||||
|
func (this *MultiStatusWriter) Close() error {
|
||||||
|
if this.Encoder == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var end []xml.Token
|
||||||
|
if this.ResponseDescription != "" {
|
||||||
|
name := xml.Name{Space: "DAV:", Local: "responsedescription"}
|
||||||
|
end = append(end,
|
||||||
|
xml.StartElement{Name: name},
|
||||||
|
xml.CharData(this.ResponseDescription),
|
||||||
|
xml.EndElement{Name: name},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end = append(end, xml.EndElement{
|
||||||
|
Name: xml.Name{Space: "DAV:", Local: "multistatus"},
|
||||||
|
})
|
||||||
|
for _, t := range end {
|
||||||
|
err := this.Encoder.EncodeToken(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.Encoder.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlLangName = xml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
|
||||||
|
|
||||||
|
func xmlLang(s xml.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 *xml.Decoder, start xml.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 := xml.NewEncoder(&b)
|
||||||
|
for {
|
||||||
|
t, err := next(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e, ok := t.(xml.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 *xml.Decoder, start xml.StartElement) error {
|
||||||
|
lang := xmlLang(start, "")
|
||||||
|
for {
|
||||||
|
t, err := next(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch elem := t.(type) {
|
||||||
|
case xml.EndElement:
|
||||||
|
if len(*ps) == 0 {
|
||||||
|
return fmt.Errorf("%s must not be empty", start.Name.Local)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case xml.StartElement:
|
||||||
|
p := Property{
|
||||||
|
XMLName: xml.Name(t.(xml.StartElement).Name),
|
||||||
|
Lang: xmlLang(t.(xml.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 xml.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 xml.Name `xml:"DAV: propertyupdate"`
|
||||||
|
Lang string `xml:"xml:lang,attr,omitempty"`
|
||||||
|
SetRemove []SetRemove `xml:",any"`
|
||||||
|
}
|
||||||
|
490
rest/dav/xml.go
490
rest/dav/xml.go
@ -1,490 +0,0 @@
|
|||||||
// 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 dav
|
|
||||||
|
|
||||||
// The XML encoding is covered by Section 14.
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#xml.element.definitions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
"tank/rest/dav/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
|
|
||||||
const (
|
|
||||||
StatusMulti = 207
|
|
||||||
StatusUnprocessableEntity = 422
|
|
||||||
StatusLocked = 423
|
|
||||||
StatusFailedDependency = 424
|
|
||||||
StatusInsufficientStorage = 507
|
|
||||||
)
|
|
||||||
|
|
||||||
func StatusText(code int) string {
|
|
||||||
switch code {
|
|
||||||
case StatusMulti:
|
|
||||||
return "Multi-Status"
|
|
||||||
case StatusUnprocessableEntity:
|
|
||||||
return "Unprocessable Entity"
|
|
||||||
case StatusLocked:
|
|
||||||
return "Locked"
|
|
||||||
case StatusFailedDependency:
|
|
||||||
return "Failed Dependency"
|
|
||||||
case StatusInsufficientStorage:
|
|
||||||
return "Insufficient Storage"
|
|
||||||
}
|
|
||||||
return http.StatusText(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errInvalidPropfind = errors.New("webdav: invalid propfind")
|
|
||||||
errInvalidResponse = errors.New("webdav: invalid response")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
|
|
||||||
type LockInfo struct {
|
|
||||||
XMLName xml.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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//这是一个带字节计数器的Reader,可以知道总共读取了多少个字节。
|
|
||||||
type CountingReader struct {
|
|
||||||
n int
|
|
||||||
reader io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CountingReader) Read(p []byte) (int, error) {
|
|
||||||
n, err := c.reader.Read(p)
|
|
||||||
c.n += n
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func escape(s string) string {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch s[i] {
|
|
||||||
case '"', '&', '\'', '<', '>':
|
|
||||||
b := bytes.NewBuffer(nil)
|
|
||||||
xml.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 *xml.Decoder) (xml.Token, error) {
|
|
||||||
for {
|
|
||||||
t, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
switch t.(type) {
|
|
||||||
case xml.Comment, xml.Directive, xml.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 *xml.Decoder, start xml.StartElement) error {
|
|
||||||
for {
|
|
||||||
t, err := next(d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch t.(type) {
|
|
||||||
case xml.EndElement:
|
|
||||||
if len(*pn) == 0 {
|
|
||||||
return fmt.Errorf("%s must not be empty", start.Name.Local)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case xml.StartElement:
|
|
||||||
name := t.(xml.StartElement).Name
|
|
||||||
t, err = next(d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, ok := t.(xml.EndElement); !ok {
|
|
||||||
return fmt.Errorf("unexpected token %T", t)
|
|
||||||
}
|
|
||||||
*pn = append(*pn, xml.Name(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
|
|
||||||
// <!ELEMENT propfind ( propname | (allprop, include?) | prop ) >
|
|
||||||
type Propfind struct {
|
|
||||||
XMLName xml.Name `xml:"DAV: propfind"`
|
|
||||||
Allprop *struct{} `xml:"DAV: allprop"`
|
|
||||||
Propname *struct{} `xml:"DAV: propname"`
|
|
||||||
Prop PropfindProps `xml:"DAV: prop"`
|
|
||||||
Include PropfindProps `xml:"DAV: include"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//从request中读出需要的属性。比如:getcontentlength 大小 creationdate 创建时间
|
|
||||||
func ReadPropfind(reader io.Reader) (propfind Propfind, status int, err error) {
|
|
||||||
c := CountingReader{reader: reader}
|
|
||||||
if err = xml.NewDecoder(&c).Decode(&propfind); 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 propfind.Allprop == nil && propfind.Include != nil {
|
|
||||||
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
|
||||||
}
|
|
||||||
if propfind.Allprop != nil && (propfind.Prop != nil || propfind.Propname != nil) {
|
|
||||||
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
|
||||||
}
|
|
||||||
if propfind.Prop != nil && propfind.Propname != nil {
|
|
||||||
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
|
||||||
}
|
|
||||||
if propfind.Propname == nil && propfind.Allprop == nil && propfind.Prop == nil {
|
|
||||||
return Propfind{}, http.StatusBadRequest, errInvalidPropfind
|
|
||||||
}
|
|
||||||
return propfind, 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 xml.Name
|
|
||||||
// instead of an xml.Name.
|
|
||||||
type IxmlProperty struct {
|
|
||||||
XMLName xml.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 xml.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 SubPropstat 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 xml.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 SubPropstat) MarshalXML(e *xml.Encoder, start xml.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: xml.Name(prop.XMLName),
|
|
||||||
Lang: prop.Lang,
|
|
||||||
InnerXML: prop.InnerXML,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, prop := range ixmlPs.Prop {
|
|
||||||
if prop.XMLName.Space == "DAV:" {
|
|
||||||
prop.XMLName = xml.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 xml.Name `xml:"D:response"`
|
|
||||||
Href []string `xml:"D:href"`
|
|
||||||
Propstat []SubPropstat `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
|
|
||||||
|
|
||||||
Writer http.ResponseWriter
|
|
||||||
Encoder *xml.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 (this *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 := this.writeHeader()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return this.Encoder.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 (this *MultiStatusWriter) writeHeader() error {
|
|
||||||
if this.Encoder != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
this.Writer.Header().Add("Content-Type", "text/xml; charset=utf-8")
|
|
||||||
this.Writer.WriteHeader(StatusMulti)
|
|
||||||
_, err := fmt.Fprintf(this.Writer, `<?xml version="1.0" encoding="UTF-8"?>`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
this.Encoder = xml.NewEncoder(this.Writer)
|
|
||||||
return this.Encoder.EncodeToken(xml.StartElement{
|
|
||||||
Name: xml.Name{
|
|
||||||
Space: "DAV:",
|
|
||||||
Local: "multistatus",
|
|
||||||
},
|
|
||||||
Attr: []xml.Attr{{
|
|
||||||
Name: xml.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 Encoder of w are nil, then no multistatus response has
|
|
||||||
// been written.
|
|
||||||
func (this *MultiStatusWriter) Close() error {
|
|
||||||
if this.Encoder == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var end []xml.Token
|
|
||||||
if this.ResponseDescription != "" {
|
|
||||||
name := xml.Name{Space: "DAV:", Local: "responsedescription"}
|
|
||||||
end = append(end,
|
|
||||||
xml.StartElement{Name: name},
|
|
||||||
xml.CharData(this.ResponseDescription),
|
|
||||||
xml.EndElement{Name: name},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
end = append(end, xml.EndElement{
|
|
||||||
Name: xml.Name{Space: "DAV:", Local: "multistatus"},
|
|
||||||
})
|
|
||||||
for _, t := range end {
|
|
||||||
err := this.Encoder.EncodeToken(t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.Encoder.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
var xmlLangName = xml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
|
|
||||||
|
|
||||||
func xmlLang(s xml.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 *xml.Decoder, start xml.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 := xml.NewEncoder(&b)
|
|
||||||
for {
|
|
||||||
t, err := next(d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e, ok := t.(xml.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 *xml.Decoder, start xml.StartElement) error {
|
|
||||||
lang := xmlLang(start, "")
|
|
||||||
for {
|
|
||||||
t, err := next(d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch elem := t.(type) {
|
|
||||||
case xml.EndElement:
|
|
||||||
if len(*ps) == 0 {
|
|
||||||
return fmt.Errorf("%s must not be empty", start.Name.Local)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case xml.StartElement:
|
|
||||||
p := Property{
|
|
||||||
XMLName: xml.Name(t.(xml.StartElement).Name),
|
|
||||||
Lang: xmlLang(t.(xml.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 xml.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 xml.Name `xml:"DAV: propertyupdate"`
|
|
||||||
Lang string `xml:"xml:lang,attr,omitempty"`
|
|
||||||
SetRemove []SetRemove `xml:",any"`
|
|
||||||
}
|
|
@ -18,17 +18,6 @@ type LiveProp struct {
|
|||||||
dir bool
|
dir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SlashClean is equivalent to but slightly more efficient than
|
|
||||||
// path.Clean("/" + name).
|
|
||||||
func SlashClean(name string) string {
|
|
||||||
if name == "" || name[0] != '/' {
|
|
||||||
name = "/" + name
|
|
||||||
}
|
|
||||||
return path.Clean(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//所有的动态属性定义及其值的获取方式
|
//所有的动态属性定义及其值的获取方式
|
||||||
var LivePropMap = map[xml.Name]LiveProp{
|
var LivePropMap = map[xml.Name]LiveProp{
|
||||||
{Space: "DAV:", Local: "resourcetype"}: {
|
{Space: "DAV:", Local: "resourcetype"}: {
|
||||||
@ -43,7 +32,7 @@ var LivePropMap = map[xml.Name]LiveProp{
|
|||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "displayname"}: {
|
{Space: "DAV:", Local: "displayname"}: {
|
||||||
findFn: func(user *User, matter *Matter) string {
|
findFn: func(user *User, matter *Matter) string {
|
||||||
if SlashClean(matter.Name) == "/" {
|
if path.Clean("/"+matter.Name) == "/" {
|
||||||
return ""
|
return ""
|
||||||
} else {
|
} else {
|
||||||
return dav.EscapeXML(matter.Name)
|
return dav.EscapeXML(matter.Name)
|
||||||
@ -131,5 +120,4 @@ var LivePropMap = map[xml.Name]LiveProp{
|
|||||||
},
|
},
|
||||||
dir: true,
|
dir: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
55
rest/test/dav_test.go
Normal file
55
rest/test/dav_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"tank/rest/dav"
|
||||||
|
"tank/rest/dav/xml"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadPropfind(t *testing.T) {
|
||||||
|
|
||||||
|
propfind := &dav.Propfind{}
|
||||||
|
|
||||||
|
str := `
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<D:propfind xmlns:D="DAV:">
|
||||||
|
<D:prop>
|
||||||
|
<D:resourcetype />
|
||||||
|
<D:getcontentlength />
|
||||||
|
<D:creationdate />
|
||||||
|
<D:getlastmodified />
|
||||||
|
</D:prop>
|
||||||
|
</D:propfind>
|
||||||
|
`
|
||||||
|
|
||||||
|
reader := bytes.NewReader([]byte(str))
|
||||||
|
|
||||||
|
err := xml.NewDecoder(reader).Decode(propfind)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMap := make(map[string]bool)
|
||||||
|
|
||||||
|
resultMap[`propfind.XMLName.Space == "DAV:"`] = propfind.XMLName.Space == "DAV:"
|
||||||
|
|
||||||
|
resultMap[`propfind.XMLName.Local == "propfind"`] = propfind.XMLName.Local == "propfind"
|
||||||
|
|
||||||
|
resultMap[`len(propfind.Prop) == 4`] = len(propfind.Prop) == 4
|
||||||
|
|
||||||
|
resultMap[`propfind.Prop[0]`] = propfind.Prop[0].Space == "DAV:" && propfind.Prop[0].Local == "resourcetype"
|
||||||
|
resultMap[`propfind.Prop[1]`] = propfind.Prop[1].Space == "DAV:" && propfind.Prop[1].Local == "getcontentlength"
|
||||||
|
resultMap[`propfind.Prop[2]`] = propfind.Prop[2].Space == "DAV:" && propfind.Prop[2].Local == "creationdate"
|
||||||
|
resultMap[`propfind.Prop[3]`] = propfind.Prop[3].Space == "DAV:" && propfind.Prop[3].Local == "getlastmodified"
|
||||||
|
|
||||||
|
for k, v := range resultMap {
|
||||||
|
if !v {
|
||||||
|
t.Errorf("index = %s error", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("[%v] pass!", time.Now())
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func TestHello(t *testing.T) {
|
func TestHello(t *testing.T) {
|
||||||
|
|
||||||
split := strings.Split("good", "/")
|
split := strings.Split("good", "/")
|
Reference in New Issue
Block a user