nextcloud / nextcloud/server

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

Open
#62,321 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

1. to develop 32-feedback bug feature: dav high hotspot: file transfer performance
Dominant language
PHP
Stars
36.9k
Forks
5.2k
Avg merge
2d 3h
Merged PRs (30d)
713

Description

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).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the WebDAV PUT handling and the size-mismatch path that logs "Expected filesize"; reproduce the interrupted non-chunked upload against an existing local-storage file. Done means a failed PUT returns 400, discards the partial data, and leaves the previous file content unchanged on the next GET.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.