// Package api has type definitions for webdav package api import ( "encoding/xml" "regexp" "strconv" "strings" "sync" "time" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/hash" ) const ( // Wed, 27 Sep 2017 14:28:34 GMT timeFormat = time.RFC1123 // The same as time.RFC1123 with optional leading zeros on the date // see https://github.com/rclone/rclone/issues/2574 noZerosRFC1123 = "Mon, _2 Jan 2006 15:04:05 MST" ) // Multistatus contains responses returned from an HTTP 207 return code type Multistatus struct { Responses []Response `xml:"response"` } // Response contains an Href the response it about and its properties type Response struct { Href string `xml:"href"` Props Prop `xml:"propstat"` } // Prop is the properties of a response // // This is a lazy way of decoding the multiple in the // response. // // The response might look like this // // // /remote.php/webdav/Nextcloud%20Manual.pdf // // // Tue, 19 Dec 2017 22:02:36 GMT // 4143665 // // "048d7be4437ff7deeae94db50ff3e209" // application/pdf // // HTTP/1.1 200 OK // // // // // // // HTTP/1.1 404 Not Found // // // // So we elide the array of and within that the array of // into one struct. // // Note that status collects all the status values for which we just // check the first is OK. type Prop struct { Status []string `xml:"DAV: status"` Name string `xml:"DAV: prop>displayname,omitempty"` Type *xml.Name `xml:"DAV: prop>resourcetype>collection,omitempty"` IsCollection *string `xml:"DAV: prop>iscollection,omitempty"` // this is a Microsoft extension see #2716 Size int64 `xml:"DAV: prop>getcontentlength,omitempty"` Modified Time `xml:"DAV: prop>getlastmodified,omitempty"` Checksums []string `xml:"prop>checksums>checksum,omitempty"` } // Parse a status of the form "HTTP/1.1 200 OK" or "HTTP/1.1 200" var parseStatus = regexp.MustCompile(`^HTTP/[0-9.]+\s+(\d+)`) // StatusOK examines the Status and returns an OK flag func (p *Prop) StatusOK() bool { // Assume OK if no statuses received if len(p.Status) == 0 { return true } match := parseStatus.FindStringSubmatch(p.Status[0]) if len(match) < 2 { return false } code, err := strconv.Atoi(match[1]) if err != nil { return false } if code >= 200 && code < 300 { return true } return false } // Hashes returns a map of all checksums - may be nil func (p *Prop) Hashes() (hashes map[hash.Type]string) { if len(p.Checksums) == 0 { return nil } hashes = make(map[hash.Type]string) for _, checksums := range p.Checksums { checksums = strings.ToLower(checksums) for _, checksum := range strings.Split(checksums, " ") { switch { case strings.HasPrefix(checksum, "sha1:"): hashes[hash.SHA1] = checksum[5:] case strings.HasPrefix(checksum, "md5:"): hashes[hash.MD5] = checksum[4:] } } } return hashes } // PropValue is a tagged name and value type PropValue struct { XMLName xml.Name `xml:""` Value string `xml:",chardata"` } // Error is used to describe webdav errors // // // Sabre\DAV\Exception\NotFound // File with name Photo could not be located // type Error struct { Exception string `xml:"exception,omitempty"` Message string `xml:"message,omitempty"` Status string StatusCode int } // Error returns a string for the error and satisfies the error interface func (e *Error) Error() string { var out []string if e.Message != "" { out = append(out, e.Message) } if e.Exception != "" { out = append(out, e.Exception) } if e.Status != "" { out = append(out, e.Status) } if len(out) == 0 { return "Webdav Error" } return strings.Join(out, ": ") } // Time represents date and time information for the // webdav API marshalling to and from timeFormat type Time time.Time // MarshalXML turns a Time into XML func (t *Time) MarshalXML(e *xml.Encoder, start xml.StartElement) error { timeString := (*time.Time)(t).Format(timeFormat) return e.EncodeElement(timeString, start) } // Possible time formats to parse the time with var timeFormats = []string{ timeFormat, // Wed, 27 Sep 2017 14:28:34 GMT (as per RFC) time.RFC1123Z, // Fri, 05 Jan 2018 14:14:38 +0000 (as used by mydrive.ch) time.UnixDate, // Wed May 17 15:31:58 UTC 2017 (as used in an internal server) noZerosRFC1123, // Fri, 7 Sep 2018 08:49:58 GMT (as used by server in #2574) time.RFC3339, // Wed, 31 Oct 2018 13:57:11 CET (as used by komfortcloud.de) } var oneTimeError sync.Once // UnmarshalXML turns XML into a Time func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { var v string err := d.DecodeElement(&v, &start) if err != nil { return err } // If time is missing then return the epoch if v == "" { *t = Time(time.Unix(0, 0)) return nil } // Parse the time format in multiple possible ways var newT time.Time for _, timeFormat := range timeFormats { newT, err = time.Parse(timeFormat, v) if err == nil { *t = Time(newT) break } } if err != nil { oneTimeError.Do(func() { fs.Errorf(nil, "Failed to parse time %q - using the epoch", v) }) // Return the epoch instead *t = Time(time.Unix(0, 0)) // ignore error err = nil } return err } // Quota is used to read the bytes used and available // // // // /remote.php/webdav/ // // // -3 // 376461895 // // HTTP/1.1 200 OK // // // type Quota struct { Available string `xml:"DAV: response>propstat>prop>quota-available-bytes"` Used string `xml:"DAV: response>propstat>prop>quota-used-bytes"` }