Ejemplo n.º 1
0
 /**
  * principalSearchPropertySetReport
  *
  * This method responsible for handing the
  * {DAV:}principal-search-property-set report. This report returns a list
  * of properties the client may search on, using the
  * {DAV:}principal-property-search report.
  *
  * @param Xml\Request\PrincipalSearchPropertySetReport $report
  * @return void
  */
 protected function principalSearchPropertySetReport($report)
 {
     $httpDepth = $this->server->getHTTPDepth(0);
     if ($httpDepth !== 0) {
         throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
     }
     $writer = $this->server->xml->getWriter();
     $writer->openMemory();
     $writer->startDocument();
     $writer->startElement('{DAV:}principal-search-property-set');
     foreach ($this->principalSearchPropertySet as $propertyName => $description) {
         $writer->startElement('{DAV:}principal-search-property');
         $writer->startElement('{DAV:}prop');
         $writer->writeElement($propertyName);
         $writer->endElement();
         // prop
         if ($description) {
             $writer->write([['name' => '{DAV:}description', 'value' => $description, 'attributes' => ['xml:lang' => 'en']]]);
         }
         $writer->endElement();
         // principal-search-property
     }
     $writer->endElement();
     // principal-search-property-set
     $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $this->server->httpResponse->setStatus(200);
     $this->server->httpResponse->setBody($writer->outputMemory());
 }
Ejemplo n.º 2
0
 /**
  * aclPrincipalPropSet REPORT
  *
  * This method is responsible for handling the {DAV:}acl-principal-prop-set
  * REPORT, as defined in:
  *
  * https://tools.ietf.org/html/rfc3744#section-9.2
  *
  * This REPORT allows a user to quickly fetch information about all
  * principals specified in the access control list. Most commonly this
  * is used to for example generate a UI with ACL rules, allowing you
  * to show names for principals for every entry.
  *
  * @param string $path
  * @param Xml\Request\AclPrincipalPropSetReport $report
  * @return void
  */
 protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
 {
     if ($this->server->getHTTPDepth(0) !== 0) {
         throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
     }
     // Fetching ACL rules for the given path. We're using the property
     // API and not the local getACL, because it will ensure that all
     // business rules and restrictions are applied.
     $acl = $this->server->getProperties($path, '{DAV:}acl');
     if (!$acl || !isset($acl['{DAV:}acl'])) {
         throw new Forbidden('Could not fetch ACL rules for this path');
     }
     $principals = [];
     foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {
         if ($ace['principal'][0] === '{') {
             // It's not a principal, it's one of the special rules such as {DAV:}authenticated
             continue;
         }
         $principals[] = $ace['principal'];
     }
     $properties = $this->server->getPropertiesForMultiplePaths($principals, $report->properties);
     $this->server->httpResponse->setStatus(207);
     $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $this->server->httpResponse->setBody($this->server->generateMultiStatus($properties));
 }
Ejemplo n.º 3
0
 /**
  * This function handles the addressbook-query REPORT
  *
  * This report is used by the client to filter an addressbook based on a
  * complex query.
  *
  * @param \DOMNode $dom
  * @return void
  */
 protected function addressbookQueryReport($dom)
 {
     $query = new AddressBookQueryParser($dom);
     $query->parse();
     $depth = $this->server->getHTTPDepth(0);
     if ($depth == 0) {
         $candidateNodes = [$this->server->tree->getNodeForPath($this->server->getRequestUri())];
         if (!$candidateNodes[0] instanceof ICard) {
             throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
         }
     } else {
         $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
     }
     $xpath = new \DOMXPath($dom);
     $xpath->registerNameSpace('card', Plugin::NS_CARDDAV);
     $xpath->registerNameSpace('dav', 'urn:DAV');
     $contentType = $xpath->evaluate("string(/card:addressbook-query/dav:prop/card:address-data/@content-type)");
     $version = $xpath->evaluate("string(/card:addressbook-query/dav:prop/card:address-data/@version)");
     if ($version) {
         $contentType .= '; version=' . $version;
     }
     $vcardType = $this->negotiateVCard($contentType);
     $validNodes = [];
     foreach ($candidateNodes as $node) {
         if (!$node instanceof ICard) {
             continue;
         }
         $blob = $node->get();
         if (is_resource($blob)) {
             $blob = stream_get_contents($blob);
         }
         if (!$this->validateFilters($blob, $query->filters, $query->test)) {
             continue;
         }
         $validNodes[] = $node;
         if ($query->limit && $query->limit <= count($validNodes)) {
             // We hit the maximum number of items, we can stop now.
             break;
         }
     }
     $result = [];
     foreach ($validNodes as $validNode) {
         if ($depth == 0) {
             $href = $this->server->getRequestUri();
         } else {
             $href = $this->server->getRequestUri() . '/' . $validNode->getName();
         }
         list($props) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0);
         if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) {
             $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard($props[200]['{' . self::NS_CARDDAV . '}address-data'], $vcardType);
         }
         $result[] = $props;
     }
     $prefer = $this->server->getHTTPPRefer();
     $this->server->httpResponse->setStatus(207);
     $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
     $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return-minimal']));
 }
