/**
  * Intercepts GET requests on addressbook urls ending with ?export.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     $queryParams = $request->getQueryParameters();
     if (!array_key_exists('export', $queryParams)) {
         return;
     }
     $path = $request->getPath();
     $node = $this->server->tree->getNodeForPath($path);
     if (!$node instanceof IAddressBook) {
         return;
     }
     $this->server->transactionType = 'get-addressbook-export';
     // Checking ACL, if available.
     if ($aclPlugin = $this->server->getPlugin('acl')) {
         $aclPlugin->checkPrivileges($path, '{DAV:}read');
     }
     $nodes = $this->server->getPropertiesForPath($path, ['{' . Plugin::NS_CARDDAV . '}address-data'], 1);
     $format = 'text/directory';
     $output = null;
     $filenameExtension = null;
     switch ($format) {
         case 'text/directory':
             $output = $this->generateVCF($nodes);
             $filenameExtension = '.vcf';
             break;
     }
     $filename = preg_replace('/[^a-zA-Z0-9-_ ]/um', '', $node->getName());
     $filename .= '-' . date('Y-m-d') . $filenameExtension;
     $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
     $response->setHeader('Content-Type', $format);
     $response->setStatus(200);
     $response->setBody($output);
     // Returning false to break the event chain
     return false;
 }
 /**
  * Intercepts GET requests on addressbook urls ending with ?photo.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool|void
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     $queryParams = $request->getQueryParameters();
     // TODO: in addition to photo we should also add logo some point in time
     if (!array_key_exists('photo', $queryParams)) {
         return true;
     }
     $path = $request->getPath();
     $node = $this->server->tree->getNodeForPath($path);
     if (!$node instanceof Card) {
         return true;
     }
     $this->server->transactionType = 'carddav-image-export';
     // Checking ACL, if available.
     if ($aclPlugin = $this->server->getPlugin('acl')) {
         /** @var \Sabre\DAVACL\Plugin $aclPlugin */
         $aclPlugin->checkPrivileges($path, '{DAV:}read');
     }
     if ($result = $this->getPhoto($node)) {
         $response->setHeader('Content-Type', $result['Content-Type']);
         $response->setStatus(200);
         $response->setBody($result['body']);
         // Returning false to break the event chain
         return false;
     }
     return true;
 }
Exemple #3
0
 /**
  * Handles POST requests for tree operations not handled in the SabreDAV parent clas
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 public function httpPOSTExtra(RequestInterface $request, ResponseInterface $response)
 {
     $contentType = $request->getHeader('Content-Type');
     list($contentType) = explode(';', $contentType);
     if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') {
         return;
     }
     $postVars = $request->getPostData();
     if (!isset($postVars['sabreActionExtra'])) {
         return;
     }
     $uri = $request->getPath();
     switch ($postVars['sabreActionExtra']) {
         case 'del':
             if (isset($postVars['path'])) {
                 // Using basename() because we won't allow slashes
                 list(, $Name) = \Sabre\HTTP\URLUtil::splitPath(trim($postVars['path']));
                 if (!empty($Name) && $this->config->browserplugin_enable_delete === true) {
                     $this->server->tree->delete($uri . '/' . $Name);
                 }
             }
             break;
     }
     $response->setHeader('Location', $request->getUrl());
     $response->setStatus(302);
     return false;
 }
Exemple #4
0
 /**
  * Plugin that adds a 'Content-Disposition: attachment' header to all files
  * delivered by SabreDAV.
  * @param RequestInterface $request
  * @param ResponseInterface $response
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     // Only handle valid files
     $node = $this->tree->getNodeForPath($request->getPath());
     if (!$node instanceof IFile) {
         return;
     }
     $response->addHeader('Content-Disposition', 'attachment');
 }
Exemple #5
0
 /**
  * Intercepts GET requests on calendar urls ending with ?export.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     $queryParams = $request->getQueryParameters();
     if (!array_key_exists('export', $queryParams)) {
         return;
     }
     $path = $request->getPath();
     $node = $this->server->getProperties($path, ['{DAV:}resourcetype', '{DAV:}displayname', '{http://sabredav.org/ns}sync-token', '{DAV:}sync-token', '{http://apple.com/ns/ical/}calendar-color']);
     if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
         return;
     }
     // Marking the transactionType, for logging purposes.
     $this->server->transactionType = 'get-calendar-export';
     $properties = $node;
     $start = null;
     $end = null;
     $expand = false;
     $componentType = false;
     if (isset($queryParams['start'])) {
         if (!ctype_digit($queryParams['start'])) {
             throw new BadRequest('The start= parameter must contain a unix timestamp');
         }
         $start = DateTime::createFromFormat('U', $queryParams['start']);
     }
     if (isset($queryParams['end'])) {
         if (!ctype_digit($queryParams['end'])) {
             throw new BadRequest('The end= parameter must contain a unix timestamp');
         }
         $end = DateTime::createFromFormat('U', $queryParams['end']);
     }
     if (isset($queryParams['expand']) && !!$queryParams['expand']) {
         if (!$start || !$end) {
             throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
         }
         $expand = true;
         $componentType = 'VEVENT';
     }
     if (isset($queryParams['componentType'])) {
         if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
             throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
         }
         $componentType = $queryParams['componentType'];
     }
     $format = \Sabre\HTTP\Util::Negotiate($request->getHeader('Accept'), ['text/calendar', 'application/calendar+json']);
     if (isset($queryParams['accept'])) {
         if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
             $format = 'application/calendar+json';
         }
     }
     if (!$format) {
         $format = 'text/calendar';
     }
     $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
     // Returning false to break the event chain
     return false;
 }
 /**
  * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     $node = $this->server->tree->getNodeForPath($request->getPath());
     if ($node instanceof DAV\IFile) {
         return;
     }
     $subRequest = clone $request;
     $subRequest->setMethod('PROPFIND');
     $this->server->invokeMethod($subRequest, $response);
     return false;
 }
Exemple #7
0
 /**
  * We intercept this to handle POST requests on calendars.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return null|bool
  */
 function httpPost(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     // Only handling xml
     $contentType = $request->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($path);
     } catch (NotFound $e) {
         return;
     }
     // CSRF protection
     $this->protectAgainstCSRF();
     $requestBody = $request->getBodyAsString();
     // 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.
     $request->setBody($requestBody);
     $dom = XMLUtil::loadDOMDocument($requestBody);
     $documentType = XMLUtil::toClarkNotation($dom->firstChild);
     switch ($documentType) {
         // Dealing with the 'share' document, which modified invitees on a
         // calendar.
         case '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}share':
             // We can only deal with IShareableCalendar objects
             if (!$node instanceof IShareableAddressBook) {
                 return;
             }
             $this->server->transactionType = 'post-calendar-share';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $mutations = $this->parseShareRequest($dom);
             $node->updateShares($mutations[0], $mutations[1]);
             $response->setStatus(200);
             // 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;
     }
 }
