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
- Have an existing file on local storage.
- Overwrite it via a single non-chunked WebDAV PUT and kill the connection mid-body (client announces the full Content-Length).
- Server answers 400 with the "Expected filesize" message.
- 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).
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: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:
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):
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
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_storagenot set (default).