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

{http://owncloud.org/ns}share needs at least one set or remo

Error message

{http://owncloud.org/ns}share needs at least one set or remove element

What it means

HTTP 400 BadRequest from validateShareRequest() when the deserialized ShareRequest contains zero set elements and zero remove elements (count($shareRequest->set) + count($shareRequest->remove) === 0). A share POST that changes nothing is rejected as invalid before updateShares() runs.

Source

Thrown at apps/dav/lib/DAV/Sharing/Plugin.php:184

				// Adding this because sending a response body may cause issues,
				// and I wanted some type of indicator the response was handled.
				$response->setHeader('X-Sabre-Status', 'everything-went-well');

				// Breaking the event chain
				return false;
		}
	}

	private function validateShareRequest($shareRequest): void {
		if (!$shareRequest instanceof ShareRequest) {
			// @FIXME: Replace switch-case in httpPost with instanceof ShareRequest
			throw new BadRequest('The given request is not valid');
		}

		$elements = (count($shareRequest->set) + count($shareRequest->remove));

		if ($elements === 0) {
			throw new BadRequest(ShareRequest::ELEMENT_SHARE . ' needs at least one set or remove element');
		}

		if ($elements > 10) {
			throw new BadRequest(ShareRequest::ELEMENT_SHARE . ' is limited to 10 set or remove elements');
		}
	}

	private function preloadCollection(PropFind $propFind, ICollection $collection): void {
		if (!$collection instanceof CalendarHome || $propFind->getDepth() !== 1) {
			return;
		}

		$backend = $collection->getCalDAVBackend();
		if (!$backend instanceof CalDavBackend) {
			return;
		}

		$calendars = $collection->getChildren();

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Skip the POST entirely when the computed set/remove diff is empty
  2. Include at least one set or remove element (each with a DAV: href) in the body
  3. When unsharing all sharees, enumerate current invitees and emit one remove element per sharee

Example fix

// before: empty operation list -> 400
<x1:share xmlns:x1='http://owncloud.org/ns'></x1:share>
// after: at least one set or remove element
<x1:share xmlns:x1='http://owncloud.org/ns' xmlns:d='DAV:'>
  <x1:remove><d:href>principal:principals/users/bob</d:href></x1:remove>
</x1:share>
Defensive patterns

Strategy: validation

Validate before calling

// never send an empty operation list
function buildShareBody(setList, removeList) {
  if (setList.length + removeList.length === 0) return null; // skip the POST
  return shareXml({ set: setList, remove: removeList });
}

Prevention

When it happens

Trigger: POSTing an <oc:share> body with no child elements, or whose children deserialize into empty set/remove arrays — typical when 'unshare everyone' code computes the remove list dynamically and it ends up empty but still sends the POST.

Common situations: Diff-based sync clients that POST the result of an empty diff; UI code that always fires the share request even when the user confirmed no changes; XML with children in an unrecognized namespace so sabre maps none of them into set/remove.

Related errors


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