nextcloud/server · error · Sabre\DAV\Exception
An error occurred while reading headers of a part
Error message
An error occurred while reading headers of a part
What it means
readPartHeaders() loops fgets() until the blank line '\r\n' that terminates a part's header block; fgets() returning false means EOF was reached first. The body ended in the middle of a part's headers, and this generic Sabre\DAV\Exception (default code 500) is thrown; inside parseNextPart() it is caught by BulkUploadPlugin and returned to the client as HTTP 400.
Source
Thrown at apps/dav/lib/BulkUpload/MultipartRequestParser.php:176
throw new BadRequest('Boundary not found where it should be.');
}
return fread($this->stream, strlen($this->boundary));
}
/**
* Return the headers of a part of the multipart body.
*
* @throws Exception
* @throws BadRequest
* @throws LengthRequired
*/
private function readPartHeaders(): array {
$headers = [];
while (($line = fgets($this->stream)) !== "\r\n") {
if ($line === false) {
throw new Exception('An error occurred while reading headers of a part');
}
if (!str_contains($line, ':')) {
$this->logger->error('Header missing ":" on bulk request: ' . json_encode($line));
throw new Exception('An error occurred while reading headers of a part', Http::STATUS_BAD_REQUEST);
}
try {
[$key, $value] = explode(':', $line, 2);
$headers[strtolower(trim($key))] = trim($value);
} catch (\Exception $e) {
throw new BadRequest('An error occurred while parsing headers of a part', Http::STATUS_BAD_REQUEST, $e);
}
}
if (!isset($headers['content-length'])) {
throw new LengthRequired('The Content-Length header must not be null.');
}View on GitHub (pinned to ecdeb153ff)
Solutions
- Retry with a tiny single-file bulk upload; if that succeeds the problem is size-, timeout-, or proxy-related
- Raise/verify proxy limits and timeouts for the DAV endpoint (nginx client_max_body_size, proxy_read_timeout; Apache LimitRequestBody, TimeOut)
- Make the client send a correct request Content-Length and complete the transfer; check access logs for 499/408/reset evidence
- Capture the wire body server-side to confirm exactly where it stops mid-headers
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: send an explicit, correct Content-Length and finish the body
$payload = buildMultipartBody($parts, $boundary);
$ch = curl_init('https://host/remote.php/dav/bulk');
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); // sets Content-Length = strlen($payload)
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/related; boundary=' . $boundary]); Prevention
- Keep each bulk upload small enough to finish within proxy and client timeouts
- Verify request Content-Length equals actual bytes sent before the request leaves
- Watch for proxy truncation (499/400 patterns) when uploads fail mid-body
When it happens
Trigger: POST to /dav/bulk whose body is truncated inside a part's header block: the request ended early (request Content-Length larger than bytes actually sent, chunked transfer aborted, connection dropped) so the terminating '\r\n' blank line never arrives.
Common situations: Client timeouts or connection resets during large multi-file uploads; reverse proxy limits truncating request bodies (nginx client_max_body_size, Apache LimitRequestBody, proxy buffering); test harnesses sending incomplete fixtures.
Related errors
- Unexpected EOF while reading stream.
- An error occurred while checking content
- Boundary not found where it should be.
- 400
- The Content-Length header must not be null.
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/61d9166ab031a27b.
Report an issue: GitHub.