Ejemplo n.º 4
0
 /**
  * Locks an uri
  *
  * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
  * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
  * of lock (shared or exclusive) and the owner of the lock
  *
  * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
  *
  * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
  *
  * @param string $uri
  * @return void
  */
 protected function httpLock($uri)
 {
     $lastLock = null;
     if (!$this->validateLock($uri, $lastLock)) {
         // If the existing lock was an exclusive lock, we need to fail
         if (!$lastLock || $lastLock->scope == LockInfo::EXCLUSIVE) {
             //var_dump($lastLock);
             throw new DAV\Exception\ConflictingLock($lastLock);
         }
     }
     if ($body = $this->server->httpRequest->getBody(true)) {
         // This is a new lock request
         $lockInfo = $this->parseLockRequest($body);
         $lockInfo->depth = $this->server->getHTTPDepth();
         $lockInfo->uri = $uri;
         if ($lastLock && $lockInfo->scope != LockInfo::SHARED) {
             throw new DAV\Exception\ConflictingLock($lastLock);
         }
     } elseif ($lastLock) {
         // This must have been a lock refresh
         $lockInfo = $lastLock;
         // The resource could have been locked through another uri.
         if ($uri != $lockInfo->uri) {
             $uri = $lockInfo->uri;
         }
     } else {
         // There was neither a lock refresh nor a new lock request
         throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
     }
     if ($timeout = $this->getTimeoutHeader()) {
         $lockInfo->timeout = $timeout;
     }
     $newFile = false;
     // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
     try {
         $this->server->tree->getNodeForPath($uri);
         // We need to call the beforeWriteContent event for RFC3744
         // Edit: looks like this is not used, and causing problems now.
         //
         // See Issue 222
         // $this->server->broadcastEvent('beforeWriteContent',array($uri));
     } catch (DAV\Exception\NotFound $e) {
         // It didn't, lets create it
         $this->server->createFile($uri, fopen('php://memory', 'r'));
         $newFile = true;
     }
     $this->lockNode($uri, $lockInfo);
     $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $this->server->httpResponse->setHeader('Lock-Token', '<opaquelocktoken:' . $lockInfo->token . '>');
     $this->server->httpResponse->sendStatus($newFile ? 201 : 200);
     $this->server->httpResponse->sendBody($this->generateLockResponse($lockInfo));
 }
Ejemplo n.º 5
0
 /**
  * This function handles the addressbook-query REPORT
  *
  * This report is used by the client to filter an addressbook based on a
  * complex query.
  *
  * @param \DOMNode $dom
  * @return void
  */
 protected function addressbookQueryReport($dom)
 {
     $query = new AddressBookQueryParser($dom);
     $query->parse();
     $depth = $this->server->getHTTPDepth(0);
     if ($depth == 0) {
         $candidateNodes = array($this->server->tree->getNodeForPath($this->server->getRequestUri()));
     } else {
         $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
     }
     $validNodes = array();
     foreach ($candidateNodes as $node) {
         if (!$node instanceof ICard) {
             continue;
         }
         $blob = $node->get();
         if (is_resource($blob)) {
             $blob = stream_get_contents($blob);
         }
         if (!$this->validateFilters($blob, $query->filters, $query->test)) {
             continue;
         }
         $validNodes[] = $node;
         if ($query->limit && $query->limit <= count($validNodes)) {
             // We hit the maximum number of items, we can stop now.
             break;
         }
     }
     $result = array();
     foreach ($validNodes as $validNode) {
         if ($depth == 0) {
             $href = $this->server->getRequestUri();
         } else {
             $href = $this->server->getRequestUri() . '/' . $validNode->getName();
         }
         list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0);
     }
     $prefer = $this->server->getHTTPPRefer();
     $this->server->httpResponse->sendStatus(207);
     $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
     $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal']));
 }