Exemple #8
0
 public function releaseLock(RequestInterface $request)
 {
     if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) {
         return;
     }
     try {
         $node = $this->server->tree->getNodeForPath($request->getPath());
     } catch (NotFound $e) {
         return;
     }
     if ($node instanceof Node) {
         $node->releaseLock(ILockingProvider::LOCK_SHARED);
     }
 }
Exemple #9
0
 public function releaseLock(RequestInterface $request)
 {
     if ($request->getMethod() !== 'PUT') {
         return;
     }
     try {
         $node = $this->tree->getNodeForPath($request->getPath());
     } catch (NotFound $e) {
         return;
     }
     if ($node instanceof Node) {
         $node->releaseLock(ILockingProvider::LOCK_SHARED);
     }
 }
 /**
  * Intercepts GET requests on addressbook urls ending with ?export.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     $queryParams = $request->getQueryParameters();
     if (!array_key_exists('export', $queryParams)) {
         return;
     }
     $path = $request->getPath();
     $node = $this->server->tree->getNodeForPath($path);
     if (!$node instanceof IAddressBook) {
         return;
     }
     $this->server->transactionType = 'get-addressbook-export';
     // Checking ACL, if available.
     if ($aclPlugin = $this->server->getPlugin('acl')) {
         $aclPlugin->checkPrivileges($path, '{DAV:}read');
     }
     $response->setHeader('Content-Type', 'text/directory');
     $response->setStatus(200);
     $nodes = $this->server->getPropertiesForPath($path, ['{' . Plugin::NS_CARDDAV . '}address-data'], 1);
     $response->setBody($this->generateVCF($nodes));
     // Returning false to break the event chain
     return false;
 }
 /**
  * Fakes a successful LOCK
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 public function fakeLockProvider(RequestInterface $request, ResponseInterface $response)
 {
     $lockInfo = new LockInfo();
     $lockInfo->token = md5($request->getPath());
     $lockInfo->uri = $request->getPath();
     $lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY;
     $lockInfo->timeout = 1800;
     $body = $this->server->xml->write('{DAV:}prop', ['{DAV:}lockdiscovery' => new LockDiscovery([$lockInfo])]);
     $response->setBody($body);
     return false;
 }
Exemple #12
0
 /**
  * This function handles the MKCALENDAR HTTP method, which creates
  * a new calendar.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpMkCalendar(RequestInterface $request, ResponseInterface $response)
 {
     $body = $request->getBodyAsString();
     $path = $request->getPath();
     $properties = [];
     if ($body) {
         try {
             $mkcalendar = $this->server->xml->expect('{urn:ietf:params:xml:ns:caldav}mkcalendar', $body);
         } catch (\Sabre\Xml\ParseException $e) {
             throw new BadRequest($e->getMessage(), null, $e);
         }
         $properties = $mkcalendar->getProperties();
     }
     // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
     // subscriptions. Before that it used MKCOL which was the correct way
     // to do this.
     //
     // If the body had a {DAV:}resourcetype, it means we stumbled upon this
     // request, and we simply use it instead of the pre-defined list.
     if (isset($properties['{DAV:}resourcetype'])) {
         $resourceType = $properties['{DAV:}resourcetype']->getValue();
     } else {
         $resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'];
     }
     $this->server->createCollection($path, new MkCol($resourceType, $properties));
     $this->server->httpResponse->setStatus(201);
     $this->server->httpResponse->setHeader('Content-Length', 0);
     // This breaks the method chain.
     return false;
 }
 /**
  * This method handles the PROPFIND method.
  *
  * It's a very lazy method, it won't bother checking the request body
  * for which properties were requested, and just sends back a default
  * set of properties.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $hR
  * @param string $tempLocation
  * @return bool
  */
 function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation)
 {
     if (!file_exists($tempLocation)) {
         return;
     }
     $hR->setHeader('X-Sabre-Temp', 'true');
     $hR->setStatus(207);
     $hR->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $properties = ['href' => $request->getPath(), 200 => ['{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), '{DAV:}getcontentlength' => filesize($tempLocation), '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), '{' . Server::NS_SABREDAV . '}tempFile' => true]];
     $data = $this->server->generateMultiStatus([$properties]);
     $hR->setBody($data);
     return false;
 }
