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  }