Ejemplo n.º 6
0
 /**
  * parsePrincipalPropertySearchReportRequest
  *
  * This method parses the request body from a
  * {DAV:}principal-property-search report.
  *
  * This method returns an array with two elements:
  *  1. an array with properties to search on, and their values
  *  2. a list of propertyvalues that should be returned for the request.
  *
  * @param \DOMDocument $dom
  * @return array
  */
 protected function parsePrincipalPropertySearchReportRequest($dom)
 {
     $httpDepth = $this->server->getHTTPDepth(0);
     if ($httpDepth !== 0) {
         throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
     }
     $searchProperties = [];
     $applyToPrincipalCollectionSet = false;
     $test = $dom->firstChild->getAttribute('test') === 'anyof' ? 'anyof' : 'allof';
     // Parsing the search request
     foreach ($dom->firstChild->childNodes as $searchNode) {
         if (DAV\XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') {
             $applyToPrincipalCollectionSet = true;
         }
         if (DAV\XMLUtil::toClarkNotation($searchNode) !== '{DAV:}property-search') {
             continue;
         }
         $propertyName = null;
         $propertyValue = null;
         foreach ($searchNode->childNodes as $childNode) {
             switch (DAV\XMLUtil::toClarkNotation($childNode)) {
                 case '{DAV:}prop':
                     $property = DAV\XMLUtil::parseProperties($searchNode);
                     reset($property);
                     $propertyName = key($property);
                     break;
                 case '{DAV:}match':
                     $propertyValue = $childNode->textContent;
                     break;
             }
         }
         if (is_null($propertyName) || is_null($propertyValue)) {
             throw new DAV\Exception\BadRequest('Invalid search request. propertyname: ' . $propertyName . '. propertvvalue: ' . $propertyValue);
         }
         $searchProperties[$propertyName] = $propertyValue;
     }
     return [$searchProperties, array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)), $applyToPrincipalCollectionSet, $test];
 }
Ejemplo n.º 7
0
 /**
  * Locks an uri
  *
  * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
  * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
  * of lock (shared or exclusive) and the owner of the lock
  *
  * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
  *
  * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
  *
  * @param RequestInterface $request
  * @param ResponseInterface $response
  * @return bool
  */
 function httpLock(RequestInterface $request, ResponseInterface $response)
 {
     $uri = $request->getPath();
     $existingLocks = $this->getLocks($uri);
     if ($body = $request->getBodyAsString()) {
         // This is a new lock request
         $existingLock = null;
         // Checking if there's already non-shared locks on the uri.
         foreach ($existingLocks as $existingLock) {
             if ($existingLock->scope === LockInfo::EXCLUSIVE) {
                 throw new DAV\Exception\ConflictingLock($existingLock);
             }
         }
         $lockInfo = $this->parseLockRequest($body);
         $lockInfo->depth = $this->server->getHTTPDepth();
         $lockInfo->uri = $uri;
         if ($existingLock && $lockInfo->scope != LockInfo::SHARED) {
             throw new DAV\Exception\ConflictingLock($existingLock);
         }
     } else {
         // Gonna check if this was a lock refresh.
         $existingLocks = $this->getLocks($uri);
         $conditions = $this->server->getIfConditions($request);
         $found = null;
         foreach ($existingLocks as $existingLock) {
             foreach ($conditions as $condition) {
                 foreach ($condition['tokens'] as $token) {
                     if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) {
                         $found = $existingLock;
                         break 3;
                     }
                 }
             }
         }
         // If none were found, this request is in error.
         if (is_null($found)) {
             if ($existingLocks) {
                 throw new DAV\Exception\Locked(reset($existingLocks));
             } else {
                 throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
             }
         }
         // This must have been a lock refresh
         $lockInfo = $found;
         // The resource could have been locked through another uri.
         if ($uri != $lockInfo->uri) {
             $uri = $lockInfo->uri;
         }
     }
     if ($timeout = $this->getTimeoutHeader()) {
         $lockInfo->timeout = $timeout;
     }
     $newFile = false;
     // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
     try {
         $this->server->tree->getNodeForPath($uri);
         // We need to call the beforeWriteContent event for RFC3744
         // Edit: looks like this is not used, and causing problems now.
         //
         // See Issue 222
         // $this->server->emit('beforeWriteContent',array($uri));
     } catch (DAV\Exception\NotFound $e) {
         // It didn't, lets create it
         $this->server->createFile($uri, fopen('php://memory', 'r'));
         $newFile = true;
     }
     $this->lockNode($uri, $lockInfo);
     $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
     $response->setHeader('Lock-Token', '<opaquelocktoken:' . $lockInfo->token . '>');
     $response->setStatus($newFile ? 201 : 200);
     $response->setBody($this->generateLockResponse($lockInfo));
     // Returning false will interupt the event chain and mark this method
     // as 'handled'.
     return false;
 }