From 71069ed5c1942113fe28e39d7d4b2eb802ef7db5 Mon Sep 17 00:00:00 2001 From: nielash Date: Tue, 2 Apr 2024 11:26:59 -0400 Subject: [PATCH] webdav: fix SetModTime erasing checksums on owncloud and nextcloud Before this change, calling SetModTime on owncloud and nextcloud would inadvertently erase the object's stored hashes. This change fixes the issue, which was discovered by the bisync integration tests. --- backend/webdav/webdav.go | 36 +++++++++++++++++++++++++++++++++--- cmd/bisync/bisync_test.go | 1 + 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go index 6aaa9d1dd..587ae0fa5 100644 --- a/backend/webdav/webdav.go +++ b/backend/webdav/webdav.go @@ -759,7 +759,7 @@ func (f *Fs) listAll(ctx context.Context, dir string, directoriesOnly bool, file } return found, fmt.Errorf("couldn't list files: %w", err) } - //fmt.Printf("result = %#v", &result) + // fmt.Printf("result = %#v", &result) baseURL, err := rest.URLJoin(f.endpoint, opts.Path) if err != nil { return false, fmt.Errorf("couldn't join URL: %w", err) @@ -1110,7 +1110,7 @@ func (f *Fs) copyOrMove(ctx context.Context, src fs.Object, remote string, metho if err != nil { return nil, fmt.Errorf("copy NewObject failed: %w", err) } - if f.useOCMtime && resp.Header.Get("X-OC-Mtime") != "accepted" && f.propsetMtime { + if f.useOCMtime && resp.Header.Get("X-OC-Mtime") != "accepted" && f.propsetMtime && !dstObj.ModTime(ctx).Equal(src.ModTime(ctx)) { fs.Debugf(dstObj, "Setting modtime after copy to %v", src.ModTime(ctx)) err = dstObj.SetModTime(ctx, src.ModTime(ctx)) if err != nil { @@ -1364,15 +1364,38 @@ var owncloudPropset = ` ` +var owncloudPropsetWithChecksum = ` + + + + %d + + %s + + + + +` + // SetModTime sets the modification time of the local fs object func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { if o.fs.propsetMtime { + checksums := "" + if o.fs.hasOCSHA1 && o.sha1 != "" { + checksums = "SHA1:" + o.sha1 + } else if o.fs.hasOCMD5 && o.md5 != "" { + checksums = "MD5:" + o.md5 + } + opts := rest.Opts{ Method: "PROPPATCH", Path: o.filePath(), NoRedirect: true, Body: strings.NewReader(fmt.Sprintf(owncloudPropset, modTime.Unix())), } + if checksums != "" { + opts.Body = strings.NewReader(fmt.Sprintf(owncloudPropsetWithChecksum, modTime.Unix(), checksums)) + } var result api.Multistatus var resp *http.Response var err error @@ -1395,6 +1418,14 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { o.modTime = modTime return nil } + // got an error, but it's possible it actually worked, so double-check + newO, err := o.fs.NewObject(ctx, o.remote) + if err != nil { + return err + } + if newO.ModTime(ctx).Equal(modTime) { + return nil + } // fallback return fs.ErrorCantSetModTime } @@ -1521,7 +1552,6 @@ func (o *Object) updateSimple(ctx context.Context, body io.Reader, getBody func( return err } return nil - } // Remove an object diff --git a/cmd/bisync/bisync_test.go b/cmd/bisync/bisync_test.go index a9c1769d6..ec57ec0fa 100644 --- a/cmd/bisync/bisync_test.go +++ b/cmd/bisync/bisync_test.go @@ -90,6 +90,7 @@ var logReplacements = []string{ `^NOTICE: .*?: Forced to upload files to set modification times on this backend.$`, dropMe, `^INFO : .*? Committing uploads - please wait...$`, dropMe, `^INFO : .*?: src and dst identical but can't set mod time without deleting and re-uploading$`, dropMe, + `^INFO : .*?: src and dst identical but can't set mod time without re-uploading$`, dropMe, // ignore crypt info messages `^INFO : .*?: Crypt detected! Using cryptcheck instead of check. \(Use --size-only or --ignore-checksum to disable\)$`, dropMe, // ignore drive info messages