Exemple #14
0
 /**
  * This function handles the MKCALENDAR HTTP method, which creates
  * a new calendar.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpMkCalendar(RequestInterface $request, ResponseInterface $response)
 {
     // Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
     // for clients matching iCal in the user agent
     //$ua = $this->server->httpRequest->getHeader('User-Agent');
     //if (strpos($ua,'iCal/')!==false) {
     //    throw new \Sabre\DAV\Exception\Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.');
     //}
     $body = $request->getBodyAsString();
     $path = $request->getPath();
     $properties = [];
     if ($body) {
         $dom = DAV\XMLUtil::loadDOMDocument($body);
         foreach ($dom->firstChild->childNodes as $child) {
             if (DAV\XMLUtil::toClarkNotation($child) !== '{DAV:}set') {
                 continue;
             }
             foreach (DAV\XMLUtil::parseProperties($child, $this->server->propertyMap) as $k => $prop) {
                 $properties[$k] = $prop;
             }
         }
     }
     // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
     // subscriptions. Before that it used MKCOL which was the correct way
     // to do this.
     //
     // If the body had a {DAV:}resourcetype, it means we stumbled upon this
     // request, and we simply use it instead of the pre-defined list.
     if (isset($properties['{DAV:}resourcetype'])) {
         $resourceType = $properties['{DAV:}resourcetype']->getValue();
     } else {
         $resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'];
     }
     $this->server->createCollection($path, $resourceType, $properties);
     $this->server->httpResponse->setStatus(201);
     $this->server->httpResponse->setHeader('Content-Length', 0);
     // This breaks the method chain.
     return false;
 }
Exemple #15
0
 /**
  * Handles POST requests for tree operations.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpPOST(RequestInterface $request, ResponseInterface $response)
 {
     $contentType = $request->getHeader('Content-Type');
     list($contentType) = explode(';', $contentType);
     if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') {
         return;
     }
     $postVars = $request->getPostData();
     if (!isset($postVars['sabreAction'])) {
         return;
     }
     $uri = $request->getPath();
     if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
         switch ($postVars['sabreAction']) {
             case 'mkcol':
                 if (isset($postVars['name']) && trim($postVars['name'])) {
                     // Using basename() because we won't allow slashes
                     list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));
                     if (isset($postVars['resourceType'])) {
                         $resourceType = explode(',', $postVars['resourceType']);
                     } else {
                         $resourceType = ['{DAV:}collection'];
                     }
                     $properties = [];
                     foreach ($postVars as $varName => $varValue) {
                         // Any _POST variable in clark notation is treated
                         // like a property.
                         if ($varName[0] === '{') {
                             // PHP will convert any dots to underscores.
                             // This leaves us with no way to differentiate
                             // the two.
                             // Therefore we replace the string *DOT* with a
                             // real dot. * is not allowed in uris so we
                             // should be good.
                             $varName = str_replace('*DOT*', '.', $varName);
                             $properties[$varName] = $varValue;
                         }
                     }
                     $mkCol = new MkCol($resourceType, $properties);
                     $this->server->createCollection($uri . '/' . $folderName, $mkCol);
                 }
                 break;
                 // @codeCoverageIgnoreStart
             // @codeCoverageIgnoreStart
             case 'put':
                 if ($_FILES) {
                     $file = current($_FILES);
                 } else {
                     break;
                 }
                 list(, $newName) = URLUtil::splitPath(trim($file['name']));
                 if (isset($postVars['name']) && trim($postVars['name'])) {
                     $newName = trim($postVars['name']);
                 }
                 // Making sure we only have a 'basename' component
                 list(, $newName) = URLUtil::splitPath($newName);
                 if (is_uploaded_file($file['tmp_name'])) {
                     $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
                 }
                 break;
                 // @codeCoverageIgnoreEnd
         }
     }
     $response->setHeader('Location', $request->getUrl());
     $response->setStatus(302);
     return false;
 }
Exemple #16
0
 /**
  * Patch an uri
  *
  * The WebDAV patch request can be used to modify only a part of an
  * existing resource. If the resource does not exist yet and the first
  * offset is not 0, the request fails
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return void
  */
 function httpPatch(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     // Get the node. Will throw a 404 if not found
     $node = $this->server->tree->getNodeForPath($path);
     if (!$node instanceof IFile && !$node instanceof IPatchSupport) {
         throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.');
     }
     $range = $this->getHTTPUpdateRange($request);
     if (!$range) {
         throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers');
     }
     $contentType = strtolower($request->getHeader('Content-Type'));
     if ($contentType != 'application/x-sabredav-partialupdate') {
         throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"');
     }
     $len = $this->server->httpRequest->getHeader('Content-Length');
     if (!$len) {
         throw new DAV\Exception\LengthRequired('A Content-Length header is required');
     }
     switch ($range[0]) {
         case self::RANGE_START:
             // Calculate the end-range if it doesn't exist.
             if (!$range[2]) {
                 $range[2] = $range[1] + $len - 1;
             } else {
                 if ($range[2] < $range[1]) {
                     throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')');
                 }
                 if ($range[2] - $range[1] + 1 != $len) {
                     throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets');
                 }
             }
             break;
     }
     if (!$this->server->emit('beforeWriteContent', [$path, $node, null])) {
         return;
     }
     $body = $this->server->httpRequest->getBody();
     if ($node instanceof IPatchSupport) {
         $etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null);
     } else {
         // The old interface
         switch ($range[0]) {
             case self::RANGE_APPEND:
                 throw new DAV\Exception\NotImplemented('This node does not support the append syntax. Please upgrade it to IPatchSupport');
             case self::RANGE_START:
                 $etag = $node->putRange($body, $range[1]);
                 break;
             case self::RANGE_END:
                 throw new DAV\Exception\NotImplemented('This node does not support the end-range syntax. Please upgrade it to IPatchSupport');
                 break;
         }
     }
     $this->server->emit('afterWriteContent', [$path, $node]);
     $response->setHeader('Content-Length', '0');
     if ($etag) {
         $response->setHeader('ETag', $etag);
     }
     $response->setStatus(204);
     // Breaks the event chain
     return false;
 }
