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.
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.
- 400
- An error occurred while checking content
- An error occurred while reading headers of a part
- Boundary not found where it should be.
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/dc551d6eee0c676c.
Report an issue: GitHub.
Appendix: 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)