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
- Validate that carddata is non-empty (after trim) before calling the backend or issuing PUT
- Use DELETE to remove a card, never an empty PUT
- 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
- Use DELETE to remove cards, never an empty PUT
- Assert a non-empty payload after every transport or transformation step
- Log request body lengths client-side when debugging truncated uploads
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
- vCards on CardDAV servers MUST have a UID property
- URI too long. Address book not created
- Unknown property: {property}
- VCard object exceeds $cardSizeLimit bytes
- Message exceeds allowed character limit of 1000
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/14a55f25b5aabe71.
Report an issue: GitHub.