nextcloud/server · error · Sabre\DAV\Exception\NotFound

Invalid node

Error message

Invalid node

What it means

CalendarObject::getName() derives the object name from the VCalendar's base component (X-FILENAME if set, otherwise UID.ics). getBaseComponent() returning null means the VCalendar built from the app backend's search() results contains no VEVENT/VTODO/VJOURNAL - invalid calendar data from the provider, as the inline comment states ('should never happen except the app provides invalid calendars'). Thrown as NotFound (HTTP 404).

Solutions

  1. Fix the provider so search() returns valid iCalendar objects whose base components (VEVENT/VTODO/VJOURNAL) carry a UID
  2. Validate the provider's search() output by parsing it with Sabre\VObject\Reader in the app's tests before shipping
  3. End users: report the issue to the app that provides the calendar - the DAV layer is only the messenger

Example fix

// before: provider returns fragments without a base component
return ['UID' => 'abc', 'SUMMARY' => 'x'];

// after: include a real base component with UID
return ['UID' => 'abc', 'objects' => $veventWithData]; // ensures VCalendar has VEVENT base
Defensive patterns

Strategy: type-guard

Validate before calling

// Provider-side: validate search() output before exposing it
use Sabre\VObject\Reader;
foreach ($this->search('') as $index => $object) {
    $v = Reader::read($object['objects'] ?? '', Reader::OPTION_IGNORE_ERRORS);
    if ($v->getBaseComponent() === null) {
        $this->logger->warning('Skipping object without base component at index ' . $index);
        unset($objects[$index]);
    }
}

Type guard

function hasCalendarBaseComponent(Sabre\VObject\Component\VCalendar $v): bool {
    return $v->getBaseComponent() !== null; // VEVENT/VTODO/VJOURNAL with UID present
}

Try / catch

try {
    $name = $object->getName();
} catch (Sabre\DAV\Exception\NotFound $e) {
    // provider returned data without a base component; skip this object and report to the app
}

Prevention

When it happens

Trigger: An app calendar provider whose search() returns objects without a base component (e.g. only VTIMEZONE definitions or malformed fragments), so any CalDAV operation that resolves the node's name (getChild, PROPFIND on children, PUT handling) fails.

Common situations: Buggy ICalendar implementations returning non-compliant search results; providers exposing raw data arrays missing the component keys; calendar apps updated with changed search() semantics.

Related errors


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

Appendix: source

Thrown at apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php:125

				$components[$key]->STATUS = 'CANCELLED';
				$components[$key]->SEQUENCE = isset($component->SEQUENCE) ? ((int)$component->SEQUENCE->getValue()) + 1 : 1;
				if ($component->name === 'VEVENT') {
					$components[$key]->METHOD = 'CANCEL';
				}
			}
			$this->backend->createFromString($this->getName(), (new VCalendar($components))->serialize());
		} else {
			throw new Forbidden('This calendar-object is read-only');
		}
	}

	#[\Override]
	public function getName(): string {
		// Every object is required to have an UID
		$base = $this->vobject->getBaseComponent();
		// This should never happen except the app provides invalid calendars (VEvent, VTodo... all require UID to be present)
		if ($base === null) {
			throw new NotFound('Invalid node');
		}
		if (isset($base->{'X-FILENAME'})) {
			return (string)$base->{'X-FILENAME'};
		}
		return (string)$base->UID . '.ics';
	}

	#[\Override]
	public function setName($name): void {
		throw new Forbidden('This calendar-object is read-only');
	}

	#[\Override]
	public function getLastModified(): ?int {
		$base = $this->vobject->getBaseComponent();
		if ($base !== null && $this->vobject->getBaseComponent()->{'LAST-MODIFIED'}) {
			/** @var DateTime */
			$lastModified = $this->vobject->getBaseComponent()->{'LAST-MODIFIED'};

View on GitHub (pinned to ecdeb153ff)