nextcloud/server · error · BadRequest

vCard can not be empty

Error message

vCard can not be empty

What it means

getUID()'s empty branch throws BadRequest('vCard can not be empty') when $cardData is the empty string - a backstop guard (the comment notes it 'should already be handled') against card creation or update calls with an empty payload. It surfaces as HTTP 400 on the DAV request.

Source

Thrown at apps/dav/lib/CardDAV/CardDavBackend.php:1583

	/**
	 * Extract UID from vcard
	 *
	 * @param string $cardData the vcard raw data
	 * @return string the uid
	 * @throws BadRequest if no UID is available or vcard is empty
	 */
	private function getUID(string $cardData): string {
		if ($cardData !== '') {
			$vCard = Reader::read($cardData);
			if ($vCard->UID) {
				$uid = $vCard->UID->getValue();
				return $uid;
			}
			// should already be handled, but just in case
			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
		}
		// should already be handled, but just in case
		throw new BadRequest('vCard can not be empty');
	}

	/**
	 * Mark all cards in an address book as needing to be validated
	 *
	 * This is done by setting the modified date to `null`, once a sync runs
	 * the mtime will be set to a non-null value. Leaving all deleted items with
	 * a null modified date.
	 */
	public function markCardsAsPending(int $addressBookId): void {
		$query = $this->db->getTypedQueryBuilder();
		$query->update($this->dbCardsTable)
			->set('lastmodified', $query->createNamedParameter(null))
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
			->executeStatement();
	}

	/**

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Validate that carddata is non-empty (after trim) before calling the backend or issuing PUT
  2. Use DELETE to remove a card, never an empty PUT
  3. Log the body length at the client to find where truncation happens

Example fix

// before
$backend->createCard($bookId, $uri, ''); // 400 vCard can not be empty

// after
if (trim((string) $cardData) === '') {
    throw new InvalidArgumentException('refusing to store an empty vCard');
}
$backend->createCard($bookId, $uri, $cardData);
Defensive patterns

Strategy: validation

Validate before calling

if (trim((string) $cardData) === '') {
    throw new InvalidArgumentException('refusing to PUT an empty vCard');
}

Type guard

function isNonEmptyCardData(?string $cardData): bool
{
    return $cardData !== null && trim($cardData) !== '';
}

Try / catch

try {
    $backend->createCard($bookId, $uri, $cardData);
} catch (\Sabre\DAV\Exception\BadRequest $e) {
    if (str_contains($e->getMessage(), 'can not be empty')) {
        // fix the upstream truncation instead of retrying with ''
    }
}

Prevention

When it happens

Trigger: PUT to a card URL with an empty request body; upstream code truncating carddata to '' before createCard()/updateCard(); server-side callers passing an uninitialized string.

Common situations: Client bugs uploading a zero-byte file; import pipelines writing the card before reading it; a 'clear card' feature implemented as PUT '' instead of DELETE.

Related errors


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