Exemple #17
0
    /**
     * This method is created to extract information from the WebDAV HTTP 'If:' header
     *
     * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information
     * The function will return an array, containing structs with the following keys
     *
     *   * uri   - the uri the condition applies to.
     *   * tokens - The lock token. another 2 dimensional array containing 3 elements
     *
     * Example 1:
     *
     * If: (<opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)
     *
     * Would result in:
     *
     * [
     *    [
     *       'uri' => '/request/uri',
     *       'tokens' => [
     *          [
     *              [
     *                  'negate' => false,
     *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
     *                  'etag'   => ""
     *              ]
     *          ]
     *       ],
     *    ]
     * ]
     *
     * Example 2:
     *
     * If: </path/> (Not <opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> ["Im An ETag"]) (["Another ETag"]) </path2/> (Not ["Path2 ETag"])
     *
     * Would result in:
     *
     * [
     *    [
     *       'uri' => 'path',
     *       'tokens' => [
     *          [
     *              [
     *                  'negate' => true,
     *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
     *                  'etag'   => '"Im An ETag"'
     *              ],
     *              [
     *                  'negate' => false,
     *                  'token'  => '',
     *                  'etag'   => '"Another ETag"'
     *              ]
     *          ]
     *       ],
     *    ],
     *    [
     *       'uri' => 'path2',
     *       'tokens' => [
     *          [
     *              [
     *                  'negate' => true,
     *                  'token'  => '',
     *                  'etag'   => '"Path2 ETag"'
     *              ]
     *          ]
     *       ],
     *    ],
     * ]
     *
     * @return array
     */
    function getIfConditions(RequestInterface $request) {

        $header = $request->getHeader('If');
        if (!$header) return [];

        $matches = [];

        $regex = '/(?:\<(?P<uri>.*?)\>\s)?\((?P<not>Not\s)?(?:\<(?P<token>[^\>]*)\>)?(?:\s?)(?:\[(?P<etag>[^\]]*)\])?\)/im';
        preg_match_all($regex, $header, $matches, PREG_SET_ORDER);

        $conditions = [];

        foreach ($matches as $match) {

            // If there was no uri specified in this match, and there were
            // already conditions parsed, we add the condition to the list of
            // conditions for the previous uri.
            if (!$match['uri'] && count($conditions)) {
                $conditions[count($conditions) - 1]['tokens'][] = [
                    'negate' => $match['not'] ? true : false,
                    'token'  => $match['token'],
                    'etag'   => isset($match['etag']) ? $match['etag'] : ''
                ];
            } else {

                if (!$match['uri']) {
                    $realUri = $request->getPath();
                } else {
                    $realUri = $this->calculateUri($match['uri']);
                }

                $conditions[] = [
                    'uri'    => $realUri,
                    'tokens' => [
                        [
                            'negate' => $match['not'] ? true : false,
                            'token'  => $match['token'],
                            'etag'   => isset($match['etag']) ? $match['etag'] : ''
                        ]
                    ],

                ];
            }

        }

        return $conditions;

    }
