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.
This commit is contained in:
nielash 2024-04-02 11:26:59 -04:00
parent 75df38f6ee
commit 71069ed5c1
2 changed files with 34 additions and 3 deletions

View File

@ -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) 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) baseURL, err := rest.URLJoin(f.endpoint, opts.Path)
if err != nil { if err != nil {
return false, fmt.Errorf("couldn't join URL: %w", err) 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 { if err != nil {
return nil, fmt.Errorf("copy NewObject failed: %w", err) 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)) fs.Debugf(dstObj, "Setting modtime after copy to %v", src.ModTime(ctx))
err = dstObj.SetModTime(ctx, src.ModTime(ctx)) err = dstObj.SetModTime(ctx, src.ModTime(ctx))
if err != nil { if err != nil {
@ -1364,15 +1364,38 @@ var owncloudPropset = `<?xml version="1.0" encoding="utf-8" ?>
</D:propertyupdate> </D:propertyupdate>
` `
var owncloudPropsetWithChecksum = `<?xml version="1.0" encoding="utf-8" ?>
<D:propertyupdate xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">
<D:set>
<D:prop>
<lastmodified xmlns="DAV:">%d</lastmodified>
<oc:checksums>
<oc:checksum>%s</oc:checksum>
</oc:checksums>
</D:prop>
</D:set>
</D:propertyupdate>
`
// SetModTime sets the modification time of the local fs object // SetModTime sets the modification time of the local fs object
func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
if o.fs.propsetMtime { 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{ opts := rest.Opts{
Method: "PROPPATCH", Method: "PROPPATCH",
Path: o.filePath(), Path: o.filePath(),
NoRedirect: true, NoRedirect: true,
Body: strings.NewReader(fmt.Sprintf(owncloudPropset, modTime.Unix())), 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 result api.Multistatus
var resp *http.Response var resp *http.Response
var err error var err error
@ -1395,6 +1418,14 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
o.modTime = modTime o.modTime = modTime
return nil 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 // fallback
return fs.ErrorCantSetModTime return fs.ErrorCantSetModTime
} }
@ -1521,7 +1552,6 @@ func (o *Object) updateSimple(ctx context.Context, body io.Reader, getBody func(
return err return err
} }
return nil return nil
} }
// Remove an object // Remove an object

View File

@ -90,6 +90,7 @@ var logReplacements = []string{
`^NOTICE: .*?: Forced to upload files to set modification times on this backend.$`, dropMe, `^NOTICE: .*?: Forced to upload files to set modification times on this backend.$`, dropMe,
`^INFO : .*? Committing uploads - please wait...$`, 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 deleting and re-uploading$`, dropMe,
`^INFO : .*?: src and dst identical but can't set mod time without re-uploading$`, dropMe,
// ignore crypt info messages // ignore crypt info messages
`^INFO : .*?: Crypt detected! Using cryptcheck instead of check. \(Use --size-only or --ignore-checksum to disable\)$`, dropMe, `^INFO : .*?: Crypt detected! Using cryptcheck instead of check. \(Use --size-only or --ignore-checksum to disable\)$`, dropMe,
// ignore drive info messages // ignore drive info messages