nextcloud/server · error · Sabre\DAV\Exception

An error occurred while reading headers of a part

Error message

An error occurred while reading headers of a part

What it means

readPartHeaders() loops fgets() until the blank line '\r\n' that terminates a part's header block; fgets() returning false means EOF was reached first. The body ended in the middle of a part's headers, and this generic Sabre\DAV\Exception (default code 500) is thrown; inside parseNextPart() it is caught by BulkUploadPlugin and returned to the client as HTTP 400.

Source

Thrown at apps/dav/lib/BulkUpload/MultipartRequestParser.php:176

			throw new BadRequest('Boundary not found where it should be.');
		}

		return fread($this->stream, strlen($this->boundary));
	}

	/**
	 * Return the headers of a part of the multipart body.
	 *
	 * @throws Exception
	 * @throws BadRequest
	 * @throws LengthRequired
	 */
	private function readPartHeaders(): array {
		$headers = [];

		while (($line = fgets($this->stream)) !== "\r\n") {
			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.');
		}

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Retry with a tiny single-file bulk upload; if that succeeds the problem is size-, timeout-, or proxy-related
  2. Raise/verify proxy limits and timeouts for the DAV endpoint (nginx client_max_body_size, proxy_read_timeout; Apache LimitRequestBody, TimeOut)
  3. Make the client send a correct request Content-Length and complete the transfer; check access logs for 499/408/reset evidence
  4. Capture the wire body server-side to confirm exactly where it stops mid-headers
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: send an explicit, correct Content-Length and finish the body
$payload = buildMultipartBody($parts, $boundary);
$ch = curl_init('https://host/remote.php/dav/bulk');
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); // sets Content-Length = strlen($payload)
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/related; boundary=' . $boundary]);

Prevention

When it happens

Trigger: POST to /dav/bulk whose body is truncated inside a part's header block: the request ended early (request Content-Length larger than bytes actually sent, chunked transfer aborted, connection dropped) so the terminating '\r\n' blank line never arrives.

Common situations: Client timeouts or connection resets during large multi-file uploads; reverse proxy limits truncating request bodies (nginx client_max_body_size, Apache LimitRequestBody, proxy buffering); test harnesses sending incomplete fixtures.

Related errors


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