nextcloud/server · error · Sabre\DAV\Exception\LengthRequired
The Content-Length header must not be null.
Error message
The Content-Length header must not be null.
What it means
After parsing a part's headers, a missing 'Content-Length' header throws Sabre\DAV\Exception\LengthRequired, i.e. HTTP 411. The bulk-upload protocol requires each multipart part to declare its own byte length because the server reads exactly that many bytes and validates the integrity hash over exactly that range.
Source
Thrown at apps/dav/lib/BulkUpload/MultipartRequestParser.php:193
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.');
}
// TODO: Drop $md5 condition when the latest desktop client that uses it is no longer supported.
if (!isset($headers['x-file-md5']) && !isset($headers['oc-checksum'])) {
throw new BadRequest('The hash headers must not be null.');
}
return $headers;
}
/**
* Return the content of a part of the multipart body.
*
* @throws Exception
* @throws BadRequest
*/
private function readPartContent(int $length): string {
if ($length === 0) {View on GitHub (pinned to ecdeb153ff)
Solutions
- Add 'Content-Length: <exact byte size of the part body>' to every part's header block
- Compare your payload against the fixtures in apps/dav/tests/unit/Files/MultipartRequestParserTest.php or a desktop client capture
- Verify no HTTP layer (proxy, framework) strips per-part headers from the multipart body
Example fix
// before $part = "--$b\r\nX-File-Path: /a.txt\r\nX-File-MD5: $md5\r\n\r\n$content\r\n"; // after $part = "--$b\r\nX-File-Path: /a.txt\r\nContent-Length: " . strlen($content) . "\r\nX-File-MD5: $md5\r\n\r\n$content\r\n";
Defensive patterns
Strategy: validation
Validate before calling
// Assert every part is complete before uploading
foreach ($parts as $i => $p) {
foreach (['x-file-path', 'content-length', 'x-file-md5'] as $required) {
if (empty($p['headers'][$required])) {
throw new \InvalidArgumentException("Part {$i} missing {$required}");
}
}
if ((int)$p['headers']['content-length'] !== strlen($p['content'])) {
throw new \InvalidArgumentException("Part {$i} Content-Length != byte size");
}
} Prevention
- Always set per-part Content-Length to strlen($content), not mb_strlen
- Validate the part header block against a fixture before sending custom clients to /dav/bulk
- HTTP 411 from this endpoint means the per-part header, not the request header, is missing
When it happens
Trigger: POST to /dav/bulk with a part whose header block lacks 'Content-Length' (the per-part header, not the HTTP request header). Keys are lowercased by the parser, so casing is not the issue - the header must simply be present with the exact byte size of the part body.
Common situations: Custom clients copying a browser FormData upload instead of the bulk multipart format; older client versions from before the header was mandatory; payload builders dropping headers on empty files.
Related errors
- Unexpected EOF while reading stream.
- An error occurred while checking content
- Boundary not found where it should be.
- An error occurred while reading headers of a part
- 400
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/dc551d6eee0c676c.
Report an issue: GitHub.