github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/webdav/api/types.go (about) 1 // Package api has type definitions for webdav 2 package api 3 4 import ( 5 "encoding/xml" 6 "regexp" 7 "strconv" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/hash" 14 ) 15 16 const ( 17 // Wed, 27 Sep 2017 14:28:34 GMT 18 timeFormat = time.RFC1123 19 // The same as time.RFC1123 with optional leading zeros on the date 20 // see https://github.com/rclone/rclone/issues/2574 21 noZerosRFC1123 = "Mon, _2 Jan 2006 15:04:05 MST" 22 ) 23 24 // Multistatus contains responses returned from an HTTP 207 return code 25 type Multistatus struct { 26 Responses []Response `xml:"response"` 27 } 28 29 // Response contains an Href the response it about and its properties 30 type Response struct { 31 Href string `xml:"href"` 32 Props Prop `xml:"propstat"` 33 } 34 35 // Prop is the properties of a response 36 // 37 // This is a lazy way of decoding the multiple <s:propstat> in the 38 // response. 39 // 40 // The response might look like this 41 // 42 // <d:response> 43 // <d:href>/remote.php/webdav/Nextcloud%20Manual.pdf</d:href> 44 // <d:propstat> 45 // <d:prop> 46 // <d:getlastmodified>Tue, 19 Dec 2017 22:02:36 GMT</d:getlastmodified> 47 // <d:getcontentlength>4143665</d:getcontentlength> 48 // <d:resourcetype/> 49 // <d:getetag>"048d7be4437ff7deeae94db50ff3e209"</d:getetag> 50 // <d:getcontenttype>application/pdf</d:getcontenttype> 51 // </d:prop> 52 // <d:status>HTTP/1.1 200 OK</d:status> 53 // </d:propstat> 54 // <d:propstat> 55 // <d:prop> 56 // <d:quota-used-bytes/> 57 // <d:quota-available-bytes/> 58 // </d:prop> 59 // <d:status>HTTP/1.1 404 Not Found</d:status> 60 // </d:propstat> 61 // </d:response> 62 // 63 // So we elide the array of <d:propstat> and within that the array of 64 // <d:prop> into one struct. 65 // 66 // Note that status collects all the status values for which we just 67 // check the first is OK. 68 type Prop struct { 69 Status []string `xml:"DAV: status"` 70 Name string `xml:"DAV: prop>displayname,omitempty"` 71 Type *xml.Name `xml:"DAV: prop>resourcetype>collection,omitempty"` 72 IsCollection *string `xml:"DAV: prop>iscollection,omitempty"` // this is a Microsoft extension see #2716 73 Size int64 `xml:"DAV: prop>getcontentlength,omitempty"` 74 Modified Time `xml:"DAV: prop>getlastmodified,omitempty"` 75 Checksums []string `xml:"prop>checksums>checksum,omitempty"` 76 } 77 78 // Parse a status of the form "HTTP/1.1 200 OK" or "HTTP/1.1 200" 79 var parseStatus = regexp.MustCompile(`^HTTP/[0-9.]+\s+(\d+)`) 80 81 // StatusOK examines the Status and returns an OK flag 82 func (p *Prop) StatusOK() bool { 83 // Assume OK if no statuses received 84 if len(p.Status) == 0 { 85 return true 86 } 87 match := parseStatus.FindStringSubmatch(p.Status[0]) 88 if len(match) < 2 { 89 return false 90 } 91 code, err := strconv.Atoi(match[1]) 92 if err != nil { 93 return false 94 } 95 if code >= 200 && code < 300 { 96 return true 97 } 98 return false 99 } 100 101 // Hashes returns a map of all checksums - may be nil 102 func (p *Prop) Hashes() (hashes map[hash.Type]string) { 103 if len(p.Checksums) == 0 { 104 return nil 105 } 106 hashes = make(map[hash.Type]string) 107 for _, checksums := range p.Checksums { 108 checksums = strings.ToLower(checksums) 109 for _, checksum := range strings.Split(checksums, " ") { 110 switch { 111 case strings.HasPrefix(checksum, "sha1:"): 112 hashes[hash.SHA1] = checksum[5:] 113 case strings.HasPrefix(checksum, "md5:"): 114 hashes[hash.MD5] = checksum[4:] 115 } 116 } 117 } 118 return hashes 119 } 120 121 // PropValue is a tagged name and value 122 type PropValue struct { 123 XMLName xml.Name `xml:""` 124 Value string `xml:",chardata"` 125 } 126 127 // Error is used to describe webdav errors 128 // 129 // <d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> 130 // <s:exception>Sabre\DAV\Exception\NotFound</s:exception> 131 // <s:message>File with name Photo could not be located</s:message> 132 // </d:error> 133 type Error struct { 134 Exception string `xml:"exception,omitempty"` 135 Message string `xml:"message,omitempty"` 136 Status string 137 StatusCode int 138 } 139 140 // Error returns a string for the error and satisfies the error interface 141 func (e *Error) Error() string { 142 var out []string 143 if e.Message != "" { 144 out = append(out, e.Message) 145 } 146 if e.Exception != "" { 147 out = append(out, e.Exception) 148 } 149 if e.Status != "" { 150 out = append(out, e.Status) 151 } 152 if len(out) == 0 { 153 return "Webdav Error" 154 } 155 return strings.Join(out, ": ") 156 } 157 158 // Time represents date and time information for the 159 // webdav API marshalling to and from timeFormat 160 type Time time.Time 161 162 // MarshalXML turns a Time into XML 163 func (t *Time) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 164 timeString := (*time.Time)(t).Format(timeFormat) 165 return e.EncodeElement(timeString, start) 166 } 167 168 // Possible time formats to parse the time with 169 var timeFormats = []string{ 170 timeFormat, // Wed, 27 Sep 2017 14:28:34 GMT (as per RFC) 171 time.RFC1123Z, // Fri, 05 Jan 2018 14:14:38 +0000 (as used by mydrive.ch) 172 time.UnixDate, // Wed May 17 15:31:58 UTC 2017 (as used in an internal server) 173 noZerosRFC1123, // Fri, 7 Sep 2018 08:49:58 GMT (as used by server in #2574) 174 time.RFC3339, // Wed, 31 Oct 2018 13:57:11 CET (as used by komfortcloud.de) 175 } 176 177 var oneTimeError sync.Once 178 179 // UnmarshalXML turns XML into a Time 180 func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 181 var v string 182 err := d.DecodeElement(&v, &start) 183 if err != nil { 184 return err 185 } 186 187 // If time is missing then return the epoch 188 if v == "" { 189 *t = Time(time.Unix(0, 0)) 190 return nil 191 } 192 193 // Parse the time format in multiple possible ways 194 var newT time.Time 195 for _, timeFormat := range timeFormats { 196 newT, err = time.Parse(timeFormat, v) 197 if err == nil { 198 *t = Time(newT) 199 break 200 } 201 } 202 if err != nil { 203 oneTimeError.Do(func() { 204 fs.Errorf(nil, "Failed to parse time %q - using the epoch", v) 205 }) 206 // Return the epoch instead 207 *t = Time(time.Unix(0, 0)) 208 // ignore error 209 err = nil 210 } 211 return err 212 } 213 214 // Quota is used to read the bytes used and available 215 // 216 // <d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns"> 217 // <d:response> 218 // <d:href>/remote.php/webdav/</d:href> 219 // <d:propstat> 220 // <d:prop> 221 // <d:quota-available-bytes>-3</d:quota-available-bytes> 222 // <d:quota-used-bytes>376461895</d:quota-used-bytes> 223 // </d:prop> 224 // <d:status>HTTP/1.1 200 OK</d:status> 225 // </d:propstat> 226 // </d:response> 227 // </d:multistatus> 228 type Quota struct { 229 Available string `xml:"DAV: response>propstat>prop>quota-available-bytes"` 230 Used string `xml:"DAV: response>propstat>prop>quota-used-bytes"` 231 }