Exemple #18
0
 /**
  * This method handles POST requests to the schedule-outbox.
  *
  * Currently, two types of requests are support:
  *   * FREEBUSY requests from RFC 6638
  *   * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
  *
  * The latter is from an expired early draft of the CalDAV scheduling
  * extensions, but iCal depends on a feature from that spec, so we
  * implement it.
  *
  * @param IOutbox $outboxNode
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return void
  */
 function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
 {
     $outboxPath = $request->getPath();
     // Parsing the request body
     try {
         $vObject = VObject\Reader::read($request->getBody());
     } catch (VObject\ParseException $e) {
         throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
     }
     // The incoming iCalendar object must have a METHOD property, and a
     // component. The combination of both determines what type of request
     // this is.
     $componentType = null;
     foreach ($vObject->getComponents() as $component) {
         if ($component->name !== 'VTIMEZONE') {
             $componentType = $component->name;
             break;
         }
     }
     if (is_null($componentType)) {
         throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
     }
     // Validating the METHOD
     $method = strtoupper((string) $vObject->METHOD);
     if (!$method) {
         throw new BadRequest('A METHOD property must be specified in iTIP messages');
     }
     // So we support one type of request:
     //
     // REQUEST with a VFREEBUSY component
     $acl = $this->server->getPlugin('acl');
     if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
         $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
         $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
     } else {
         throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
     }
 }
Exemple #19
0
 /**
  * Handles POST requests for tree operations.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpPOST(RequestInterface $request, ResponseInterface $response)
 {
     $contentType = $request->getHeader('Content-Type');
     list($contentType) = explode(';', $contentType);
     if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') {
         return;
     }
     $postVars = $request->getPostData();
     if (!isset($postVars['sabreAction'])) {
         return;
     }
     $uri = $request->getPath();
     if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
         switch ($postVars['sabreAction']) {
             case 'mkcol':
                 if (isset($postVars['name']) && trim($postVars['name'])) {
                     // Using basename() because we won't allow slashes
                     list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));
                     $this->server->createDirectory($uri . '/' . $folderName);
                 }
                 break;
                 // @codeCoverageIgnoreStart
             // @codeCoverageIgnoreStart
             case 'put':
                 if ($_FILES) {
                     $file = current($_FILES);
                 } else {
                     break;
                 }
                 list(, $newName) = URLUtil::splitPath(trim($file['name']));
                 if (isset($postVars['name']) && trim($postVars['name'])) {
                     $newName = trim($postVars['name']);
                 }
                 // Making sure we only have a 'basename' component
                 list(, $newName) = URLUtil::splitPath($newName);
                 if (is_uploaded_file($file['tmp_name'])) {
                     $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
                 }
                 break;
                 // @codeCoverageIgnoreEnd
         }
     }
     $response->setHeader('Location', $request->getUrl());
     $response->setStatus(302);
     return false;
 }
Exemple #20
0
 /**
  * This event is triggered before the usual GET request handler.
  *
  * We use this to intercept GET calls to notification nodes, and return the
  * proper response.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return void
  */
 function httpGet(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     try {
         $node = $this->server->tree->getNodeForPath($path);
     } catch (DAV\Exception\NotFound $e) {
         return;
     }
     if (!$node instanceof INode) {
         return;
     }
     $dom = new \DOMDocument('1.0', 'UTF-8');
     $dom->formatOutput = true;
     $root = $dom->createElement('cs:notification');
     foreach ($this->server->xmlNamespaces as $namespace => $prefix) {
         $root->setAttribute('xmlns:' . $prefix, $namespace);
     }
     $dom->appendChild($root);
     $node->getNotificationType()->serializeBody($this->server, $root);
     $response->setHeader('Content-Type', 'application/xml');
     $response->setHeader('ETag', $node->getETag());
     $response->setStatus(200);
     $response->setBody($dom->saveXML());
     // Return false to break the event chain.
     return false;
 }
