From 8e744de5e6c40ef612295fe084fe887fe759aa0a Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 25 Aug 2026 12:18:22 +0100 Subject: [PATCH] pikpak: fix truncated single part uploads reported as ok when source ends early If the source supplied fewer bytes than its declared size, the single part upload path accepted the short body and stored a truncated file recorded with the declared size, reporting a successful upload. The multipart path already checks for this. Count the bytes actually read and fail the upload if they do not match the declared size, which cancels the partially created file. This was found by the FsPutShortEOF integration test. --- backend/pikpak/pikpak.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/pikpak/pikpak.go b/backend/pikpak/pikpak.go index 59d10d294..df5c3f499 100644 --- a/backend/pikpak/pikpak.go +++ b/backend/pikpak/pikpak.go @@ -58,6 +58,7 @@ import ( "github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/random" + "github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/rest" "golang.org/x/oauth2" ) @@ -1446,10 +1447,11 @@ func (f *Fs) uploadByResumable(ctx context.Context, in io.Reader, name string, s if err != nil { return fmt.Errorf("failed to create upload client: %w", err) } + counter := readers.NewCountingReader(in) req := &s3.PutObjectInput{ Bucket: &p.Bucket, Key: &p.Key, - Body: io.NopCloser(in), + Body: io.NopCloser(counter), } // Apply upload options for _, option := range options { @@ -1477,7 +1479,14 @@ func (f *Fs) uploadByResumable(ctx context.Context, in io.Reader, name string, s _, err = client.PutObject(ctx, req, s3opts...) return f.shouldRetry(ctx, nil, err) }) - return + if err != nil { + return err + } + // Check for truncation of input + if size >= 0 && int64(counter.BytesRead()) != size { + return fmt.Errorf("expected %d bytes in input, but got %d: %w", size, counter.BytesRead(), io.ErrUnexpectedEOF) + } + return nil } func (f *Fs) upload(ctx context.Context, in io.Reader, leaf, dirID, gcid string, size int64, options ...fs.OpenOption) (info *api.File, err error) {