{"record":{"id":"dc551d6eee0c676c","repo":"nextcloud/server","slug":"the-content-length-header-must-not-be-null","errorCode":null,"errorMessage":"The Content-Length header must not be null.","messagePattern":"The Content-Length header must not be null\\.","errorType":"exception","errorClass":"Sabre\\DAV\\Exception\\LengthRequired","httpStatus":411,"severity":"error","filePath":"apps/dav/lib/BulkUpload/MultipartRequestParser.php","lineNumber":193,"sourceCode":"\t\t\tif ($line === false) {\n\t\t\t\tthrow new Exception('An error occurred while reading headers of a part');\n\t\t\t}\n\n\t\t\tif (!str_contains($line, ':')) {\n\t\t\t\t$this->logger->error('Header missing \":\" on bulk request: ' . json_encode($line));\n\t\t\t\tthrow new Exception('An error occurred while reading headers of a part', Http::STATUS_BAD_REQUEST);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t[$key, $value] = explode(':', $line, 2);\n\t\t\t\t$headers[strtolower(trim($key))] = trim($value);\n\t\t\t} catch (\\Exception $e) {\n\t\t\t\tthrow new BadRequest('An error occurred while parsing headers of a part', Http::STATUS_BAD_REQUEST, $e);\n\t\t\t}\n\t\t}\n\n\t\tif (!isset($headers['content-length'])) {\n\t\t\tthrow new LengthRequired('The Content-Length header must not be null.');\n\t\t}\n\n\t\t// TODO: Drop $md5 condition when the latest desktop client that uses it is no longer supported.\n\t\tif (!isset($headers['x-file-md5']) && !isset($headers['oc-checksum'])) {\n\t\t\tthrow new BadRequest('The hash headers must not be null.');\n\t\t}\n\n\t\treturn $headers;\n\t}\n\n\t/**\n\t * Return the content of a part of the multipart body.\n\t *\n\t * @throws Exception\n\t * @throws BadRequest\n\t */\n\tprivate function readPartContent(int $length): string {\n\t\tif ($length === 0) {","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/BulkUpload/MultipartRequestParser.php#L175-L211","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n$part = \"--$b\\r\\nX-File-Path: /a.txt\\r\\nX-File-MD5: $md5\\r\\n\\r\\n$content\\r\\n\";\n\n// after\n$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\";","handlingStrategy":"validation","validationCode":"// Assert every part is complete before uploading\nforeach ($parts as $i => $p) {\n    foreach (['x-file-path', 'content-length', 'x-file-md5'] as $required) {\n        if (empty($p['headers'][$required])) {\n            throw new \\InvalidArgumentException(\"Part {$i} missing {$required}\");\n        }\n    }\n    if ((int)$p['headers']['content-length'] !== strlen($p['content'])) {\n        throw new \\InvalidArgumentException(\"Part {$i} Content-Length != byte size\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["php","webdav","nextcloud","bulk-upload","multipart","content-length","http-411"],"backgroundTag":"missing-content-length","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}