Exemple #21
0
 /**
  * Fakes a successful LOCK
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 public function fakeLockProvider(RequestInterface $request, ResponseInterface $response)
 {
     $dom = new \DOMDocument('1.0', 'utf-8');
     $prop = $dom->createElementNS('DAV:', 'd:prop');
     $dom->appendChild($prop);
     $lockDiscovery = $dom->createElementNS('DAV:', 'd:lockdiscovery');
     $prop->appendChild($lockDiscovery);
     $lockInfo = new LockInfo();
     $lockInfo->token = md5($request->getPath());
     $lockInfo->uri = $request->getPath();
     $lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY;
     $lockInfo->timeout = 1800;
     $lockObj = new LockDiscovery([$lockInfo]);
     $lockObj->serialize($this->server, $lockDiscovery);
     $response->setBody($dom->saveXML());
     return false;
 }
Exemple #22
0
 /**
  * POST operation on system tag collections
  *
  * @param RequestInterface $request request object
  * @param ResponseInterface $response response object
  * @return null|false
  */
 public function httpPost(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     // Making sure the node exists
     try {
         $node = $this->server->tree->getNodeForPath($path);
     } catch (NotFound $e) {
         return null;
     }
     if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
         $data = $request->getBodyAsString();
         $tag = $this->createTag($data, $request->getHeader('Content-Type'));
         if ($node instanceof SystemTagsObjectMappingCollection) {
             // also add to collection
             $node->createFile($tag->getId());
             $url = $request->getBaseUrl() . 'systemtags/';
         } else {
             $url = $request->getUrl();
         }
         if ($url[strlen($url) - 1] !== '/') {
             $url .= '/';
         }
         $response->setHeader('Content-Location', $url . $tag->getId());
         // created
         $response->setStatus(201);
         return false;
     }
 }
