nextcloud/server · error · Sabre\DAV\Exception

500

500

Error message

Unknown error while seeking content

What it means

isAt() peeks at the stream with fread() and then rewinds with fseek($stream, -$length, SEEK_CUR); a -1 return means the body stream is not seekable and parsing cannot continue. The parser fundamentally requires a seekable body because it re-reads bytes for hash validation, so this surfaces as HTTP 500 (it escapes the plugin's try/catch via isAtLastBoundary()).

Source

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

		return $boundaryValue;
	}

	/**
	 * Check whether the stream's cursor is sitting right before the provided string.
	 *
	 * @throws Exception
	 */
	private function isAt(string $expectedContent): bool {
		$expectedContentLength = strlen($expectedContent);

		$content = fread($this->stream, $expectedContentLength);
		if ($content === false) {
			throw new Exception('An error occurred while checking content');
		}

		$seekBackResult = fseek($this->stream, -$expectedContentLength, SEEK_CUR);
		if ($seekBackResult === -1) {
			throw new Exception('Unknown error while seeking content', Http::STATUS_INTERNAL_SERVER_ERROR);
		}

		return $expectedContent === $content;
	}

	/**
	 * Check whether the stream's cursor is sitting right before the boundary.
	 */
	private function isAtBoundary(): bool {
		return $this->isAt($this->boundary);
	}

	/**
	 * Check whether the stream's cursor is sitting right before the last boundary.
	 */
	public function isAtLastBoundary(): bool {
		return $this->isAt($this->lastBoundary);
	}

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Buffer the incoming body into a seekable stream (php://temp) before parsing and set it as the request body
  2. Check which resource the webserver/SAPI hands to PHP for php://input in your setup; standard FPM/Apache setups are seekable, custom servers often are not
  3. Inspect the stack trace to find which component supplied the non-seekable body and fix or wrap it

Example fix

// before
$parser = new MultipartRequestParser($request, $logger); // body may be non-seekable -> fseek returns -1

// after
$src = $request->getBody();
if (!stream_get_meta_data($src)['seekable']) {
    $buf = fopen('php://temp', 'r+b');
    stream_copy_to_stream($src, $buf);
    rewind($buf);
    $request->setBody($buf);
}
$parser = new MultipartRequestParser($request, $logger);
Defensive patterns

Strategy: validation

Validate before calling

$body = $request->getBody();
if (!stream_get_meta_data($body)['seekable']) {
    $buf = fopen('php://temp', 'r+b');
    stream_copy_to_stream($body, $buf);
    rewind($buf);
    $request->setBody($buf);
}

Type guard

function isSeekableStream($stream): bool {
    return is_resource($stream) && (stream_get_meta_data($stream)['seekable'] ?? false);
}

Prevention

When it happens

Trigger: POST to /dav/bulk where the Sabre\HTTP request body is a non-seekable stream: php://input under SAPI configurations where it is not seekable, a socket/pipe, or a custom stream wrapper without seek support. Also hit when an earlier handler replaces the body with a pipe-like resource.

Common situations: Custom SAPI or CLI servers fronting DAV; stream wrappers that only support sequential access; exotic proxy setups that hand PHP a non-seekable body.

Related errors


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