From e1bf9405e2eb1b17fd77fc75f2eb9a501511af4f Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Fri, 7 Aug 2026 12:51:42 +0100 Subject: [PATCH] filelu: fix truncated files being uploaded successfully when the source ends early If the source supplied fewer bytes than its declared size, the truncated file was stored and the upload reported success with the object claiming the declared size. Count the bytes actually read from the source and if they do not match the declared size, remove the truncated file and return an error. This was found by the new TestRcatSizeShortEOF integration test. --- backend/filelu/filelu_object.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/filelu/filelu_object.go b/backend/filelu/filelu_object.go index 6665be767..d4bf87880 100644 --- a/backend/filelu/filelu_object.go +++ b/backend/filelu/filelu_object.go @@ -14,6 +14,7 @@ import ( "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/hash" + "github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/rest" ) @@ -141,6 +142,8 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadClo // Update updates the object with new data func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { size := src.Size() + counter := readers.NewCountingReader(in) + in = counter if size <= int64(o.fs.opt.UploadCutoff) { err := o.fs.uploadFile(ctx, in, o.remote) @@ -155,6 +158,15 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op } } + // Check the source supplied the number of bytes it declared + // otherwise a truncated file would be stored as a good upload + if size >= 0 && int64(counter.BytesRead()) != size { + if removeErr := o.Remove(ctx); removeErr != nil { + fs.Errorf(o, "Failed to remove partially transferred object: %v", removeErr) + } + return fmt.Errorf("expected %d bytes in input, but got %d: %w", size, counter.BytesRead(), io.ErrUnexpectedEOF) + } + o.size = size o.modTime = src.ModTime(ctx) return nil