Exemple #23
0
 /**
  * We intercept this to handle POST requests on calendars.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return null|bool
  */
 function httpPost(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     // Only handling xml
     $contentType = $request->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($path);
     } catch (DAV\Exception\NotFound $e) {
         return;
     }
     $requestBody = $request->getBodyAsString();
     // 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.
     $request->setBody($requestBody);
     $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
     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;
             }
             $this->server->transactionType = 'post-calendar-share';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $node->updateShares($message->set, $message->remove);
             $response->setStatus(200);
             // 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;
             // The invite-reply document is sent when the user replies to an
             // invitation of a calendar share.
         // 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 CalendarHome) {
                 return;
             }
             $this->server->transactionType = 'post-invite-reply';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $url = $node->shareReply($message->href, $message->status, $message->calendarUri, $message->inReplyTo, $message->summary);
             $response->setStatus(200);
             // 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');
             if ($url) {
                 $writer = $this->server->xml->getWriter($this->server->getBaseUri());
                 $writer->openMemory();
                 $writer->startDocument();
                 $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
                 $writer->write(new Href($url));
                 $writer->endElement();
                 $response->setHeader('Content-Type', 'application/xml');
                 $response->setBody($writer->outputMemory());
             }
             // Breaking the event chain
             return false;
         case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar':
             // We can only deal with IShareableCalendar objects
             if (!$node instanceof IShareableCalendar) {
                 return;
             }
             $this->server->transactionType = 'post-publish-calendar';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $node->setPublishStatus(true);
             // iCloud sends back the 202, so we will too.
             $response->setStatus(202);
             // 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;
         case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar':
             // We can only deal with IShareableCalendar objects
             if (!$node instanceof IShareableCalendar) {
                 return;
             }
             $this->server->transactionType = 'post-unpublish-calendar';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $node->setPublishStatus(false);
             $response->setStatus(200);
             // 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;
     }
 }
 /**
  * HTTP REPORT method implementation
  *
  * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
  * It's used in a lot of extensions, so it made sense to implement it into the core.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpReport(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     $result = $this->server->xml->parse($request->getBody(), $request->getUrl(), $rootElementName);
     if ($this->server->emit('report', [$rootElementName, $result, $path])) {
         // If emit returned true, it means the report was not supported
         throw new Exception\ReportNotSupported();
     }
     // Sending back false will interupt the event chain and tell the server
     // we've handled this method.
     return false;
 }
 /**
  * HTTP REPORT method implementation
  *
  * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
  * It's used in a lot of extensions, so it made sense to implement it into the core.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpReport(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     $body = $request->getBodyAsString();
     $dom = XMLUtil::loadDOMDocument($body);
     $reportName = XMLUtil::toClarkNotation($dom->firstChild);
     if ($this->server->emit('report', [$reportName, $dom, $path])) {
         // If emit returned true, it means the report was not supported
         throw new Exception\ReportNotSupported();
     }
     // Sending back false will interupt the event chain and tell the server
     // we've handled this method.
     return false;
 }
Exemple #26
0
 /**
  * The validateTokens event is triggered before every request.
  *
  * It's a moment where this plugin can check all the supplied lock tokens
  * in the If: header, and check if they are valid.
  *
  * In addition, it will also ensure that it checks any missing lokens that
  * must be present in the request, and reject requests without the proper
  * tokens.
  *
  * @param RequestInterface $request
  * @param mixed $conditions
  * @return void
  */
 function validateTokens(RequestInterface $request, &$conditions)
 {
     // First we need to gather a list of locks that must be satisfied.
     $mustLocks = [];
     $method = $request->getMethod();
     // Methods not in that list are operations that doesn't alter any
     // resources, and we don't need to check the lock-states for.
     switch ($method) {
         case 'DELETE':
             $mustLocks = array_merge($mustLocks, $this->getLocks($request->getPath(), true));
             break;
         case 'MKCOL':
         case 'MKCALENDAR':
         case 'PROPPATCH':
         case 'PUT':
         case 'PATCH':
             $mustLocks = array_merge($mustLocks, $this->getLocks($request->getPath(), false));
             break;
         case 'MOVE':
             $mustLocks = array_merge($mustLocks, $this->getLocks($request->getPath(), true));
             $mustLocks = array_merge($mustLocks, $this->getLocks($this->server->calculateUri($request->getHeader('Destination')), false));
             break;
         case 'COPY':
             $mustLocks = array_merge($mustLocks, $this->getLocks($this->server->calculateUri($request->getHeader('Destination')), false));
             break;
         case 'LOCK':
             //Temporary measure.. figure out later why this is needed
             // Here we basically ignore all incoming tokens...
             foreach ($conditions as $ii => $condition) {
                 foreach ($condition['tokens'] as $jj => $token) {
                     $conditions[$ii]['tokens'][$jj]['validToken'] = true;
                 }
             }
             return;
     }
     // It's possible that there's identical locks, because of shared
     // parents. We're removing the duplicates here.
     $tmp = [];
     foreach ($mustLocks as $lock) {
         $tmp[$lock->token] = $lock;
     }
     $mustLocks = array_values($tmp);
     foreach ($conditions as $kk => $condition) {
         foreach ($condition['tokens'] as $ii => $token) {
             // Lock tokens always start with opaquelocktoken:
             if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') {
                 continue;
             }
             $checkToken = substr($token['token'], 16);
             // Looping through our list with locks.
             foreach ($mustLocks as $jj => $mustLock) {
                 if ($mustLock->token == $checkToken) {
                     // We have a match!
                     // Removing this one from mustlocks
                     unset($mustLocks[$jj]);
                     // Marking the condition as valid.
                     $conditions[$kk]['tokens'][$ii]['validToken'] = true;
                     // Advancing to the next token
                     continue 2;
                 }
             }
             // If we got here, it means that there was a
             // lock-token, but it was not in 'mustLocks'.
             //
             // This is an edge-case, as it could mean that token
             // was specified with a url that was not 'required' to
             // check. So we're doing one extra lookup to make sure
             // we really don't know this token.
             //
             // This also gets triggered when the user specified a
             // lock-token that was expired.
             $oddLocks = $this->getLocks($condition['uri']);
             foreach ($oddLocks as $oddLock) {
                 if ($oddLock->token === $checkToken) {
                     // We have a hit!
                     $conditions[$kk]['tokens'][$ii]['validToken'] = true;
                     continue 2;
                 }
             }
             // If we get all the way here, the lock-token was
             // really unknown.
         }
     }
     // If there's any locks left in the 'mustLocks' array, it means that
     // the resource was locked and we must block it.
     if ($mustLocks) {
         throw new DAV\Exception\Locked(reset($mustLocks));
     }
 }
