github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/webdav/webdav.go (about) 1 // Package webdav provides an interface to the Webdav 2 // object storage system. 3 package webdav 4 5 // SetModTime might be possible 6 // https://stackoverflow.com/questions/3579608/webdav-can-a-client-modify-the-mtime-of-a-file 7 // ...support for a PROPSET to lastmodified (mind the missing get) which does the utime() call might be an option. 8 // For example the ownCloud WebDAV server does it that way. 9 10 import ( 11 "bytes" 12 "context" 13 "encoding/xml" 14 "fmt" 15 "io" 16 "net/http" 17 "net/url" 18 "os/exec" 19 "path" 20 "strings" 21 "time" 22 23 "github.com/ncw/rclone/backend/webdav/api" 24 "github.com/ncw/rclone/backend/webdav/odrvcookie" 25 "github.com/ncw/rclone/fs" 26 "github.com/ncw/rclone/fs/config/configmap" 27 "github.com/ncw/rclone/fs/config/configstruct" 28 "github.com/ncw/rclone/fs/config/obscure" 29 "github.com/ncw/rclone/fs/fserrors" 30 "github.com/ncw/rclone/fs/fshttp" 31 "github.com/ncw/rclone/fs/hash" 32 "github.com/ncw/rclone/lib/pacer" 33 "github.com/ncw/rclone/lib/rest" 34 "github.com/pkg/errors" 35 ) 36 37 const ( 38 minSleep = 10 * time.Millisecond 39 maxSleep = 2 * time.Second 40 decayConstant = 2 // bigger for slower decay, exponential 41 defaultDepth = "1" // depth for PROPFIND 42 ) 43 44 // Register with Fs 45 func init() { 46 fs.Register(&fs.RegInfo{ 47 Name: "webdav", 48 Description: "Webdav", 49 NewFs: NewFs, 50 Options: []fs.Option{{ 51 Name: "url", 52 Help: "URL of http host to connect to", 53 Required: true, 54 Examples: []fs.OptionExample{{ 55 Value: "https://example.com", 56 Help: "Connect to example.com", 57 }}, 58 }, { 59 Name: "vendor", 60 Help: "Name of the Webdav site/service/software you are using", 61 Examples: []fs.OptionExample{{ 62 Value: "nextcloud", 63 Help: "Nextcloud", 64 }, { 65 Value: "owncloud", 66 Help: "Owncloud", 67 }, { 68 Value: "sharepoint", 69 Help: "Sharepoint", 70 }, { 71 Value: "other", 72 Help: "Other site/service or software", 73 }}, 74 }, { 75 Name: "user", 76 Help: "User name", 77 }, { 78 Name: "pass", 79 Help: "Password.", 80 IsPassword: true, 81 }, { 82 Name: "bearer_token", 83 Help: "Bearer token instead of user/pass (eg a Macaroon)", 84 }, { 85 Name: "bearer_token_command", 86 Help: "Command to run to get a bearer token", 87 Advanced: true, 88 }}, 89 }) 90 } 91 92 // Options defines the configuration for this backend 93 type Options struct { 94 URL string `config:"url"` 95 Vendor string `config:"vendor"` 96 User string `config:"user"` 97 Pass string `config:"pass"` 98 BearerToken string `config:"bearer_token"` 99 BearerTokenCommand string `config:"bearer_token_command"` 100 } 101 102 // Fs represents a remote webdav 103 type Fs struct { 104 name string // name of this remote 105 root string // the path we are working on 106 opt Options // parsed options 107 features *fs.Features // optional features 108 endpoint *url.URL // URL of the host 109 endpointURL string // endpoint as a string 110 srv *rest.Client // the connection to the one drive server 111 pacer *fs.Pacer // pacer for API calls 112 precision time.Duration // mod time precision 113 canStream bool // set if can stream 114 useOCMtime bool // set if can use X-OC-Mtime 115 retryWithZeroDepth bool // some vendors (sharepoint) won't list files when Depth is 1 (our default) 116 hasChecksums bool // set if can use owncloud style checksums 117 } 118 119 // Object describes a webdav object 120 // 121 // Will definitely have info but maybe not meta 122 type Object struct { 123 fs *Fs // what this object is part of 124 remote string // The remote path 125 hasMetaData bool // whether info below has been set 126 size int64 // size of the object 127 modTime time.Time // modification time of the object 128 sha1 string // SHA-1 of the object content if known 129 md5 string // MD5 of the object content if known 130 } 131 132 // ------------------------------------------------------------ 133 134 // Name of the remote (as passed into NewFs) 135 func (f *Fs) Name() string { 136 return f.name 137 } 138 139 // Root of the remote (as passed into NewFs) 140 func (f *Fs) Root() string { 141 return f.root 142 } 143 144 // String converts this Fs to a string 145 func (f *Fs) String() string { 146 return fmt.Sprintf("webdav root '%s'", f.root) 147 } 148 149 // Features returns the optional features of this Fs 150 func (f *Fs) Features() *fs.Features { 151 return f.features 152 } 153 154 // retryErrorCodes is a slice of error codes that we will retry 155 var retryErrorCodes = []int{ 156 423, // Locked 157 429, // Too Many Requests. 158 500, // Internal Server Error 159 502, // Bad Gateway 160 503, // Service Unavailable 161 504, // Gateway Timeout 162 509, // Bandwidth Limit Exceeded 163 } 164 165 // shouldRetry returns a boolean as to whether this resp and err 166 // deserve to be retried. It returns the err as a convenience 167 func (f *Fs) shouldRetry(resp *http.Response, err error) (bool, error) { 168 // If we have a bearer token command and it has expired then refresh it 169 if f.opt.BearerTokenCommand != "" && resp != nil && resp.StatusCode == 401 { 170 fs.Debugf(f, "Bearer token expired: %v", err) 171 authErr := f.fetchAndSetBearerToken() 172 if authErr != nil { 173 err = authErr 174 } 175 return true, err 176 } 177 return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 178 } 179 180 // itemIsDir returns true if the item is a directory 181 // 182 // When a client sees a resourcetype it doesn't recognize it should 183 // assume it is a regular non-collection resource. [WebDav book by 184 // Lisa Dusseault ch 7.5.8 p170] 185 func itemIsDir(item *api.Response) bool { 186 if t := item.Props.Type; t != nil { 187 if t.Space == "DAV:" && t.Local == "collection" { 188 return true 189 } 190 fs.Debugf(nil, "Unknown resource type %q/%q on %q", t.Space, t.Local, item.Props.Name) 191 } 192 // the iscollection prop is a Microsoft extension, but if present it is a reliable indicator 193 // if the above check failed - see #2716. This can be an integer or a boolean - see #2964 194 if t := item.Props.IsCollection; t != nil { 195 switch x := strings.ToLower(*t); x { 196 case "0", "false": 197 return false 198 case "1", "true": 199 return true 200 default: 201 fs.Debugf(nil, "Unknown value %q for IsCollection", x) 202 } 203 } 204 return false 205 } 206 207 // readMetaDataForPath reads the metadata from the path 208 func (f *Fs) readMetaDataForPath(path string, depth string) (info *api.Prop, err error) { 209 // FIXME how do we read back additional properties? 210 opts := rest.Opts{ 211 Method: "PROPFIND", 212 Path: f.filePath(path), 213 ExtraHeaders: map[string]string{ 214 "Depth": depth, 215 }, 216 NoRedirect: true, 217 } 218 if f.hasChecksums { 219 opts.Body = bytes.NewBuffer(owncloudProps) 220 } 221 var result api.Multistatus 222 var resp *http.Response 223 err = f.pacer.Call(func() (bool, error) { 224 resp, err = f.srv.CallXML(&opts, nil, &result) 225 return f.shouldRetry(resp, err) 226 }) 227 if apiErr, ok := err.(*api.Error); ok { 228 // does not exist 229 switch apiErr.StatusCode { 230 case http.StatusNotFound: 231 if f.retryWithZeroDepth && depth != "0" { 232 return f.readMetaDataForPath(path, "0") 233 } 234 return nil, fs.ErrorObjectNotFound 235 case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther: 236 // Some sort of redirect - go doesn't deal with these properly (it resets 237 // the method to GET). However we can assume that if it was redirected the 238 // object was not found. 239 return nil, fs.ErrorObjectNotFound 240 } 241 } 242 if err != nil { 243 return nil, errors.Wrap(err, "read metadata failed") 244 } 245 if len(result.Responses) < 1 { 246 return nil, fs.ErrorObjectNotFound 247 } 248 item := result.Responses[0] 249 if !item.Props.StatusOK() { 250 return nil, fs.ErrorObjectNotFound 251 } 252 if itemIsDir(&item) { 253 return nil, fs.ErrorNotAFile 254 } 255 return &item.Props, nil 256 } 257 258 // errorHandler parses a non 2xx error response into an error 259 func errorHandler(resp *http.Response) error { 260 body, err := rest.ReadBody(resp) 261 if err != nil { 262 return errors.Wrap(err, "error when trying to read error from body") 263 } 264 // Decode error response 265 errResponse := new(api.Error) 266 err = xml.Unmarshal(body, &errResponse) 267 if err != nil { 268 // set the Message to be the body if can't parse the XML 269 errResponse.Message = strings.TrimSpace(string(body)) 270 } 271 errResponse.Status = resp.Status 272 errResponse.StatusCode = resp.StatusCode 273 return errResponse 274 } 275 276 // addSlash makes sure s is terminated with a / if non empty 277 func addSlash(s string) string { 278 if s != "" && !strings.HasSuffix(s, "/") { 279 s += "/" 280 } 281 return s 282 } 283 284 // filePath returns a file path (f.root, file) 285 func (f *Fs) filePath(file string) string { 286 return rest.URLPathEscape(path.Join(f.root, file)) 287 } 288 289 // dirPath returns a directory path (f.root, dir) 290 func (f *Fs) dirPath(dir string) string { 291 return addSlash(f.filePath(dir)) 292 } 293 294 // filePath returns a file path (f.root, remote) 295 func (o *Object) filePath() string { 296 return o.fs.filePath(o.remote) 297 } 298 299 // NewFs constructs an Fs from the path, container:path 300 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 301 ctx := context.Background() 302 // Parse config into Options struct 303 opt := new(Options) 304 err := configstruct.Set(m, opt) 305 if err != nil { 306 return nil, err 307 } 308 rootIsDir := strings.HasSuffix(root, "/") 309 root = strings.Trim(root, "/") 310 311 if !strings.HasSuffix(opt.URL, "/") { 312 opt.URL += "/" 313 } 314 if opt.Pass != "" { 315 var err error 316 opt.Pass, err = obscure.Reveal(opt.Pass) 317 if err != nil { 318 return nil, errors.Wrap(err, "couldn't decrypt password") 319 } 320 } 321 if opt.Vendor == "" { 322 opt.Vendor = "other" 323 } 324 root = strings.Trim(root, "/") 325 326 // Parse the endpoint 327 u, err := url.Parse(opt.URL) 328 if err != nil { 329 return nil, err 330 } 331 332 f := &Fs{ 333 name: name, 334 root: root, 335 opt: *opt, 336 endpoint: u, 337 endpointURL: u.String(), 338 srv: rest.NewClient(fshttp.NewClient(fs.Config)).SetRoot(u.String()), 339 pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), 340 precision: fs.ModTimeNotSupported, 341 } 342 f.features = (&fs.Features{ 343 CanHaveEmptyDirectories: true, 344 }).Fill(f) 345 if opt.User != "" || opt.Pass != "" { 346 f.srv.SetUserPass(opt.User, opt.Pass) 347 } else if opt.BearerToken != "" { 348 f.setBearerToken(opt.BearerToken) 349 } else if f.opt.BearerTokenCommand != "" { 350 err = f.fetchAndSetBearerToken() 351 if err != nil { 352 return nil, err 353 } 354 } 355 f.srv.SetErrorHandler(errorHandler) 356 err = f.setQuirks(opt.Vendor) 357 if err != nil { 358 return nil, err 359 } 360 361 if root != "" && !rootIsDir { 362 // Check to see if the root actually an existing file 363 remote := path.Base(root) 364 f.root = path.Dir(root) 365 if f.root == "." { 366 f.root = "" 367 } 368 _, err := f.NewObject(ctx, remote) 369 if err != nil { 370 if errors.Cause(err) == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile { 371 // File doesn't exist so return old f 372 f.root = root 373 return f, nil 374 } 375 return nil, err 376 } 377 // return an error with an fs which points to the parent 378 return f, fs.ErrorIsFile 379 } 380 return f, nil 381 } 382 383 // sets the BearerToken up 384 func (f *Fs) setBearerToken(token string) { 385 f.opt.BearerToken = token 386 f.srv.SetHeader("Authorization", "BEARER "+token) 387 } 388 389 // fetch the bearer token using the command 390 func (f *Fs) fetchBearerToken(cmd string) (string, error) { 391 var ( 392 args = strings.Split(cmd, " ") 393 stdout bytes.Buffer 394 stderr bytes.Buffer 395 c = exec.Command(args[0], args[1:]...) 396 ) 397 c.Stdout = &stdout 398 c.Stderr = &stderr 399 var ( 400 err = c.Run() 401 stdoutString = strings.TrimSpace(stdout.String()) 402 stderrString = strings.TrimSpace(stderr.String()) 403 ) 404 if err != nil { 405 if stderrString == "" { 406 stderrString = stdoutString 407 } 408 return "", errors.Wrapf(err, "failed to get bearer token using %q: %s", f.opt.BearerTokenCommand, stderrString) 409 } 410 return stdoutString, nil 411 } 412 413 // fetch the bearer token and set it if successful 414 func (f *Fs) fetchAndSetBearerToken() error { 415 if f.opt.BearerTokenCommand == "" { 416 return nil 417 } 418 token, err := f.fetchBearerToken(f.opt.BearerTokenCommand) 419 if err != nil { 420 return err 421 } 422 f.setBearerToken(token) 423 return nil 424 } 425 426 // setQuirks adjusts the Fs for the vendor passed in 427 func (f *Fs) setQuirks(vendor string) error { 428 switch vendor { 429 case "owncloud": 430 f.canStream = true 431 f.precision = time.Second 432 f.useOCMtime = true 433 f.hasChecksums = true 434 case "nextcloud": 435 f.precision = time.Second 436 f.useOCMtime = true 437 f.hasChecksums = true 438 case "sharepoint": 439 // To mount sharepoint, two Cookies are required 440 // They have to be set instead of BasicAuth 441 f.srv.RemoveHeader("Authorization") // We don't need this Header if using cookies 442 spCk := odrvcookie.New(f.opt.User, f.opt.Pass, f.endpointURL) 443 spCookies, err := spCk.Cookies() 444 if err != nil { 445 return err 446 } 447 448 odrvcookie.NewRenew(12*time.Hour, func() { 449 spCookies, err := spCk.Cookies() 450 if err != nil { 451 fs.Errorf("could not renew cookies: %s", err.Error()) 452 return 453 } 454 f.srv.SetCookie(&spCookies.FedAuth, &spCookies.RtFa) 455 fs.Debugf(spCookies, "successfully renewed sharepoint cookies") 456 }) 457 458 f.srv.SetCookie(&spCookies.FedAuth, &spCookies.RtFa) 459 460 // sharepoint, unlike the other vendors, only lists files if the depth header is set to 0 461 // however, rclone defaults to 1 since it provides recursive directory listing 462 // to determine if we may have found a file, the request has to be resent 463 // with the depth set to 0 464 f.retryWithZeroDepth = true 465 case "other": 466 default: 467 fs.Debugf(f, "Unknown vendor %q", vendor) 468 } 469 470 // Remove PutStream from optional features 471 if !f.canStream { 472 f.features.PutStream = nil 473 } 474 return nil 475 } 476 477 // Return an Object from a path 478 // 479 // If it can't be found it returns the error fs.ErrorObjectNotFound. 480 func (f *Fs) newObjectWithInfo(remote string, info *api.Prop) (fs.Object, error) { 481 o := &Object{ 482 fs: f, 483 remote: remote, 484 } 485 var err error 486 if info != nil { 487 // Set info 488 err = o.setMetaData(info) 489 } else { 490 err = o.readMetaData() // reads info and meta, returning an error 491 } 492 if err != nil { 493 return nil, err 494 } 495 return o, nil 496 } 497 498 // NewObject finds the Object at remote. If it can't be found 499 // it returns the error fs.ErrorObjectNotFound. 500 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 501 return f.newObjectWithInfo(remote, nil) 502 } 503 504 // Read the normal props, plus the checksums 505 // 506 // <oc:checksums><oc:checksum>SHA1:f572d396fae9206628714fb2ce00f72e94f2258f MD5:b1946ac92492d2347c6235b4d2611184 ADLER32:084b021f</oc:checksum></oc:checksums> 507 var owncloudProps = []byte(`<?xml version="1.0"?> 508 <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns"> 509 <d:prop> 510 <d:displayname /> 511 <d:getlastmodified /> 512 <d:getcontentlength /> 513 <d:resourcetype /> 514 <d:getcontenttype /> 515 <oc:checksums /> 516 </d:prop> 517 </d:propfind> 518 `) 519 520 // list the objects into the function supplied 521 // 522 // If directories is set it only sends directories 523 // User function to process a File item from listAll 524 // 525 // Should return true to finish processing 526 type listAllFn func(string, bool, *api.Prop) bool 527 528 // Lists the directory required calling the user function on each item found 529 // 530 // If the user fn ever returns true then it early exits with found = true 531 func (f *Fs) listAll(dir string, directoriesOnly bool, filesOnly bool, depth string, fn listAllFn) (found bool, err error) { 532 opts := rest.Opts{ 533 Method: "PROPFIND", 534 Path: f.dirPath(dir), // FIXME Should not start with / 535 ExtraHeaders: map[string]string{ 536 "Depth": depth, 537 }, 538 } 539 if f.hasChecksums { 540 opts.Body = bytes.NewBuffer(owncloudProps) 541 } 542 var result api.Multistatus 543 var resp *http.Response 544 err = f.pacer.Call(func() (bool, error) { 545 resp, err = f.srv.CallXML(&opts, nil, &result) 546 return f.shouldRetry(resp, err) 547 }) 548 if err != nil { 549 if apiErr, ok := err.(*api.Error); ok { 550 // does not exist 551 if apiErr.StatusCode == http.StatusNotFound { 552 if f.retryWithZeroDepth && depth != "0" { 553 return f.listAll(dir, directoriesOnly, filesOnly, "0", fn) 554 } 555 return found, fs.ErrorDirNotFound 556 } 557 } 558 return found, errors.Wrap(err, "couldn't list files") 559 } 560 //fmt.Printf("result = %#v", &result) 561 baseURL, err := rest.URLJoin(f.endpoint, opts.Path) 562 if err != nil { 563 return false, errors.Wrap(err, "couldn't join URL") 564 } 565 for i := range result.Responses { 566 item := &result.Responses[i] 567 isDir := itemIsDir(item) 568 569 // Find name 570 u, err := rest.URLJoin(baseURL, item.Href) 571 if err != nil { 572 fs.Errorf(nil, "URL Join failed for %q and %q: %v", baseURL, item.Href, err) 573 continue 574 } 575 // Make sure directories end with a / 576 if isDir { 577 u.Path = addSlash(u.Path) 578 } 579 if !strings.HasPrefix(u.Path, baseURL.Path) { 580 fs.Debugf(nil, "Item with unknown path received: %q, %q", u.Path, baseURL.Path) 581 continue 582 } 583 remote := path.Join(dir, u.Path[len(baseURL.Path):]) 584 if strings.HasSuffix(remote, "/") { 585 remote = remote[:len(remote)-1] 586 } 587 588 // the listing contains info about itself which we ignore 589 if remote == dir { 590 continue 591 } 592 593 // Check OK 594 if !item.Props.StatusOK() { 595 fs.Debugf(remote, "Ignoring item with bad status %q", item.Props.Status) 596 continue 597 } 598 599 if isDir { 600 if filesOnly { 601 continue 602 } 603 } else { 604 if directoriesOnly { 605 continue 606 } 607 } 608 // item.Name = restoreReservedChars(item.Name) 609 if fn(remote, isDir, &item.Props) { 610 found = true 611 break 612 } 613 } 614 return 615 } 616 617 // List the objects and directories in dir into entries. The 618 // entries can be returned in any order but should be for a 619 // complete directory. 620 // 621 // dir should be "" to list the root, and should not have 622 // trailing slashes. 623 // 624 // This should return ErrDirNotFound if the directory isn't 625 // found. 626 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 627 var iErr error 628 _, err = f.listAll(dir, false, false, defaultDepth, func(remote string, isDir bool, info *api.Prop) bool { 629 if isDir { 630 d := fs.NewDir(remote, time.Time(info.Modified)) 631 // .SetID(info.ID) 632 // FIXME more info from dir? can set size, items? 633 entries = append(entries, d) 634 } else { 635 o, err := f.newObjectWithInfo(remote, info) 636 if err != nil { 637 iErr = err 638 return true 639 } 640 entries = append(entries, o) 641 } 642 return false 643 }) 644 if err != nil { 645 return nil, err 646 } 647 if iErr != nil { 648 return nil, iErr 649 } 650 return entries, nil 651 } 652 653 // Creates from the parameters passed in a half finished Object which 654 // must have setMetaData called on it 655 // 656 // Used to create new objects 657 func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Object) { 658 // Temporary Object under construction 659 o = &Object{ 660 fs: f, 661 remote: remote, 662 size: size, 663 modTime: modTime, 664 } 665 return o 666 } 667 668 // Put the object 669 // 670 // Copy the reader in to the new object which is returned 671 // 672 // The new object may have been created if an error is returned 673 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 674 o := f.createObject(src.Remote(), src.ModTime(ctx), src.Size()) 675 return o, o.Update(ctx, in, src, options...) 676 } 677 678 // PutStream uploads to the remote path with the modTime given of indeterminate size 679 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 680 return f.Put(ctx, in, src, options...) 681 } 682 683 // mkParentDir makes the parent of the native path dirPath if 684 // necessary and any directories above that 685 func (f *Fs) mkParentDir(ctx context.Context, dirPath string) error { 686 // defer log.Trace(dirPath, "")("") 687 // chop off trailing / if it exists 688 if strings.HasSuffix(dirPath, "/") { 689 dirPath = dirPath[:len(dirPath)-1] 690 } 691 parent := path.Dir(dirPath) 692 if parent == "." { 693 parent = "" 694 } 695 return f.mkdir(ctx, parent) 696 } 697 698 // low level mkdir, only makes the directory, doesn't attempt to create parents 699 func (f *Fs) _mkdir(dirPath string) error { 700 // We assume the root is already created 701 if dirPath == "" { 702 return nil 703 } 704 // Collections must end with / 705 if !strings.HasSuffix(dirPath, "/") { 706 dirPath += "/" 707 } 708 opts := rest.Opts{ 709 Method: "MKCOL", 710 Path: dirPath, 711 NoResponse: true, 712 } 713 err := f.pacer.Call(func() (bool, error) { 714 resp, err := f.srv.Call(&opts) 715 return f.shouldRetry(resp, err) 716 }) 717 if apiErr, ok := err.(*api.Error); ok { 718 // already exists 719 // owncloud returns 423/StatusLocked if the create is already in progress 720 if apiErr.StatusCode == http.StatusMethodNotAllowed || apiErr.StatusCode == http.StatusNotAcceptable || apiErr.StatusCode == http.StatusLocked { 721 return nil 722 } 723 } 724 return err 725 } 726 727 // mkdir makes the directory and parents using native paths 728 func (f *Fs) mkdir(ctx context.Context, dirPath string) error { 729 // defer log.Trace(dirPath, "")("") 730 err := f._mkdir(dirPath) 731 if apiErr, ok := err.(*api.Error); ok { 732 // parent does not exist so create it first then try again 733 if apiErr.StatusCode == http.StatusConflict { 734 err = f.mkParentDir(ctx, dirPath) 735 if err == nil { 736 err = f._mkdir(dirPath) 737 } 738 } 739 } 740 return err 741 } 742 743 // Mkdir creates the directory if it doesn't exist 744 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 745 dirPath := f.dirPath(dir) 746 return f.mkdir(ctx, dirPath) 747 } 748 749 // dirNotEmpty returns true if the directory exists and is not Empty 750 // 751 // if the directory does not exist then err will be ErrorDirNotFound 752 func (f *Fs) dirNotEmpty(dir string) (found bool, err error) { 753 return f.listAll(dir, false, false, defaultDepth, func(remote string, isDir bool, info *api.Prop) bool { 754 return true 755 }) 756 } 757 758 // purgeCheck removes the root directory, if check is set then it 759 // refuses to do so if it has anything in 760 func (f *Fs) purgeCheck(dir string, check bool) error { 761 if check { 762 notEmpty, err := f.dirNotEmpty(dir) 763 if err != nil { 764 return err 765 } 766 if notEmpty { 767 return fs.ErrorDirectoryNotEmpty 768 } 769 } 770 opts := rest.Opts{ 771 Method: "DELETE", 772 Path: f.dirPath(dir), 773 NoResponse: true, 774 } 775 var resp *http.Response 776 var err error 777 err = f.pacer.Call(func() (bool, error) { 778 resp, err = f.srv.CallXML(&opts, nil, nil) 779 return f.shouldRetry(resp, err) 780 }) 781 if err != nil { 782 return errors.Wrap(err, "rmdir failed") 783 } 784 // FIXME parse Multistatus response 785 return nil 786 } 787 788 // Rmdir deletes the root folder 789 // 790 // Returns an error if it isn't empty 791 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 792 return f.purgeCheck(dir, true) 793 } 794 795 // Precision return the precision of this Fs 796 func (f *Fs) Precision() time.Duration { 797 return f.precision 798 } 799 800 // Copy or Move src to this remote using server side copy operations. 801 // 802 // This is stored with the remote path given 803 // 804 // It returns the destination Object and a possible error 805 // 806 // Will only be called if src.Fs().Name() == f.Name() 807 // 808 // If it isn't possible then return fs.ErrorCantCopy/fs.ErrorCantMove 809 func (f *Fs) copyOrMove(ctx context.Context, src fs.Object, remote string, method string) (fs.Object, error) { 810 srcObj, ok := src.(*Object) 811 if !ok { 812 fs.Debugf(src, "Can't copy - not same remote type") 813 if method == "COPY" { 814 return nil, fs.ErrorCantCopy 815 } 816 return nil, fs.ErrorCantMove 817 } 818 dstPath := f.filePath(remote) 819 err := f.mkParentDir(ctx, dstPath) 820 if err != nil { 821 return nil, errors.Wrap(err, "Copy mkParentDir failed") 822 } 823 destinationURL, err := rest.URLJoin(f.endpoint, dstPath) 824 if err != nil { 825 return nil, errors.Wrap(err, "copyOrMove couldn't join URL") 826 } 827 var resp *http.Response 828 opts := rest.Opts{ 829 Method: method, 830 Path: srcObj.filePath(), 831 NoResponse: true, 832 ExtraHeaders: map[string]string{ 833 "Destination": destinationURL.String(), 834 "Overwrite": "F", 835 }, 836 } 837 if f.useOCMtime { 838 opts.ExtraHeaders["X-OC-Mtime"] = fmt.Sprintf("%f", float64(src.ModTime(ctx).UnixNano())/1E9) 839 } 840 err = f.pacer.Call(func() (bool, error) { 841 resp, err = f.srv.Call(&opts) 842 return f.shouldRetry(resp, err) 843 }) 844 if err != nil { 845 return nil, errors.Wrap(err, "Copy call failed") 846 } 847 dstObj, err := f.NewObject(ctx, remote) 848 if err != nil { 849 return nil, errors.Wrap(err, "Copy NewObject failed") 850 } 851 return dstObj, nil 852 } 853 854 // Copy src to this remote using server side copy operations. 855 // 856 // This is stored with the remote path given 857 // 858 // It returns the destination Object and a possible error 859 // 860 // Will only be called if src.Fs().Name() == f.Name() 861 // 862 // If it isn't possible then return fs.ErrorCantCopy 863 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 864 return f.copyOrMove(ctx, src, remote, "COPY") 865 } 866 867 // Purge deletes all the files and the container 868 // 869 // Optional interface: Only implement this if you have a way of 870 // deleting all the files quicker than just running Remove() on the 871 // result of List() 872 func (f *Fs) Purge(ctx context.Context) error { 873 return f.purgeCheck("", false) 874 } 875 876 // Move src to this remote using server side move operations. 877 // 878 // This is stored with the remote path given 879 // 880 // It returns the destination Object and a possible error 881 // 882 // Will only be called if src.Fs().Name() == f.Name() 883 // 884 // If it isn't possible then return fs.ErrorCantMove 885 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 886 return f.copyOrMove(ctx, src, remote, "MOVE") 887 } 888 889 // DirMove moves src, srcRemote to this remote at dstRemote 890 // using server side move operations. 891 // 892 // Will only be called if src.Fs().Name() == f.Name() 893 // 894 // If it isn't possible then return fs.ErrorCantDirMove 895 // 896 // If destination exists then return fs.ErrorDirExists 897 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 898 srcFs, ok := src.(*Fs) 899 if !ok { 900 fs.Debugf(srcFs, "Can't move directory - not same remote type") 901 return fs.ErrorCantDirMove 902 } 903 srcPath := srcFs.filePath(srcRemote) 904 dstPath := f.filePath(dstRemote) 905 906 // Check if destination exists 907 _, err := f.dirNotEmpty(dstRemote) 908 if err == nil { 909 return fs.ErrorDirExists 910 } 911 if err != fs.ErrorDirNotFound { 912 return errors.Wrap(err, "DirMove dirExists dst failed") 913 } 914 915 // Make sure the parent directory exists 916 err = f.mkParentDir(ctx, dstPath) 917 if err != nil { 918 return errors.Wrap(err, "DirMove mkParentDir dst failed") 919 } 920 921 destinationURL, err := rest.URLJoin(f.endpoint, dstPath) 922 if err != nil { 923 return errors.Wrap(err, "DirMove couldn't join URL") 924 } 925 926 var resp *http.Response 927 opts := rest.Opts{ 928 Method: "MOVE", 929 Path: addSlash(srcPath), 930 NoResponse: true, 931 ExtraHeaders: map[string]string{ 932 "Destination": addSlash(destinationURL.String()), 933 "Overwrite": "F", 934 }, 935 } 936 err = f.pacer.Call(func() (bool, error) { 937 resp, err = f.srv.Call(&opts) 938 return f.shouldRetry(resp, err) 939 }) 940 if err != nil { 941 return errors.Wrap(err, "DirMove MOVE call failed") 942 } 943 return nil 944 } 945 946 // Hashes returns the supported hash sets. 947 func (f *Fs) Hashes() hash.Set { 948 if f.hasChecksums { 949 return hash.NewHashSet(hash.MD5, hash.SHA1) 950 } 951 return hash.Set(hash.None) 952 } 953 954 // About gets quota information 955 func (f *Fs) About(ctx context.Context) (*fs.Usage, error) { 956 opts := rest.Opts{ 957 Method: "PROPFIND", 958 Path: "", 959 ExtraHeaders: map[string]string{ 960 "Depth": "0", 961 }, 962 } 963 opts.Body = bytes.NewBuffer([]byte(`<?xml version="1.0" ?> 964 <D:propfind xmlns:D="DAV:"> 965 <D:prop> 966 <D:quota-available-bytes/> 967 <D:quota-used-bytes/> 968 </D:prop> 969 </D:propfind> 970 `)) 971 var q = api.Quota{ 972 Available: -1, 973 Used: -1, 974 } 975 var resp *http.Response 976 var err error 977 err = f.pacer.Call(func() (bool, error) { 978 resp, err = f.srv.CallXML(&opts, nil, &q) 979 return f.shouldRetry(resp, err) 980 }) 981 if err != nil { 982 return nil, errors.Wrap(err, "about call failed") 983 } 984 usage := &fs.Usage{} 985 if q.Available != 0 || q.Used != 0 { 986 if q.Available >= 0 && q.Used >= 0 { 987 usage.Total = fs.NewUsageValue(q.Available + q.Used) 988 } 989 if q.Used >= 0 { 990 usage.Used = fs.NewUsageValue(q.Used) 991 } 992 } 993 return usage, nil 994 } 995 996 // ------------------------------------------------------------ 997 998 // Fs returns the parent Fs 999 func (o *Object) Fs() fs.Info { 1000 return o.fs 1001 } 1002 1003 // Return a string version 1004 func (o *Object) String() string { 1005 if o == nil { 1006 return "<nil>" 1007 } 1008 return o.remote 1009 } 1010 1011 // Remote returns the remote path 1012 func (o *Object) Remote() string { 1013 return o.remote 1014 } 1015 1016 // Hash returns the SHA1 or MD5 of an object returning a lowercase hex string 1017 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 1018 if o.fs.hasChecksums { 1019 switch t { 1020 case hash.SHA1: 1021 return o.sha1, nil 1022 case hash.MD5: 1023 return o.md5, nil 1024 } 1025 } 1026 return "", hash.ErrUnsupported 1027 } 1028 1029 // Size returns the size of an object in bytes 1030 func (o *Object) Size() int64 { 1031 err := o.readMetaData() 1032 if err != nil { 1033 fs.Logf(o, "Failed to read metadata: %v", err) 1034 return 0 1035 } 1036 return o.size 1037 } 1038 1039 // setMetaData sets the metadata from info 1040 func (o *Object) setMetaData(info *api.Prop) (err error) { 1041 o.hasMetaData = true 1042 o.size = info.Size 1043 o.modTime = time.Time(info.Modified) 1044 if o.fs.hasChecksums { 1045 hashes := info.Hashes() 1046 o.sha1 = hashes[hash.SHA1] 1047 o.md5 = hashes[hash.MD5] 1048 } 1049 return nil 1050 } 1051 1052 // readMetaData gets the metadata if it hasn't already been fetched 1053 // 1054 // it also sets the info 1055 func (o *Object) readMetaData() (err error) { 1056 if o.hasMetaData { 1057 return nil 1058 } 1059 info, err := o.fs.readMetaDataForPath(o.remote, defaultDepth) 1060 if err != nil { 1061 return err 1062 } 1063 return o.setMetaData(info) 1064 } 1065 1066 // ModTime returns the modification time of the object 1067 // 1068 // It attempts to read the objects mtime and if that isn't present the 1069 // LastModified returned in the http headers 1070 func (o *Object) ModTime(ctx context.Context) time.Time { 1071 err := o.readMetaData() 1072 if err != nil { 1073 fs.Logf(o, "Failed to read metadata: %v", err) 1074 return time.Now() 1075 } 1076 return o.modTime 1077 } 1078 1079 // SetModTime sets the modification time of the local fs object 1080 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 1081 return fs.ErrorCantSetModTime 1082 } 1083 1084 // Storable returns a boolean showing whether this object storable 1085 func (o *Object) Storable() bool { 1086 return true 1087 } 1088 1089 // Open an object for read 1090 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 1091 var resp *http.Response 1092 opts := rest.Opts{ 1093 Method: "GET", 1094 Path: o.filePath(), 1095 Options: options, 1096 } 1097 err = o.fs.pacer.Call(func() (bool, error) { 1098 resp, err = o.fs.srv.Call(&opts) 1099 return o.fs.shouldRetry(resp, err) 1100 }) 1101 if err != nil { 1102 return nil, err 1103 } 1104 return resp.Body, err 1105 } 1106 1107 // Update the object with the contents of the io.Reader, modTime and size 1108 // 1109 // If existing is set then it updates the object rather than creating a new one 1110 // 1111 // The new object may have been created if an error is returned 1112 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 1113 err = o.fs.mkParentDir(ctx, o.filePath()) 1114 if err != nil { 1115 return errors.Wrap(err, "Update mkParentDir failed") 1116 } 1117 1118 size := src.Size() 1119 var resp *http.Response 1120 opts := rest.Opts{ 1121 Method: "PUT", 1122 Path: o.filePath(), 1123 Body: in, 1124 NoResponse: true, 1125 ContentLength: &size, // FIXME this isn't necessary with owncloud - See https://github.com/nextcloud/nextcloud-snap/issues/365 1126 ContentType: fs.MimeType(ctx, src), 1127 } 1128 if o.fs.useOCMtime || o.fs.hasChecksums { 1129 opts.ExtraHeaders = map[string]string{} 1130 if o.fs.useOCMtime { 1131 opts.ExtraHeaders["X-OC-Mtime"] = fmt.Sprintf("%f", float64(src.ModTime(ctx).UnixNano())/1E9) 1132 } 1133 if o.fs.hasChecksums { 1134 // Set an upload checksum - prefer SHA1 1135 // 1136 // This is used as an upload integrity test. If we set 1137 // only SHA1 here, owncloud will calculate the MD5 too. 1138 if sha1, _ := src.Hash(ctx, hash.SHA1); sha1 != "" { 1139 opts.ExtraHeaders["OC-Checksum"] = "SHA1:" + sha1 1140 } else if md5, _ := src.Hash(ctx, hash.MD5); md5 != "" { 1141 opts.ExtraHeaders["OC-Checksum"] = "MD5:" + md5 1142 } 1143 } 1144 } 1145 err = o.fs.pacer.CallNoRetry(func() (bool, error) { 1146 resp, err = o.fs.srv.Call(&opts) 1147 return o.fs.shouldRetry(resp, err) 1148 }) 1149 if err != nil { 1150 // Give the WebDAV server a chance to get its internal state in order after the 1151 // error. The error may have been local in which case we closed the connection. 1152 // The server may still be dealing with it for a moment. A sleep isn't ideal but I 1153 // haven't been able to think of a better method to find out if the server has 1154 // finished - ncw 1155 time.Sleep(1 * time.Second) 1156 // Remove failed upload 1157 _ = o.Remove(ctx) 1158 return err 1159 } 1160 // read metadata from remote 1161 o.hasMetaData = false 1162 return o.readMetaData() 1163 } 1164 1165 // Remove an object 1166 func (o *Object) Remove(ctx context.Context) error { 1167 opts := rest.Opts{ 1168 Method: "DELETE", 1169 Path: o.filePath(), 1170 NoResponse: true, 1171 } 1172 return o.fs.pacer.Call(func() (bool, error) { 1173 resp, err := o.fs.srv.Call(&opts) 1174 return o.fs.shouldRetry(resp, err) 1175 }) 1176 } 1177 1178 // Check the interfaces are satisfied 1179 var ( 1180 _ fs.Fs = (*Fs)(nil) 1181 _ fs.Purger = (*Fs)(nil) 1182 _ fs.PutStreamer = (*Fs)(nil) 1183 _ fs.Copier = (*Fs)(nil) 1184 _ fs.Mover = (*Fs)(nil) 1185 _ fs.DirMover = (*Fs)(nil) 1186 _ fs.Abouter = (*Fs)(nil) 1187 _ fs.Object = (*Object)(nil) 1188 )