| |
| |
| |
|
|
| package webdav |
|
|
| |
| |
|
|
| import ( |
| "bytes" |
| "encoding/xml" |
| "fmt" |
| "io" |
| "net/http" |
| "time" |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ixml "github.com/OpenListTeam/OpenList/v4/server/webdav/internal/xml" |
| ) |
|
|
| |
| 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"` |
| } |
|
|
| |
| 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 { |
| |
| |
| return lockInfo{}, 0, nil |
| } |
| err = errInvalidLockInfo |
| } |
| return lockInfo{}, http.StatusBadRequest, err |
| } |
| |
| |
| 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 |
| } |
|
|
| |
| |
| |
| |
| |
| 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 |
| } |
| } |
| } |
|
|
| |
| type propfindProps []xml.Name |
|
|
| |
| |
| |
| |
| 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)) |
| } |
| } |
| } |
|
|
| |
| 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 { |
| |
| |
| 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 |
| } |
|
|
| |
| |
| type Property struct { |
| |
| XMLName xml.Name |
|
|
| |
| Lang string `xml:"xml:lang,attr,omitempty"` |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| InnerXML []byte `xml:",innerxml"` |
| } |
|
|
| |
| |
| type ixmlProperty struct { |
| XMLName ixml.Name |
| Lang string `xml:"xml:lang,attr,omitempty"` |
| InnerXML []byte `xml:",innerxml"` |
| } |
|
|
| |
| |
| type xmlError struct { |
| XMLName ixml.Name `xml:"D:error"` |
| InnerXML []byte `xml:",innerxml"` |
| } |
|
|
| |
| |
| 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"` |
| } |
|
|
| |
| |
| 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"` |
| } |
|
|
| |
| |
| func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error { |
| |
| 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 |
| } |
| } |
| |
| type newpropstat ixmlPropstat |
| return e.EncodeElement(newpropstat(ixmlPs), start) |
| } |
|
|
| |
| |
| 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"` |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| type multistatusWriter struct { |
| |
| |
| |
| |
| responseDescription string |
|
|
| w http.ResponseWriter |
| enc *ixml.Encoder |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| 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) |
| } |
|
|
| |
| |
| |
| 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:", |
| }}, |
| }) |
| } |
|
|
| |
| |
| |
| |
| 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 { |
| |
| |
| |
| |
| 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 |
| } |
|
|
| |
| type proppatchProps []Property |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| 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) |
| } |
| } |
| } |
|
|
| |
| |
| type setRemove struct { |
| XMLName ixml.Name |
| Lang string `xml:"xml:lang,attr,omitempty"` |
| Prop proppatchProps `xml:"DAV: prop"` |
| } |
|
|
| |
| 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"}: |
| |
| 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 |
| } |
|
|