Exemple #1
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 #2
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;
     }
 }
 /**
  * Detects all unsupported clients and throws a \Sabre\DAV\Exception\Forbidden
  * exception which will result in a 403 to them.
  * @param RequestInterface $request
  * @throws \Sabre\DAV\Exception\Forbidden If the client version is not supported
  */
 public function beforeHandler(RequestInterface $request)
 {
     $userAgent = $request->getHeader('User-Agent');
     if ($userAgent === null) {
         return;
     }
     $minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '1.7.0');
     // Match on the mirall version which is in scheme "Mozilla/5.0 (%1) mirall/%2" or
     // "mirall/%1" for older releases
     preg_match("/(?:mirall\\/)([\\d.]+)/i", $userAgent, $versionMatches);
     if (isset($versionMatches[1]) && version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
         throw new \Sabre\DAV\Exception\Forbidden('Unsupported client version.');
     }
 }
Exemple #4
0
 /**
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return array
  */
 private function auth(RequestInterface $request, ResponseInterface $response)
 {
     if (\OC_User::handleApacheAuth() || $this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED)) || $this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) {
         $user = $this->userSession->getUser()->getUID();
         \OC_Util::setupFS($user);
         $this->currentUser = $user;
         $this->session->close();
         return [true, $this->principalPrefix . $user];
     }
     if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With')))) {
         // do not re-authenticate over ajax, use dummy auth name to prevent browser popup
         $response->addHeader('WWW-Authenticate', 'DummyBasic realm="' . $this->realm . '"');
         $response->setStatus(401);
         throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
     }
     return parent::check($request, $response);
 }
Exemple #5
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 #6
0
 /**
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return array
  * @throws NotAuthenticated
  */
 private function auth(RequestInterface $request, ResponseInterface $response)
 {
     $forcedLogout = false;
     if (!$this->request->passesCSRFCheck() && $this->requiresCSRFCheck()) {
         // In case of a fail with POST we need to recheck the credentials
         if ($this->request->getMethod() === 'POST') {
             $forcedLogout = true;
         } else {
             $response->setStatus(401);
             throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.');
         }
     }
     if ($forcedLogout) {
         $this->userSession->logout();
     } else {
         if ($this->twoFactorManager->needsSecondFactor()) {
             throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
         }
         if (\OC_User::handleApacheAuth() || $this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED)) || $this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) {
             $user = $this->userSession->getUser()->getUID();
             \OC_Util::setupFS($user);
             $this->currentUser = $user;
             $this->session->close();
             return [true, $this->principalPrefix . $user];
         }
     }
     if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With')))) {
         // do not re-authenticate over ajax, use dummy auth name to prevent browser popup
         $response->addHeader('WWW-Authenticate', 'DummyBasic realm="' . $this->realm . '"');
         $response->setStatus(401);
         throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
     }
     $data = parent::check($request, $response);
     if ($data[0] === true) {
         $startPos = strrpos($data[1], '/') + 1;
         $user = $this->userSession->getUser()->getUID();
         $data[1] = substr_replace($data[1], $user, $startPos);
     }
     return $data;
 }
