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  }