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.
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
- Message exceeds allowed character limit of 1000
- Unknown property
- VCard object exceeds $cardSizeLimit bytes
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/14a55f25b5aabe71.
Report an issue: GitHub.
Appendix: 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)