aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php')
-rw-r--r--vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php526
1 files changed, 526 insertions, 0 deletions
diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php
new file mode 100644
index 000000000..c37571706
--- /dev/null
+++ b/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php
@@ -0,0 +1,526 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAV;
+
+/**
+ * This plugin implements support for caldav sharing.
+ *
+ * This spec is defined at:
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
+ *
+ * See:
+ * Sabre\CalDAV\Backend\SharingSupport for all the documentation.
+ *
+ * Note: This feature is experimental, and may change in between different
+ * SabreDAV versions.
+ *
+ * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class SharingPlugin extends DAV\ServerPlugin {
+
+ /**
+ * These are the various status constants used by sharing-messages.
+ */
+ const STATUS_ACCEPTED = 1;
+ const STATUS_DECLINED = 2;
+ const STATUS_DELETED = 3;
+ const STATUS_NORESPONSE = 4;
+ const STATUS_INVALID = 5;
+
+ /**
+ * Reference to SabreDAV server object.
+ *
+ * @var Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * This method should return a list of server-features.
+ *
+ * This is for example 'versioning' and is added to the DAV: header
+ * in an OPTIONS response.
+ *
+ * @return array
+ */
+ public function getFeatures() {
+
+ return array('calendarserver-sharing');
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ public function getPluginName() {
+
+ return 'caldav-sharing';
+
+ }
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ public function initialize(DAV\Server $server) {
+
+ $this->server = $server;
+ $server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared';
+
+ array_push(
+ $this->server->protectedProperties,
+ '{' . Plugin::NS_CALENDARSERVER . '}invite',
+ '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
+ '{' . Plugin::NS_CALENDARSERVER . '}shared-url'
+ );
+
+ $this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
+ $this->server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties'));
+ $this->server->subscribeEvent('updateProperties', array($this, 'updateProperties'));
+ $this->server->subscribeEvent('unknownMethod', array($this,'unknownMethod'));
+
+ }
+
+ /**
+ * This event is triggered when properties are requested for a certain
+ * node.
+ *
+ * This allows us to inject any properties early.
+ *
+ * @param string $path
+ * @param DAV\INode $node
+ * @param array $requestedProperties
+ * @param array $returnedProperties
+ * @return void
+ */
+ public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) {
+
+ if ($node instanceof IShareableCalendar) {
+ if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) {
+
+ unset($requestedProperties[$index]);
+ $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] =
+ new Property\Invite(
+ $node->getShares()
+ );
+
+ }
+
+ }
+
+ if ($node instanceof ISharedCalendar) {
+
+ if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}shared-url', $requestedProperties))!==false) {
+
+ unset($requestedProperties[$index]);
+ $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}shared-url'] =
+ new DAV\Property\Href(
+ $node->getSharedUrl()
+ );
+
+ }
+ // The 'invite' property is slightly different for the 'shared'
+ // instance of the calendar, as it also contains the owner
+ // information.
+ if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) {
+
+ unset($requestedProperties[$index]);
+
+ // Fetching owner information
+ $props = $this->server->getPropertiesForPath($node->getOwner(), array(
+ '{http://sabredav.org/ns}email-address',
+ '{DAV:}displayname',
+ ), 1);
+
+ $ownerInfo = array(
+ 'href' => $node->getOwner(),
+ );
+
+ if (isset($props[0][200])) {
+
+ // We're mapping the internal webdav properties to the
+ // elements caldav-sharing expects.
+ if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) {
+ $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address'];
+ }
+ if (isset($props[0][200]['{DAV:}displayname'])) {
+ $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname'];
+ }
+
+ }
+
+ $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] =
+ new Property\Invite(
+ $node->getShares(),
+ $ownerInfo
+ );
+
+ }
+
+
+ }
+
+ }
+
+ /**
+ * This method is triggered *after* all properties have been retrieved.
+ * This allows us to inject the correct resourcetype for calendars that
+ * have been shared.
+ *
+ * @param string $path
+ * @param array $properties
+ * @param DAV\INode $node
+ * @return void
+ */
+ public function afterGetProperties($path, &$properties, DAV\INode $node) {
+
+ if ($node instanceof IShareableCalendar) {
+ if (isset($properties[200]['{DAV:}resourcetype'])) {
+ if (count($node->getShares())>0) {
+ $properties[200]['{DAV:}resourcetype']->add(
+ '{' . Plugin::NS_CALENDARSERVER . '}shared-owner'
+ );
+ }
+ }
+ $propName = '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes';
+ if (array_key_exists($propName, $properties[404])) {
+ unset($properties[404][$propName]);
+ $properties[200][$propName] = new Property\AllowedSharingModes(true,false);
+ }
+
+ }
+
+ }
+
+ /**
+ * This method is trigged when a user attempts to update a node's
+ * properties.
+ *
+ * A previous draft of the sharing spec stated that it was possible to use
+ * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
+ * the calendar.
+ *
+ * Even though this is no longer in the current spec, we keep this around
+ * because OS X 10.7 may still make use of this feature.
+ *
+ * @param array $mutations
+ * @param array $result
+ * @param DAV\INode $node
+ * @return void
+ */
+ public function updateProperties(array &$mutations, array &$result, DAV\INode $node) {
+
+ if (!$node instanceof IShareableCalendar)
+ return;
+
+ if (!isset($mutations['{DAV:}resourcetype'])) {
+ return;
+ }
+
+ // Only doing something if shared-owner is indeed not in the list.
+ if($mutations['{DAV:}resourcetype']->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return;
+
+ $shares = $node->getShares();
+ $remove = array();
+ foreach($shares as $share) {
+ $remove[] = $share['href'];
+ }
+ $node->updateShares(array(), $remove);
+
+ // We're marking this update as 200 OK
+ $result[200]['{DAV:}resourcetype'] = null;
+
+ // Removing it from the mutations list
+ unset($mutations['{DAV:}resourcetype']);
+
+ }
+
+ /**
+ * This event is triggered when the server didn't know how to handle a
+ * certain request.
+ *
+ * We intercept this to handle POST requests on calendars.
+ *
+ * @param string $method
+ * @param string $uri
+ * @return null|bool
+ */
+ public function unknownMethod($method, $uri) {
+
+ if ($method!=='POST') {
+ return;
+ }
+
+ // Only handling xml
+ $contentType = $this->server->httpRequest->getHeader('Content-Type');
+ if (strpos($contentType,'application/xml')===false && strpos($contentType,'text/xml')===false)
+ return;
+
+ // Making sure the node exists
+ try {
+ $node = $this->server->tree->getNodeForPath($uri);
+ } catch (DAV\Exception\NotFound $e) {
+ return;
+ }
+
+ $requestBody = $this->server->httpRequest->getBody(true);
+
+ // If this request handler could not deal with this POST request, it
+ // will return 'null' and other plugins get a chance to handle the
+ // request.
+ //
+ // However, we already requested the full body. This is a problem,
+ // because a body can only be read once. This is why we preemptively
+ // re-populated the request body with the existing data.
+ $this->server->httpRequest->setBody($requestBody);
+
+ $dom = DAV\XMLUtil::loadDOMDocument($requestBody);
+
+ $documentType = DAV\XMLUtil::toClarkNotation($dom->firstChild);
+
+ switch($documentType) {
+
+ // Dealing with the 'share' document, which modified invitees on a
+ // calendar.
+ case '{' . Plugin::NS_CALENDARSERVER . '}share' :
+
+ // We can only deal with IShareableCalendar objects
+ if (!$node instanceof IShareableCalendar) {
+ return;
+ }
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($uri, '{DAV:}write');
+ }
+
+ $mutations = $this->parseShareRequest($dom);
+
+ $node->updateShares($mutations[0], $mutations[1]);
+
+ $this->server->httpResponse->sendStatus(200);
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ // The invite-reply document is sent when the user replies to an
+ // invitation of a calendar share.
+ case '{'. Plugin::NS_CALENDARSERVER.'}invite-reply' :
+
+ // This only works on the calendar-home-root node.
+ if (!$node instanceof UserCalendars) {
+ return;
+ }
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($uri, '{DAV:}write');
+ }
+
+ $message = $this->parseInviteReplyRequest($dom);
+
+ $url = $node->shareReply(
+ $message['href'],
+ $message['status'],
+ $message['calendarUri'],
+ $message['inReplyTo'],
+ $message['summary']
+ );
+
+ $this->server->httpResponse->sendStatus(200);
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ if ($url) {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+
+ $root = $dom->createElement('cs:shared-as');
+ foreach($this->server->xmlNamespaces as $namespace => $prefix) {
+ $root->setAttribute('xmlns:' . $prefix, $namespace);
+ }
+
+ $dom->appendChild($root);
+ $href = new DAV\Property\Href($url);
+
+ $href->serialize($this->server, $root);
+ $this->server->httpResponse->setHeader('Content-Type','application/xml');
+ $this->server->httpResponse->sendBody($dom->saveXML());
+
+ }
+
+ // Breaking the event chain
+ return false;
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
+
+ // We can only deal with IShareableCalendar objects
+ if (!$node instanceof IShareableCalendar) {
+ return;
+ }
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($uri, '{DAV:}write');
+ }
+
+ $node->setPublishStatus(true);
+
+ // iCloud sends back the 202, so we will too.
+ $this->server->httpResponse->sendStatus(202);
+
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
+
+ // We can only deal with IShareableCalendar objects
+ if (!$node instanceof IShareableCalendar) {
+ return;
+ }
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($uri, '{DAV:}write');
+ }
+
+ $node->setPublishStatus(false);
+
+ $this->server->httpResponse->sendStatus(200);
+
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ }
+
+
+
+ }
+
+ /**
+ * Parses the 'share' POST request.
+ *
+ * This method returns an array, containing two arrays.
+ * The first array is a list of new sharees. Every element is a struct
+ * containing a:
+ * * href element. (usually a mailto: address)
+ * * commonName element (often a first and lastname, but can also be
+ * false)
+ * * readOnly (true or false)
+ * * summary (A description of the share, can also be false)
+ *
+ * The second array is a list of sharees that are to be removed. This is
+ * just a simple array with 'hrefs'.
+ *
+ * @param \DOMDocument $dom
+ * @return array
+ */
+ protected function parseShareRequest(\DOMDocument $dom) {
+
+ $xpath = new \DOMXPath($dom);
+ $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER);
+ $xpath->registerNamespace('d', 'urn:DAV');
+
+ $set = array();
+ $elems = $xpath->query('cs:set');
+
+ for($i=0; $i < $elems->length; $i++) {
+
+ $xset = $elems->item($i);
+ $set[] = array(
+ 'href' => $xpath->evaluate('string(d:href)', $xset),
+ 'commonName' => $xpath->evaluate('string(cs:common-name)', $xset),
+ 'summary' => $xpath->evaluate('string(cs:summary)', $xset),
+ 'readOnly' => $xpath->evaluate('boolean(cs:read)', $xset)!==false
+ );
+
+ }
+
+ $remove = array();
+ $elems = $xpath->query('cs:remove');
+
+ for($i=0; $i < $elems->length; $i++) {
+
+ $xremove = $elems->item($i);
+ $remove[] = $xpath->evaluate('string(d:href)', $xremove);
+
+ }
+
+ return array($set, $remove);
+
+ }
+
+ /**
+ * Parses the 'invite-reply' POST request.
+ *
+ * This method returns an array, containing the following properties:
+ * * href - The sharee who is replying
+ * * status - One of the self::STATUS_* constants
+ * * calendarUri - The url of the shared calendar
+ * * inReplyTo - The unique id of the share invitation.
+ * * summary - Optional description of the reply.
+ *
+ * @param \DOMDocument $dom
+ * @return array
+ */
+ protected function parseInviteReplyRequest(\DOMDocument $dom) {
+
+ $xpath = new \DOMXPath($dom);
+ $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER);
+ $xpath->registerNamespace('d', 'urn:DAV');
+
+ $hostHref = $xpath->evaluate('string(cs:hosturl/d:href)');
+ if (!$hostHref) {
+ throw new DAV\Exception\BadRequest('The {' . Plugin::NS_CALENDARSERVER . '}hosturl/{DAV:}href element is required');
+ }
+
+ return array(
+ 'href' => $xpath->evaluate('string(d:href)'),
+ 'calendarUri' => $this->server->calculateUri($hostHref),
+ 'inReplyTo' => $xpath->evaluate('string(cs:in-reply-to)'),
+ 'summary' => $xpath->evaluate('string(cs:summary)'),
+ 'status' => $xpath->evaluate('boolean(cs:invite-accepted)')?self::STATUS_ACCEPTED:self::STATUS_DECLINED
+ );
+
+ }
+
+}