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

  1. Add 'Content-Length: <exact byte size of the part body>' to every part's header block
  2. Compare your payload against the fixtures in apps/dav/tests/unit/Files/MultipartRequestParserTest.php or a desktop client capture
  3. 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

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


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/dc551d6eee0c676c. Report an issue: GitHub.