Skip to content

[Bug]: Interrupted non-chunked WebDAV PUT returns 400 but keeps the truncated body as live file content (data corruption) #62321

Description

@detrax

Bug description

An interrupted (non-chunked) WebDAV PUT to an existing file corrupted that file on the server, even though Nextcloud itself detected the size mismatch and returned HTTP 400.

Today a KeePass client (Strongbox on macOS) saved my kdbx database via a plain PUT /remote.php/dav/files/USER/path/db.kdbx. The connection dropped mid-transfer: the server received 2,277,376 of the announced 2,381,775 bytes (cut off exactly at a 4 KiB boundary). Nextcloud noticed this and answered 400, logging:

Expected filesize of 2381775 bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) 2277376 bytes. This can usually be fixed by checking the network connection...

So far so good. But the truncated body was kept as the live file content. The very next GET (seconds later) already served the 2,277,376-byte torso, and the log shows the file cache being adjusted to the truncated size:

fixing cached size of file id=XXXXXXX, cached size was 2381647, but the filesystem reported a size of 2277376

Every other client then synced the corrupted database down. I could only recover because files_versions still had the previous version.

Since the server clearly knows the upload was incomplete (it returns 400!), writing the partial data over the previous file content defeats the point of that check. I would have expected the write to go to a part file and be thrown away on mismatch, leaving the old file untouched.

Access log of the event (client IPs redacted):

"PUT /remote.php/dav/files/USER/z_wichtig/NewDatabase.kdbx HTTP/1.1" 204 1164 "-" "Strongbox/5795 CFNetwork/3860.600.21 Darwin/25.5.0"   <- good save, 36s earlier
"PUT /remote.php/dav/files/USER/z_wichtig/NewDatabase.kdbx HTTP/1.1" 400 1137 "-" "Strongbox/5795 CFNetwork/3860.600.21 Darwin/25.5.0"   <- interrupted save
"GET /remote.php/dav/files/USER/z_wichtig/NewDatabase.kdbx HTTP/1.1" 200 2278684 "-" "Strongbox/..."                                      <- serves the truncated file

There are older reports that look related (#6338, #37695) but they were closed with different root causes (chunking limits, LimitRequestBody). This case is a plain single-shot PUT on local storage, no proxy limits involved, and the 400 was correctly produced — the file just wasn't protected from the partial write.

Steps to reproduce

  1. Have an existing file on local storage.
  2. Overwrite it via a single non-chunked WebDAV PUT and kill the connection mid-body (client announces the full Content-Length).
  3. Server answers 400 with the "Expected filesize" message.
  4. GET the file: it now contains the truncated body instead of the previous content.

Expected behavior

A failed/incomplete PUT must never replace the previous file content. Partial data should land in a part file and be discarded on size mismatch.

Installation method

Community manual installation with Archive

Nextcloud Server version

32.0.10.1

Operating system

Ubuntu 22.04, Apache 2.4, PHP 8.3-FPM, local storage (no S3), no server-side encryption, part_file_in_storage not set (default).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions