nextcloud/server · error · Sabre\DAV\Exception
Unexpected EOF while reading stream.
Error message
Unexpected EOF while reading stream.
What it means
After reading Content-Length bytes of a part, feof() must still be false: a '\r\n' plus the next boundary (or the final boundary) has to follow. If the stream is already at EOF, the declared length overran the real body and parsing aborts with 'Unexpected EOF while reading stream.' (returned to the client as HTTP 400 by BulkUploadPlugin).
Source
Thrown at apps/dav/lib/BulkUpload/MultipartRequestParser.php:222
/**
* Return the content of a part of the multipart body.
*
* @throws Exception
* @throws BadRequest
*/
private function readPartContent(int $length): string {
if ($length === 0) {
$content = '';
} else {
$content = stream_get_line($this->stream, $length);
}
if ($content === false) {
throw new Exception("Fail to read part's content.");
}
if ($length !== 0 && feof($this->stream)) {
throw new Exception('Unexpected EOF while reading stream.');
}
// Read '\r\n'.
stream_get_contents($this->stream, 2);
return $content;
}
/**
* Compute the MD5 or checksum hash of the next x bytes.
* TODO: Drop $md5 argument when the latest desktop client that uses it is no longer supported.
*/
private function validateHash(int $length, string $fileMd5Header, string $checksumHeader): void {
if ($checksumHeader !== '') {
[$algorithm, $hash] = explode(':', $checksumHeader, 2);
} elseif ($fileMd5Header !== '') {
$algorithm = 'md5';
$hash = $fileMd5Header;View on GitHub (pinned to ecdeb153ff)
Solutions
- Set Content-Length to the exact byte count of the raw part body (strlen($content), never mb_strlen)
- Dump the wire payload and verify each part's byte count against its declared Content-Length
- Build the request with a maintained multipart library instead of manual concatenation
Example fix
// before "Content-Length: " . mb_strlen($content) . "\r\n" // counts code points, not bytes // after "Content-Length: " . strlen($content) . "\r\n" // exact byte length
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: length and hash must derive from the same final byte string
$content = $part['content'];
$headers = "Content-Length: " . strlen($content) . "\r\n"
. "X-File-MD5: " . md5($content) . "\r\n"; Prevention
- Freeze the part body bytes first, then derive Content-Length and hash from that same string
- Use strlen (bytes), never mb_strlen (code points)
- Ensure the trailing '\r\n--<boundary>' after each part is actually written
When it happens
Trigger: A part's Content-Length is larger than the bytes actually sent for that part, or the whole request body ends immediately after the part content so the trailing '\r\n--boundary' never arrives.
Common situations: Client computes the length in characters instead of bytes (mb_strlen vs strlen on UTF-8), hashes/lengths computed before an encoding transform, truncated transfer, or off-by-N bugs in hand-rolled multipart writers.
Related errors
- An error occurred while reading headers of a part
- The Content-Length header must not be null.
- An error occurred while checking content
- Boundary not found where it should be.
- 400
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/8530e1ced3dbf398.
Report an issue: GitHub.