Exemple #7
0
 /**
  * This event is triggered after GET requests.
  *
  * This is used to transform data into jCal, if this was requested.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return void
  */
 function httpAfterGet(RequestInterface $request, ResponseInterface $response)
 {
     if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) {
         return;
     }
     $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
     $newBody = $this->convertVCard($response->getBody(), $target);
     $response->setBody($newBody);
     $response->setHeader('Content-Type', $mimeType . '; charset=utf-8');
     $response->setHeader('Content-Length', strlen($newBody));
 }
 /**
  * WebDAV MKCOL
  *
  * The MKCOL method is used to create a new collection (directory) on the server
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpMkcol(RequestInterface $request, ResponseInterface $response)
 {
     $requestBody = $request->getBodyAsString();
     $path = $request->getPath();
     if ($requestBody) {
         $contentType = $request->getHeader('Content-Type');
         if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) {
             // We must throw 415 for unsupported mkcol bodies
             throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
         }
         $dom = XMLUtil::loadDOMDocument($requestBody);
         if (XMLUtil::toClarkNotation($dom->firstChild) !== '{DAV:}mkcol') {
             // We must throw 415 for unsupported mkcol bodies
             throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must be a {DAV:}mkcol request construct.');
         }
         $properties = [];
         foreach ($dom->firstChild->childNodes as $childNode) {
             if (XMLUtil::toClarkNotation($childNode) !== '{DAV:}set') {
                 continue;
             }
             $properties = array_merge($properties, XMLUtil::parseProperties($childNode, $this->server->propertyMap));
         }
         if (!isset($properties['{DAV:}resourcetype'])) {
             throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
         }
         $resourceType = $properties['{DAV:}resourcetype']->getValue();
         unset($properties['{DAV:}resourcetype']);
     } else {
         $properties = [];
         $resourceType = ['{DAV:}collection'];
     }
     $result = $this->server->createCollection($path, $resourceType, $properties);
     if (is_array($result)) {
         $response->setStatus(207);
         $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
         $response->setBody($this->server->generateMultiStatus([$result]));
     } else {
         $response->setHeader('Content-Length', '0');
         $response->setStatus(201);
     }
     // Sending back false will interupt the event chain and tell the server
     // we've handled this method.
     return false;
 }
Exemple #9
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;
 }
 /**
  * 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;
     }
 }
Exemple #11
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 #12
0
 /**
  * @return bool
  */
 function httpGet(Request $request, Response $response)
 {
     if (System\Collection::NAME . '/' . Node::NAME !== $request->getPath()) {
         return;
     }
     $payload = ['current_version' => SABRE_KATANA_VERSION];
     $extra = [];
     if (true === $request->hasHeader('Referer')) {
         $extra['referer'] = $request->getHeader('Referer');
     }
     $updatesDotJson = Updater::getUpdateUrl(Updater::DEFAULT_UPDATE_SERVER, $extra);
     $versions = @file_get_contents($updatesDotJson);
     if (!empty($versions)) {
         $versions = json_decode($versions, true);
         $versionsToFetch = Updater::filterVersions($versions, SABRE_KATANA_VERSION, Updater::FORMAT_PHAR);
         $payload['next_versions'] = array_keys($versionsToFetch);
     }
     $response->setHeader('Content-Type', 'application/json');
     $response->setBody(json_encode($payload));
     return false;
 }
Exemple #13
0
 /**
  * We intercept this to handle POST requests on shared resources
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return null|bool
  */
 function httpPost(RequestInterface $request, ResponseInterface $response)
 {
     $path = $request->getPath();
     $contentType = $request->getHeader('Content-Type');
     // We're only interested in the davsharing content type.
     if (strpos($contentType, 'application/davsharing+xml') === false) {
         return;
     }
     $message = $this->server->xml->parse($request->getBody(), $request->getUrl(), $documentType);
     switch ($documentType) {
         case '{DAV:}share-resource':
             $this->shareResource($path, $message->sharees);
             $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;
         default:
             throw new BadRequest('Unexpected document type: ' . $documentType . ' for this Content-Type');
     }
 }
