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