{"record":{"id":"13bcc6e9835ffbbb","repo":"nextcloud/server","slug":"boundary-not-found-where-it-should-be","errorCode":null,"errorMessage":"Boundary not found where it should be.","messagePattern":"Boundary not found where it should be\\.","errorType":"exception","errorClass":"Sabre\\DAV\\Exception\\BadRequest","httpStatus":400,"severity":"error","filePath":"apps/dav/lib/BulkUpload/MultipartRequestParser.php","lineNumber":158,"sourceCode":"\n\t\t$headers = $this->readPartHeaders();\n\n\t\t$length = (int)$headers['content-length'];\n\n\t\t$this->validateHash($length, $headers['x-file-md5'] ?? '', $headers['oc-checksum'] ?? '');\n\t\t$content = $this->readPartContent($length);\n\n\t\treturn [$headers, $content];\n\t}\n\n\t/**\n\t * Read the boundary and check its content.\n\t *\n\t * @throws BadRequest\n\t */\n\tprivate function readBoundary(): string {\n\t\tif (!$this->isAtBoundary()) {\n\t\t\tthrow new BadRequest('Boundary not found where it should be.');\n\t\t}\n\n\t\treturn fread($this->stream, strlen($this->boundary));\n\t}\n\n\t/**\n\t * Return the headers of a part of the multipart body.\n\t *\n\t * @throws Exception\n\t * @throws BadRequest\n\t * @throws LengthRequired\n\t */\n\tprivate function readPartHeaders(): array {\n\t\t$headers = [];\n\n\t\twhile (($line = fgets($this->stream)) !== \"\\r\\n\") {\n\t\t\tif ($line === false) {\n\t\t\t\tthrow new Exception('An error occurred while reading headers of a part');","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/BulkUpload/MultipartRequestParser.php#L140-L176","documentation":"readBoundary() first verifies the stream cursor sits exactly before '--<boundary>\\r\\n' (via isAtBoundary()); a mismatch throws Sabre\\DAV\\Exception\\BadRequest (HTTP 400). It means the multipart payload structure desynchronized from the boundary declared in the Content-Type header.","triggerScenarios":"POST to /dav/bulk where a part is not immediately followed by the exact boundary string: boundary in 'Content-Type: multipart/related; boundary=X' differs from the '--X\\r\\n' lines actually in the body, missing or extra CRLF between part content and the next boundary, LF-only line endings, or stray preamble bytes.","commonSituations":"Hand-rolled multipart writers using \\n instead of \\r\\n; quoted or whitespace-padded boundary values handled differently by client and server; proxies rewriting line endings; copy-pasted example payloads with an edited boundary.","solutions":["Byte-inspect the exact request (curl --trace-ascii, tcpdump) and compare the boundary after 'boundary=' in Content-Type with the '--<boundary>\\r\\n' markers in the body","Ensure every part ends with \\r\\n and is followed by '--<boundary>\\r\\n', and the body ends with '--<boundary>--\\r\\n'","Stop building the body by string concatenation; use a multipart library or the desktop client's payload as reference (see tests/unit .../MultipartRequestParserTest.php fixtures)","Verify no intermediary proxy or input handler strips CR bytes"],"exampleFix":"// before (LF-only, wrong framing)\n$body = \"--$boundary\\nContent-Length: 3\\n\\nfoo\\n--$boundary--\\n\";\n\n// after (exact CRLF framing the parser expects)\n$body = \"--$boundary\\r\\nContent-Length: 3\\r\\nX-File-MD5: \" . md5('foo') . \"\\r\\n\\r\\nfoo\\r\\n--$boundary--\\r\\n\";","handlingStrategy":"validation","validationCode":"// Client-side: assert the exact framing before sending\n$marker = \"--{$boundary}\\r\\n\";\nforeach ($parts as $i => $p) {\n    $body = $p['raw'];\n    if (!str_ends_with($body, \"\\r\\n\")) {\n        throw new \\InvalidArgumentException(\"Part {$i} must end with CRLF before the next boundary\");\n    }\n}","typeGuard":null,"tryCatchPattern":"// Client side: inspect the 400 JSON body; parsing errors abort the whole upload\n// HTTP 400 + {\"<path>\": ...} from /dav/bulk means framing was rejected - fix payload, do not retry as-is","preventionTips":["Use CRLF everywhere in multipart bodies; never \\n","Reuse the exact boundary token from Content-Type in every '--<boundary>' marker, unquoted","Prefer a maintained multipart builder over string concatenation"],"tags":["php","webdav","nextcloud","bulk-upload","multipart","boundary"],"backgroundTag":"multipart-boundary-mismatch","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}