Exemple #14
0
 /**
  * We intercept this to handle POST requests on a dav resource.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return null|false
  */
 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;
     }
     $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 '{' . self::NS_OWNCLOUD . '}share':
             // We can only deal with IShareableCalendar objects
             if (!$node instanceof IShareable) {
                 return;
             }
             $this->server->transactionType = 'post-oc-resource-share';
             // Getting ACL info
             $acl = $this->server->getPlugin('acl');
             // If there's no ACL support, we allow everything
             if ($acl) {
                 /** @var \Sabre\DAVACL\Plugin $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;
     }
 }
Exemple #15
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;
 }
Exemple #16
0
 /**
  * This event is triggered after GET requests.
  *
  * This is used to transform data into jCal, if this was requested.
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return void
  */
 function httpAfterGet(RequestInterface $request, ResponseInterface $response)
 {
     if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
         return;
     }
     $result = HTTP\Util::negotiate($request->getHeader('Accept'), ['text/calendar', 'application/calendar+json']);
     if ($result !== 'application/calendar+json') {
         // Do nothing
         return;
     }
     // Transforming.
     $vobj = VObject\Reader::read($response->getBody());
     $jsonBody = json_encode($vobj->jsonSerialize());
     $response->setBody($jsonBody);
     $response->setHeader('Content-Type', 'application/calendar+json');
     $response->setHeader('Content-Length', strlen($jsonBody));
 }
Exemple #17
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;
     }
 }
 /**
  * WebDAV MKCOL
  *
  * The MKCOL method is used to create a new collection (directory) on the server
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpMkcol(RequestInterface $request, ResponseInterface $response)
 {
     $requestBody = $request->getBodyAsString();
     $path = $request->getPath();
     if ($requestBody) {
         $contentType = $request->getHeader('Content-Type');
         if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) {
             // We must throw 415 for unsupported mkcol bodies
             throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
         }
         try {
             $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
         } catch (\Sabre\Xml\ParseException $e) {
             throw new Exception\BadRequest($e->getMessage(), null, $e);
         }
         $properties = $mkcol->getProperties();
         if (!isset($properties['{DAV:}resourcetype'])) {
             throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
         }
         $resourceType = $properties['{DAV:}resourcetype']->getValue();
         unset($properties['{DAV:}resourcetype']);
     } else {
         $properties = [];
         $resourceType = ['{DAV:}collection'];
     }
     $mkcol = new MkCol($resourceType, $properties);
     $result = $this->server->createCollection($path, $mkcol);
     if (is_array($result)) {
         $response->setStatus(207);
         $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
         $response->setBody($this->server->generateMultiStatus([$result]));
     } else {
         $response->setHeader('Content-Length', '0');
         $response->setStatus(201);
     }
     // Sending back false will interupt the event chain and tell the server
     // we've handled this method.
     return false;
 }
Exemple #19
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 #20
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 #21
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;
 }
Exemple #22
0
 /**
  * Returns the HTTP custom range update header
  *
  * This method returns null if there is no well-formed HTTP range request
  * header. It returns array(1) if it was an append request, array(2,
  * $start, $end) if it's a start and end range, lastly it's array(3,
  * $endoffset) if the offset was negative, and should be calculated from
  * the end of the file.
  *
  * Examples:
  *
  * null - invalid
  * [1] - append
  * [2,10,15] - update bytes 10, 11, 12, 13, 14, 15
  * [2,10,null] - update bytes 10 until the end of the patch body
  * [3,-5] - update from 5 bytes from the end of the file.
  *
  * @param RequestInterface $request
  * @return array|null
  */
 function getHTTPUpdateRange(RequestInterface $request)
 {
     $range = $request->getHeader('X-Update-Range');
     if (is_null($range)) {
         return null;
     }
     // Matching "Range: bytes=1234-5678: both numbers are optional
     if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) {
         return null;
     }
     if ($matches[1] === 'append') {
         return [self::RANGE_APPEND];
     } elseif (strlen($matches[2]) > 0) {
         return [self::RANGE_START, $matches[2], $matches[3] ?: null];
     } elseif ($matches[4]) {
         return [self::RANGE_END, $matches[4]];
     } else {
         return null;
     }
 }
Exemple #23
0
 /**
  * This method checks the 'Schedule-Reply' header
  * and returns false if it's 'F', otherwise true.
  *
  * @param RequestInterface $request
  * @return bool
  */
 private function scheduleReply(RequestInterface $request)
 {
     $scheduleReply = $request->getHeader('Schedule-Reply');
     return $scheduleReply !== 'F';
 }