serve s3: fix modtime not being set when only mtime metadata is supplied on PUT

The mtime metadata fallback was nested inside the X-Amz-Meta-Mtime
branch, so it only ran when X-Amz-Meta-Mtime was present but invalid -
and then set the modtime from the invalid value's failed parse rather
than parsing mtime. An object PUT with only mtime metadata kept the
upload time as its modtime.

Now the two keys are checked independently, as TouchObject already
does.
This commit is contained in:
Nick Craig-Wood
2026-08-11 20:58:48 +01:00
parent 531d873bd7
commit a3489456de
2 changed files with 27 additions and 1 deletions
+4 -1
View File
@@ -424,8 +424,11 @@ func (b *s3Backend) PutObject(
return result, _vfs.Chtimes(fp, ti, ti)
}
// ignore error since the file is successfully created
}
if val, ok := meta["mtime"]; ok {
if val, ok := meta["mtime"]; ok {
ti, err := swift.FloatStringToTime(val)
if err == nil {
b.storeModtime(fp, meta, val)
return result, _vfs.Chtimes(fp, ti, ti)
}
+23
View File
@@ -15,6 +15,7 @@ import (
"testing"
"time"
"github.com/ncw/swift/v2"
"github.com/rclone/gofakes3"
"github.com/rclone/rclone/cmd/serve/proxy"
"github.com/rclone/rclone/fs"
@@ -152,6 +153,28 @@ func TestPutObjectFailureNewKey(t *testing.T) {
}
}
// TestPutObjectMtime checks that the object's modtime is set from the
// "X-Amz-Meta-Mtime" or "mtime" metadata supplied with the PUT.
func TestPutObjectMtime(t *testing.T) {
want := fstest.Time("2011-12-25T12:59:59.123456789Z")
for _, metaKey := range []string{"X-Amz-Meta-Mtime", "mtime"} {
t.Run(metaKey, func(t *testing.T) {
b, f, bucket := newPutTestBackend(t, "", nil)
ctx := context.Background()
const object = "mtime.txt"
contents := []byte(random.String(50))
meta := map[string]string{metaKey: swift.TimeToFloatString(want)}
_, err := b.PutObject(ctx, bucket, object, meta, bytes.NewReader(contents), int64(len(contents)))
require.NoError(t, err)
o, err := f.NewObject(ctx, path.Join(bucket, object))
require.NoError(t, err)
fstest.AssertTimeEqualWithPrecision(t, object, want, o.ModTime(ctx), f.Precision())
})
}
}
// waitForObject waits for bucket/object to appear on the backing Fs (e.g.
// after the VFS write-back delay).
func waitForObject(t *testing.T, f fs.Fs, bucket, object string) {