nextcloud/server · error · Sabre\DAV\Exception\BadRequest
The hash headers must not be null.
Error message
The hash headers must not be null.
What it means
Each bulk-upload part must carry an integrity hash: either 'X-File-MD5' (legacy desktop client) or 'OC-Checksum' in 'algorithm:value' form. If neither header is present in the part, readPartHeaders() rejects it with HTTP 400 'The hash headers must not be null.' - the endpoint never accepts unverified part content.
Source
Thrown at apps/dav/lib/BulkUpload/MultipartRequestParser.php:198
$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) {
$content = '';
} else {
$content = stream_get_line($this->stream, $length);
}
View on GitHub (pinned to ecdeb153ff)
Solutions
- Send 'X-File-MD5: <lowercase hex md5>' per part, or preferably 'OC-Checksum: SHA1:<hex>' (any hash_init() algorithm works)
- Compute the digest over exactly the bytes that form the part body, before boundary and CRLF
- Cross-check your part layout with the parser's unit tests or a desktop client capture
Example fix
// before
$headers = "X-File-Path: /a.txt\r\nContent-Length: 3\r\n\r\n";
// after
$headers = "X-File-Path: /a.txt\r\nContent-Length: 3\r\n"
. "X-File-MD5: " . md5($content) . "\r\n\r\n"; Defensive patterns
Strategy: validation
Validate before calling
// Build the hash header for every part, computed from the exact bytes
$part['headers']['x-file-md5'] = hash('md5', $part['content']);
// or: $part['headers']['oc-checksum'] = 'SHA1:' . hash('sha1', $part['content']); Prevention
- Every part needs X-File-MD5 or OC-Checksum - the endpoint has no unchecked mode
- Compute the digest over the raw bytes that form the part body, after all encoding is final
- Watch for header names mangled by templating (wrong case is fine, typos are not)
When it happens
Trigger: POST to /dav/bulk with a part whose headers contain neither x-file-md5 nor oc-checksum; e.g. a third-party client implementing the endpoint from an outdated spec, or a payload assembler that drops these headers.
Common situations: Custom sync scripts and integrations hitting POST /dav/bulk for the first time; clients that assumed checksums were optional; header typos like 'X-File-MD5 ' or 'OC-Checksums'.
Related errors
- Computed $algorithm hash is incorrect ($computedHash).
- No hash provided.
- An error occurred while checking content
- Boundary not found where it should be.
- An error occurred while reading headers of a part
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/c13741806e88a8aa.
Report an issue: GitHub.