Exemple #27
0
 /**
  * Compute an HTTP POST method.
  *
  * @param  Request   $request     HTTP request.
  * @param  Response  $response    HTTP response.
  * @return bool
  * @throws Exception\Dav\Exception
  */
 function httpPost(Request $request, Response $response)
 {
     if (System\Collection::NAME . '/' . Node::NAME !== $request->getPath()) {
         return;
     }
     $payload = @json_decode($request->getBodyAsString());
     if (!$payload || !isset($payload->transport) || !isset($payload->username) || !isset($payload->password)) {
         throw new Exception\Dav\Exception('Payload is corrupted.');
     }
     $this->configuration->mail = new StdClass();
     $this->configuration->mail->transport = $payload->transport;
     $this->configuration->mail->username = $payload->username;
     $this->configuration->mail->password = $payload->password;
     $this->configuration->save();
     $response->setHeader('Content-Type', 'application/json');
     $response->setBody(json_encode(true));
     return false;
 }
Exemple #28
0
 /**
  * This method is responsible for handling the 'ACL' event.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpAcl(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     $body = $request->getBodyAsString();
     $dom = DAV\XMLUtil::loadDOMDocument($body);
     $newAcl = Property\Acl::unserialize($dom->firstChild, $this->server->propertyMap)->getPrivileges();
     // Normalizing urls
     foreach ($newAcl as $k => $newAce) {
         $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
     }
     $node = $this->server->tree->getNodeForPath($path);
     if (!$node instanceof IACL) {
         throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method');
     }
     $oldAcl = $this->getACL($node);
     $supportedPrivileges = $this->getFlatPrivilegeSet($node);
     /* Checking if protected principals from the existing principal set are
        not overwritten. */
     foreach ($oldAcl as $oldAce) {
         if (!isset($oldAce['protected']) || !$oldAce['protected']) {
             continue;
         }
         $found = false;
         foreach ($newAcl as $newAce) {
             if ($newAce['privilege'] === $oldAce['privilege'] && $newAce['principal'] === $oldAce['principal'] && $newAce['protected']) {
                 $found = true;
             }
         }
         if (!$found) {
             throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
         }
     }
     foreach ($newAcl as $newAce) {
         // Do we recognize the privilege
         if (!isset($supportedPrivileges[$newAce['privilege']])) {
             throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server');
         }
         if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
             throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege');
         }
         // Looking up the principal
         try {
             $principal = $this->server->tree->getNodeForPath($newAce['principal']);
         } catch (DAV\Exception\NotFound $e) {
             throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist');
         }
         if (!$principal instanceof IPrincipal) {
             throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal');
         }
     }
     $node->setACL($newAcl);
     $response->setStatus(200);
     // Breaking the event chain, because we handled this method.
     return false;
 }
Exemple #29
0
 /**
  * POST operation on Comments collections
  *
  * @param RequestInterface $request request object
  * @param ResponseInterface $response response object
  * @return null|false
  */
 public function httpPost(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     $node = $this->server->tree->getNodeForPath($path);
     if (!$node instanceof EntityCollection) {
         return null;
     }
     $data = $request->getBodyAsString();
     $comment = $this->createComment($node->getName(), $node->getId(), $data, $request->getHeader('Content-Type'));
     // update read marker for the current user/poster to avoid
     // having their own comments marked as unread
     $node->setReadMarker(null);
     $url = $request->getUrl() . '/' . urlencode($comment->getId());
     $response->setHeader('Content-Location', $url);
     // created
     $response->setStatus(201);
     return false;
 }
 /**
  * We intercept this to handle POST requests on calendars.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return null|bool
  */
 function httpPost(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     // Only handling xml
     $contentType = $request->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($path);
     } catch (DAV\Exception\NotFound $e) {
         return;
     }
     $requestBody = $request->getBodyAsString();
     // 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.
     $request->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;
             }
             $this->server->transactionType = 'post-calendar-share';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $mutations = $this->parseShareRequest($dom);
             $node->updateShares($mutations[0], $mutations[1]);
             $response->setStatus(200);
             // 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;
             // The invite-reply document is sent when the user replies to an
             // invitation of a calendar share.
         // 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 CalendarHome) {
                 return;
             }
             $this->server->transactionType = 'post-invite-reply';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $message = $this->parseInviteReplyRequest($dom);
             $url = $node->shareReply($message['href'], $message['status'], $message['calendarUri'], $message['inReplyTo'], $message['summary']);
             $response->setStatus(200);
             // 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');
             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);
                 $response->setHeader('Content-Type', 'application/xml');
                 $response->setBody($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;
             }
             $this->server->transactionType = 'post-publish-calendar';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $node->setPublishStatus(true);
             // iCloud sends back the 202, so we will too.
             $response->setStatus(202);
             // 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;
         case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar':
             // We can only deal with IShareableCalendar objects
             if (!$node instanceof IShareableCalendar) {
                 return;
             }
             $this->server->transactionType = 'post-unpublish-calendar';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 $acl->checkPrivileges($path, '{DAV:}write');
             }
             $node->setPublishStatus(false);
             $response->setStatus(200);
             // 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;
     }
 }