requestUri; // Check authorization if ( !ezcWebdavServer::getInstance()->isAuthorized( $source, $request->getHeader( 'Authorization' ) ) ) { return $this->createUnauthorizedResponse( $source, $request->getHeader( 'Authorization' ) ); } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } // Verify If-[None-]Match headers if ( ( $res = $this->checkIfMatchHeaders( $request, $source ) ) !== null ) { return $res; } $res = null; // Init if ( !$this->isCollection( $source ) ) { // Just deliver file $res = new ezcWebdavGetResourceResponse( new ezcWebdavResource( $source, $this->getAllProperties( $source ), $this->getResourceContents( $source ) ) ); } else { // Return collection with contained children $res = new ezcWebdavGetCollectionResponse( new ezcWebdavCollection( $source, $this->getAllProperties( $source ), $this->getCollectionMembers( $source ) ) ); } // Add ETag header $res->setHeader( 'ETag', $this->getETag( $source ) ); // Deliver response return $res; } /** * Serves HEAD requests. * * The method receives a {@link ezcWebdavHeadRequest} object containing all * relevant information obout the clients request and will return an {@link * ezcWebdavErrorResponse} instance on error or an instance of {@link * ezcWebdavHeadResponse} on success. * * @param ezcWebdavHeadRequest $request * @return ezcWebdavResponse */ public function head( ezcWebdavHeadRequest $request ) { $source = $request->requestUri; // Check authorization if ( !ezcWebdavServer::getInstance()->isAuthorized( $source, $request->getHeader( 'Authorization' ) ) ) { return $this->createUnauthorizedResponse( $source, $request->getHeader( 'Authorization' ) ); } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } $res = null; // Init if ( !$this->isCollection( $source ) ) { // Just deliver file without contents $res = new ezcWebdavHeadResponse( new ezcWebdavResource( $source, $this->getAllProperties( $source ) ) ); } else { // Just deliver collection without children $res = new ezcWebdavHeadResponse( new ezcWebdavCollection( $source, $this->getAllProperties( $source ) ) ); } // Add ETag header $res->setHeader( 'ETag', $this->getETag( $source ) ); // Deliver response return $res; } /** * Returns all child nodes. * * Get all nodes from the resource identified by $source up to the given * depth. Reuses the method {@link getCollectionMembers()}, but you may * want to overwrite this implementation by somethings which fits better * with your backend. * * @param string $source * @param int $depth * @return array(ezcWebdavResource|ezcWebdavCollection) */ protected function getNodes( $source, $depth ) { // No special handling for plain resources if ( !$this->isCollection( $source ) ) { return array( new ezcWebdavResource( $source ) ); } // For zero depth just return the collection if ( $depth === ezcWebdavRequest::DEPTH_ZERO ) { return array( new ezcWebdavCollection( $source ) ); } $nodes = array( new ezcWebdavCollection( $source ) ); $recurseCollections = array( $source ); // Collect children for all collections listed in $recurseCollections. for ( $i = 0; $i < count( $recurseCollections ); ++$i ) { $source = $recurseCollections[$i]; $children = $this->getCollectionMembers( $source ); foreach ( $children as $child ) { $nodes[] = $child; // Check if we should recurse deeper, and add collections to // processing list in this case. if ( ( $child instanceof ezcWebdavCollection ) && ( $depth === ezcWebdavRequest::DEPTH_INFINITY ) ) { $recurseCollections[] = $child->path; } } } return $nodes; } /** * Returns properties, fetched by name. * * Fetch properties as defined by the passed $request for the resource * referenced. Properties are fetched by their names. * * This method checks also for each of the nodes affected by the request if * authorization suceeds. * * @param ezcWebdavPropFindRequest $request * @return ezcWebdavResponse */ protected function fetchProperties( ezcWebdavPropFindRequest $request ) { $source = $request->requestUri; // Get list of all affected node, depeding on source and depth $nodes = $this->getNodes( $source, $request->getHeader( 'Depth' ) ); // Pathes which were already determined as unauthorized $unauthorizedPaths = array(); $server = ezcWebdavServer::getInstance(); $performAuth = ( $server->auth !== null && $server->auth instanceof ezcWebdavAuthorizer ); // Get requested properties for all files $responses = array(); foreach ( $nodes as $node ) { // Responses for the current node $nodeResponses = array(); // Authorization $authorized = true; if ( $performAuth ) { $nodePath = $node->path; foreach ( $unauthorizedPaths as $unauthorizedPath ) { // Check if a parent path was already determined as unauthorized if ( strpos( $nodePath, $unauthorizedPath ) === 0 ) { // Skip this node completely, since we already have a // parent node with 403 continue 2; } } // Check authorization if ( !ezcWebdavServer::getInstance()->isAuthorized( $nodePath, $request->getHeader( 'Authorization' ) ) ) { $authorized = false; $unauthorizedPaths[] = $nodePath; } } if ( !$authorized ) { $nodeResponses[] = new ezcWebdavPropStatResponse( $request->prop, // We send 403 Forbidden here. Hope that's correct? RFC // does not state anything... ezcWebdavResponse::STATUS_403 ); } else { // Get all properties form node ... $nodeProperties = $this->getAllProperties( $node->path ); // ... and diff the with the requested properties. $notFound = $request->prop->diff( $nodeProperties ); $valid = $nodeProperties->intersect( $request->prop ); // Add propstat sub response for valid responses if ( count( $valid ) ) { $nodeResponses[] = new ezcWebdavPropStatResponse( $valid ); } // Only create error response, when some properties could not be // found. if ( count( $notFound ) ) { $nodeResponses[] = new ezcWebdavPropStatResponse( $notFound, ezcWebdavResponse::STATUS_404 ); } } // Create response $responses[] = new ezcWebdavPropFindResponse( $node, $nodeResponses ); } return new ezcWebdavMultistatusResponse( $responses ); } /** * Returns names of all available properties for a resource. * * Fetches the names of all properties assigned to the reosource referenced * in $request and, if the resozurce is a collection, also returns property * names for its children, depending on the depth header of the $request. * * @param ezcWebdavPropFindRequest $request * @return ezcWebdavResponse */ protected function fetchPropertyNames( ezcWebdavPropFindRequest $request ) { $source = $request->requestUri; // Get list of all affected node, depeding on source and depth $nodes = $this->getNodes( $source, $request->getHeader( 'Depth' ) ); // Pathes which were already determined as unauthorized $unauthorizedPaths = array(); $server = ezcWebdavServer::getInstance(); $performAuth = ( $server->auth !== null && $server->auth instanceof ezcWebdavAuthorizer ); // Get requested properties for all files $responses = array(); foreach ( $nodes as $node ) { if ( $performAuth ) { $nodePath = $node->path; foreach ( $unauthorizedPaths as $unauthorizedPath ) { // Check if a parent path was already determined as unauthorized if ( substr( $nodePath, $unauthorizedPath ) === 0 ) { // Skip this node completely, since we already have a // parent node with error response continue 2; } } // Check authorization if ( !ezcWebdavServer::getInstance()->isAuthorized( $nodePath, $request->getHeader( 'Authorization' ) ) ) { $unauthorizedPaths[] = $nodePath; // Silently exclude unauthorized properties. $responses[] = new ezcWebdavPropFindResponse( $node, new ezcWebdavPropStatResponse( new ezcWebdavBasicPropertyStorage() ) ); // Skip further processing of this node continue; } } // Get all properties form node ... $nodeProperties = $this->getAllProperties( $node->path ); // ... and clear and add them to the property name storage. $propertyNames = new ezcWebdavBasicPropertyStorage(); foreach ( $nodeProperties->getAllProperties() as $namespace => $properties ) { foreach ( $properties as $name => $property ) { // Clear property, because the client only want the names // of the available properties. $property = clone $property; $property->clear(); $propertyNames->attach( $property ); } } // Add response $responses[] = new ezcWebdavPropFindResponse( $node, new ezcWebdavPropStatResponse( $propertyNames ) ); } return new ezcWebdavMultistatusResponse( $responses ); } /** * Returns all available properties for a resource. * * Fetches all available properties assigned to the reosource referenced in * $request and, if the resource is a collection, also returns properties * for its children, depending on the depth header of the $request. The * instances of {@link ezcWebdavPropFindResponse} generated by this method * are encapsulated in a {@link ezcWebdavMultistatusResponse} object. * * @param ezcWebdavPropFindRequest $request * @return ezcWebdavMultistatusResponse */ protected function fetchAllProperties( ezcWebdavPropFindRequest $request ) { $source = $request->requestUri; // Get list of all affected node, depeding on source and depth $nodes = $this->getNodes( $source, $request->getHeader( 'Depth' ) ); // Pathes which were already determined as unauthorized $unauthorizedPaths = array(); $server = ezcWebdavServer::getInstance(); $performAuth = ( $server->auth !== null && $server->auth instanceof ezcWebdavAuthorizer ); // Get requested properties for all files $responses = array(); foreach ( $nodes as $node ) { if ( $performAuth ) { foreach ( $unauthorizedPaths as $unauthorizedPath ) { // Check if a parent path was already determined as unauthorized if ( substr( $node->path, $unauthorizedPath ) === 0 ) { // Skip this node completely, since we already have a // parent node with 403 continue 2; } } $nodePath = $node->path; // Check authorization if ( !ezcWebdavServer::getInstance()->isAuthorized( $nodePath, $request->getHeader( 'Authorization' ) ) ) { $responses[] = $this->createUnauthorizedResponse( $nodePath, $request->getHeader( 'Authorization' ) ); $unauthorizedPaths[] = $nodePath; // Skip further processing of node continue; } } // Just create response from properties $responses[] = new ezcWebdavPropFindResponse( $node, new ezcWebdavPropStatResponse( $this->getAllProperties( $node->path ) ) ); } return new ezcWebdavMultistatusResponse( $responses ); } /** * Serves PROPFIND requests. * * The method receives a {@link ezcWebdavPropFindRequest} object containing * all relevant information obout the clients request and will either * return an instance of {@link ezcWebdavErrorResponse} to indicate an error * or a {@link ezcWebdavPropFindResponse} on success. If the referenced * resource is a collection or if some properties produced errors, an * instance of {@link ezcWebdavMultistatusResponse} may be returned. * * The {@link ezcWebdavPropFindRequest} object contains a definition to * find one or more properties of a given collection or non-collection * resource. * * @param ezcWebdavPropFindRequest $request * @return ezcWebdavResponse */ public function propFind( ezcWebdavPropFindRequest $request ) { $source = $request->requestUri; if ( !ezcWebdavServer::getInstance()->isAuthorized( $source, $request->getHeader( 'Authorization' ) ) ) { // Globally issue a 401, if the user does not have access to the // requested resource itself. return $this->createUnauthorizedResponse( $source, $request->getHeader( 'Authorization' ) ); // Multistatus with 403 will be issued for nested resources in the // specific methods. } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } // Verify If-[None-]Match headers $res = $this->checkIfMatchHeadersRecursive( $request, $source, $request->getHeader( 'Depth' ) ); if ( $res !== null ) { return $res; } // Check the exact type of propfind request and dispatch to // corresponding method. switch ( true ) { case $request->prop: return $this->fetchProperties( $request ); case $request->propName: return $this->fetchPropertyNames( $request ); case $request->allProp: return $this->fetchAllProperties( $request ); } // This should really never happen, because the request class itself // should have ensured, that on of those options is set. Untestable. return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_500 ); } /** * Serves PROPPATCH requests. * * The method receives a {@link ezcWebdavPropPatchRequest} object * containing all relevant information obout the clients request and will * return an instance of {@link ezcWebdavErrorResponse} on error or a * {@link ezcWebdavPropPatchResponse} response on success. If the * referenced resource is a collection or if only some properties produced * errors, an instance of {@link ezcWebdavMultistatusResponse} may be * returned. * * @param ezcWebdavPropPatchRequest $request * @return ezcWebdavResponse */ public function propPatch( ezcWebdavPropPatchRequest $request ) { $source = $request->requestUri; // Check authorization // Need to do this before checking of node existence is checked, to // avoid leaking information if ( !ezcWebdavServer::getInstance()->isAuthorized( $source, $request->getHeader( 'Authorization' ), ezcWebdavAuthorizer::ACCESS_WRITE ) ) { return $this->createUnauthorizedResponse( $source, $request->getHeader( 'Authorization' ) ); } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } // Store proeprties, to be able to revert all changes later $propertyBackup = clone $this->getAllProperties( $source ); $errors = array( ezcWebdavResponse::STATUS_403 => new ezcWebdavBasicPropertyStorage(), ezcWebdavResponse::STATUS_409 => new ezcWebdavBasicPropertyStorage(), ezcWebdavResponse::STATUS_424 => new ezcWebdavBasicPropertyStorage(), ); $errnous = false; // Update properties, like requested foreach ( $request->updates as $property ) { // If there already has been some error, issue failed // dependency errors for everything else. if ( $errnous ) { $errors[ezcWebdavResponse::STATUS_424]->attach( $property ); continue; } // Check for property validation errors and add a 409 for this. if ( $property->hasError ) { $errors[ezcWebdavResponse::STATUS_409]->attach( $property ); $errnous = true; continue; } switch ( $request->updates->getFlag( $property->name, $property->namespace ) ) { case ezcWebdavPropPatchRequest::REMOVE: if ( !$this->removeProperty( $source, $property ) ) { // If update failed, we assume the access has been denied. $errors[ezcWebdavResponse::STATUS_403]->attach( $property ); $errnous = true; } break; case ezcWebdavPropPatchRequest::SET: if ( !$this->setProperty( $source, $property ) ) { // If update failed, we assume the access has been denied. // // @todo: This assumptions is not particular correct. // In case of live properties, which were tried to // update a 409 error would be correct. $errors[ezcWebdavResponse::STATUS_403]->attach( $property ); $errnous = true; } break; default: // This may happen, when a broken flag has been assigned // during request generation. This SHOULD never happen. $this->resetProperties( $source, $propertyBackup ); return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_500 ); } } // Create node from source for response if ( $this->isCollection( $source ) ) { $node = new ezcWebdavCollection( $source ); } else { $node = new ezcWebdavResource( $source ); } if ( $errnous ) { // Revert all changes $this->resetProperties( $source, $propertyBackup ); // Create response return new ezcWebdavMultistatusResponse( new ezcWebdavPropPatchResponse( $node, new ezcWebdavPropStatResponse( $errors[ezcWebdavResponse::STATUS_403], ezcWebdavResponse::STATUS_403 ), new ezcWebdavPropStatResponse( $errors[ezcWebdavResponse::STATUS_409], ezcWebdavResponse::STATUS_409 ), new ezcWebdavPropStatResponse( $errors[ezcWebdavResponse::STATUS_424], ezcWebdavResponse::STATUS_424 ) ) ); } // Verify If-[None-]Match headers. // Done in this place to ensure that PROPPATCH would succeed otherwise. // Reset of properties to orgiginal state is performed if ETag check // fails. if ( ( $res = $this->checkIfMatchHeaders( $request, $source ) ) !== null ) { $this->resetProperties( $source, $propertyBackup ); return $res; } return new ezcWebdavPropPatchResponse( $node ); } /** * Serves PUT requests. * * The method receives a {@link ezcWebdavPutRequest} objects containing all * relevant information obout the clients request and will return an * instance of {@link ezcWebdavErrorResponse} on error or {@link * ezcWebdavPutResponse} on success. * * @param ezcWebdavPutRequest $request * @return ezcWebdavResponse */ public function put( ezcWebdavPutRequest $request ) { $source = $request->requestUri; // Check authorization // Need to do this before checking of node existence is checked, to // avoid leaking information if ( !ezcWebdavServer::getInstance()->isAuthorized( $source, $request->getHeader( 'Authorization' ), ezcWebdavAuthorizer::ACCESS_WRITE ) ) { return $this->createUnauthorizedResponse( $source, $request->getHeader( 'Authorization' ) ); } // Check if parent node exists and throw a 409 otherwise if ( !$this->nodeExists( dirname( $source ) ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_409, $source ); } // Check if parent node is a collection, and throw a 409 otherwise if ( !$this->isCollection( dirname( $source ) ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_409, $source ); } // Check if resource to be updated or created does not exists already // AND is a collection if ( $this->nodeExists( $source ) && $this->isCollection( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_409, $source ); } // @todo: RFC2616 Section 9.6 PUT requires us to send 501 on all // Content-* we don't support. // Verify If-[None-]Match headers if ( $this->nodeExists( $source ) && ( $res = $this->checkIfMatchHeaders( $request, $source ) ) !== null ) { return $res; } // Everything is OK, create or update resource. if ( !$this->nodeExists( $source ) ) { $this->createResource( $source ); } $this->setResourceContents( $source, $request->body ); $res = new ezcWebdavPutResponse( $source ); // Add ETag header $res->setHeader( 'ETag', $this->getETag( $source ) ); // Deliver response return $res; } /** * Serves DELETE requests. * * The method receives a {@link ezcWebdavDeleteRequest} objects containing * all relevant information obout the clients request and will return an * instance of {@link ezcWebdavErrorResponse} on error or {@link * ezcWebdavDeleteResponse} on success. * * @param ezcWebdavDeleteRequest $request * @return ezcWebdavResponse */ public function delete( ezcWebdavDeleteRequest $request ) { $source = $request->requestUri; // Check authorization // Need to do this before checking of node existence is checked, to // avoid leaking information $authState = $this->recursiveAuthCheck( $request, $source, ezcWebdavAuthorizer::ACCESS_WRITE, true ); if ( count( $authState['errors'] ) !== 0 ) { return $authState['errors'][0]; } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } // Verify If-[None-]Match headers // @todo: Does this make sense for PROPFIND requests? $res = $this->checkIfMatchHeadersRecursive( $request, $source, ezcWebdavRequest::DEPTH_INFINITY ); if ( $res !== null ) { return $res; } // Delete $deletion = $this->performDelete( $source ); if ( $deletion !== null ) { // ezcWebdavMultistatusResponse return $deletion; } // Send proper response on success return new ezcWebdavDeleteResponse( $source ); } /** * Serves COPY requests. * * The method receives a {@link ezcWebdavCopyRequest} objects containing * all relevant information obout the clients request and will return an * instance of {@link ezcWebdavErrorResponse} on error or {@link * ezcWebdavCopyResponse} on success. If only some operations failed, this * method may return an instance of {@link ezcWebdavMultistatusResponse}. * * @param ezcWebdavCopyRequest $request * @return ezcWebdavResponse */ public function copy( ezcWebdavCopyRequest $request ) { // Indicates wheather a destiantion resource has been replaced or not. // The success response code depends on this. $replaced = false; // Extract paths from request $source = $request->requestUri; $dest = $request->getHeader( 'Destination' ); // Check authorization // Need to do this before checking of node existence is checked, to // avoid leaking information if ( !ezcWebdavServer::getInstance()->isAuthorized( $source, $request->getHeader( 'Authorization' ) ) ) { return $this->createUnauthorizedResponse( $source, $request->getHeader( 'Authorization' ) ); } if ( !ezcWebdavServer::getInstance()->isAuthorized( $dest, $request->getHeader( 'Authorization' ), ezcWebdavAuthorizer::ACCESS_WRITE ) ) { return $this->createUnauthorizedResponse( $dest, $request->getHeader( 'Authorization' ) ); } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } // If source and destination are equal, the request should always fail. if ( $source === $dest ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_403, $source ); } // Check if destination resource exists and throw error, when // overwrite header is F if ( ( $request->getHeader( 'Overwrite' ) === 'F' ) && $this->nodeExists( $dest ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_412, $dest ); } // Check if the destination parent directory already exists, otherwise // bail out. if ( !$this->nodeExists( $destDir = dirname( $dest ) ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_409, $dest ); } // Verify If-[None-]Match headers on the $source $res = $this->checkIfMatchHeadersRecursive( $request, $source, $request->getHeader( 'Depth' ) ); if ( $res !== null ) { return $res; } // Verify If-[None-]Match headers on the $dest if it exists if ( $this->nodeExists( $dest ) && ( $res = $this->checkIfMatchHeaders( $request, $dest ) ) !== null ) { return $res; } // Verify If-[None-]Match headers on the on $dests parent dir, if it // does not exist elseif ( ( $res = $this->checkIfMatchHeaders( $request, $destDir ) ) !== null ) { return $res; } // The destination resource should be deleted if it exists and the // overwrite headers is T if ( ( $request->getHeader( 'Overwrite' ) === 'T' ) && $this->nodeExists( $dest ) ) { // Check sub-sequent authorization on destination $authState = $this->recursiveAuthCheck( $request, $dest, ezcWebdavAuthorizer::ACCESS_WRITE, true ); if ( count( $authState['errors'] ) !== 0 ) { // Permission denied on deleting destination return $authState['errors'][0]; } // Perform delete // @todo: This method might return errors. If it does, the delete // was not successful and therefore no copy should happen! (see: // move()). $replaced = true; $this->performDelete( $dest ); } $errors = array(); $copyPaths = array(); if ( $request->getHeader( 'Depth' ) === ezcWebdavRequest::DEPTH_INFINITY ) { $authState = $this->recursiveAuthCheck( $request, $source ); $errors = $authState['errors']; $copyPaths = $authState['paths']; } else { // Non recursive auth check necessary, plain check on $source // already performed $copyPaths = array( $source => ezcWebdavRequest::DEPTH_ZERO ); } // Recursively copy paths that should be copied foreach ( $copyPaths as $copySource => $copyDepth ) { // Build destination path fur descendants $copyDest = $dest . (string) substr( $copySource, strlen( $source ) ); // Perform copy and collect additional errors. $errors = array_merge( $errors, // @todo: handle keepalive setting somehow - even the RFC is quite // vague how to handle them exactly. $this->performCopy( $copySource, $copyDest, $copyDepth ) ); } if ( !count( $errors ) ) { // No errors occured during copy. Just response with success. return new ezcWebdavCopyResponse( $replaced ); } // Send proper response on success return new ezcWebdavMultistatusResponse( $errors ); } /** * Serves MOVE requests. * * The method receives a {@link ezcWebdavMoveRequest} objects containing * all relevant information obout the clients request and will return an * instance of {@link ezcWebdavErrorResponse} on error or {@link * ezcWebdavMoveResponse} on success. If only some operations failed, this * method may return an instance of {@link ezcWebdavMultistatusResponse}. * * @param ezcWebdavMoveRequest $request * @return ezcWebdavResponse */ public function move( ezcWebdavMoveRequest $request ) { // Indicates wheather a destiantion resource has been replaced or not. // The success response code depends on this. $replaced = false; // Extract paths from request $source = $request->requestUri; $dest = $request->getHeader( 'Destination' ); // Check authorization // Need to do this before checking of node existence is checked, to // avoid leaking information $authState = $this->recursiveAuthCheck( $request, $dest, ezcWebdavAuthorizer::ACCESS_WRITE, true ); if ( count( $authState['errors'] ) !== 0 ) { // Source permission denied return $authState['errors'][0]; } if ( !ezcWebdavServer::getInstance()->isAuthorized( $dest, $request->getHeader( 'Authorization' ), ezcWebdavAuthorizer::ACCESS_WRITE ) ) { return $this->createUnauthorizedResponse( $dest, $request->getHeader( 'Authorization' ) ); } // Check if resource is available if ( !$this->nodeExists( $source ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_404, $source ); } // If source and destination are equal, the request should always fail. if ( $source === $dest ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_403, $source ); } // Check if destination resource exists and throw error, when // overwrite header is F if ( ( $request->getHeader( 'Overwrite' ) === 'F' ) && $this->nodeExists( $dest ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_412, $dest ); } // Check if the destination parent directory already exists, otherwise // bail out. if ( !$this->nodeExists( $destDir = dirname( $dest ) ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_409, $dest ); } // Verify If-[None-]Match headers on the $source $res = $this->checkIfMatchHeadersRecursive( $request, $source, // We move, not copy! ezcWebdavRequest::DEPTH_INFINITY ); if ( $res !== null ) { return $res; } // Verify If-[None-]Match headers on the $dest if it exists if ( $this->nodeExists( $dest ) && ( $res = $this->checkIfMatchHeaders( $request, $dest ) ) !== null ) { return $res; } // Verify If-[None-]Match headers on the on $dests parent dir, if it // does not exist elseif ( ( $res = $this->checkIfMatchHeaders( $request, $destDir ) ) !== null ) { return $res; } // The destination resource should be deleted if it exists and the // overwrite headers is T if ( ( $request->getHeader( 'Overwrite' ) === 'T' ) && $this->nodeExists( $dest ) ) { // Check sub-sequent authorization on destination $authState = $this->recursiveAuthCheck( $request, $dest, ezcWebdavAuthorizer::ACCESS_WRITE, true ); if ( count( $authState['errors'] ) !== 0 ) { // Permission denied on deleting destination return $authState['errors'][0]; } $replaced = true; if ( count( $delteErrors = $this->performDelete( $dest ) ) > 0 ) { return new ezcWebdavMultistatusResponse( $delteErrors ); } } // All checks are passed, we can actuall copy now. // // MOVEd contents should always be copied using infinity depth. // // @todo: handle keepalive setting somehow - even the RFC is quite // vague how to handle them exactly. $errors = $this->performCopy( $source, $dest, ezcWebdavRequest::DEPTH_INFINITY ); // If an error occured we skip deletion of source. // // @IMPORTANT: This is a definition / assumption made by us, because it // is not defined in the RFC how to handle such a case. if ( count( $errors ) ) { // We need a multistatus response, because some errors occured for some // of the resources. return new ezcWebdavMultistatusResponse( $errors ); } // Delete the source, COPY has been successful $deletion = $this->performDelete( $source ); // If deletion failed, this has again been caused by the automatic // error causing facilities of the backend. Send 423 by choice. // // @todo: The error generated here should depend on the actual backend // implementation and not be generated guessing what may fit. if ( count( $deletion ) > 0 ) { return new ezcWebdavMultistatusResponse( $deletion ); } // Send proper response on success return new ezcWebdavMoveResponse( $replaced ); } /** * Serves MKCOL (make collection) requests. * * The method receives a {@link ezcWebdavMakeCollectionRequest} objects * containing all relevant information obout the clients request and will * return an instance of {@link ezcWebdavErrorResponse} on error or {@link * ezcWebdavMakeCollectionResponse} on success. * * @param ezcWebdavMakeCollectionRequest $request * @return ezcWebdavResponse */ public function makeCollection( ezcWebdavMakeCollectionRequest $request ) { $collection = $request->requestUri; // Check authorization // Need to do this before checking of node existence is checked, to // avoid leaking information if ( !ezcWebdavServer::getInstance()->isAuthorized( $collection, $request->getHeader( 'Authorization' ), ezcWebdavAuthorizer::ACCESS_WRITE ) ) { return $this->createUnauthorizedResponse( $collection, $request->getHeader( 'Authorization' ) ); } // If resource already exists, the collection cannot be created and a // 405 is thrown. if ( $this->nodeExists( $collection ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_405, $collection ); } // Check if the parent node already exists, otherwise throw a 409 // error. if ( !$this->nodeExists( dirname( $collection ) ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_409, $collection ); } // If the parent node exists, but is a resource, which obviously can // not accept any members, throw a 403 error. if ( !$this->isCollection( $destDir = dirname( $collection ) ) ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_403, $collection ); } // Verify If-[None-]Match headers on the on $dests parent dir if ( ( $res = $this->checkIfMatchHeaders( $request, $destDir ) ) !== null ) { return $res; } // As the handling of request bodies is not described in RFC 2518, we // skip their handling and always return a 415 error. if ( $request->body ) { return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_415, $collection ); } // Cause error, if requested? // All checks passed, we can create the collection $this->createCollection( $collection ); // Return success return new ezcWebdavMakeCollectionResponse( $collection ); } /** * Handles the OPTIONS request. * * Applies authorization checking to the OPTIONS request and returns the * parent response. * * @param ezcWebdavOptionsRequest $request * @return ezcWebdavOptionsResponse */ public function options( ezcWebdavOptionsRequest $request ) { // Check authorization if ( !ezcWebdavServer::getInstance()->isAuthorized( $request->requestUri, $request->getHeader( 'Authorization' ) ) ) { return $this->createUnauthorizedResponse( $request->requestUri, $request->getHeader( 'Authorization' ) ); } return parent::options( $request ); } /** * Returns the etag representing the current state of $path. * * Calculates and returns the ETag for the resource represented by $path. * The ETag is calculated from the $path itself and the following * properties, which are concatenated and md5 hashed: * *
* array(
* 'errors' => array(
* ezcWebdavErrorResponse(),
* ezcWebdavErrorResponse(),
* // ...
* )
* 'paths' => array(
* '/some/path' => ezcWebdavRequest::DEPTH_INFINITY,
* '/some/other/path' => ezcWebdavRequest::DEPTH_ZERO,
* // ...
* )
* )
*
*
* The 'errors' key is assigned to an array of authorization error
* responses that will be merged to the ezcWebdavMultistatusResponse
* returned by the copy() method. The 'paths' array contains all paths that
* may be copied by the method. A path is assigned to the depth that it
* might be copied. The depth can be {@link
* ezcWebdavRequest::DEPTH_INFINITY} to indicate that a complete sub tree
* is save for copying, or {@link ezcWebdavRequest::DEPTH_ZERO}, to
* indicate that only the path itself may be copied, but none of its
* descendants.
*
* The $access parameter specifies which permission is to be checked {@link
* ezcWebdavAuthorizer::ACCESS_READ} is the default, {@link
* ezcWebdavAuthorizer::ACCESS_WRITE} may be set to indicate write
* permissions.
*
* If the $breakOnError parameter is set to true, no further checks will be
* applied to sibling resources, but the method will instantly return. This
* parameter is set to true for the MOVE request, since this request must
* be processed completly or not at all. The COPY request in contrast may
* also be processed partially, so this parameter is left as is.
*
* @param ezcWebdavRequest $request
* @param string $path
* @param int $access
* @param bool $breakOnError
* @return array
*
* @todo Mark protected as soon as API is final.
*/
private function recursiveAuthCheck( ezcWebdavRequest $request, $path, $access = ezcWebdavAuthorizer::ACCESS_WRITE, $breakOnError = false )
{
$result = array(
'errors' => array(),
'paths' => array(),
);
// Check auth for collections and resources equally
if ( !ezcWebdavServer::getInstance()->isAuthorized( $path, $request->getHeader( 'Authorization' ), $access ) )
{
$result['errors'][] = $this->createUnauthorizedResponse(
$path,
$request->getHeader( 'Authorization' )
);
}
else
{
if ( $this->isCollection( $path ) )
{
foreach ( $this->getCollectionMembers( $path ) as $member )
{
$tmpRes = $this->recursiveAuthCheck( $request, $member->path, $access );
if ( count( $tmpRes['errors'] ) !== 0 )
{
if ( $breakOnError )
{
return $tmpRes;
}
$result['errors'] = array_merge( $result['errors'], $tmpRes['errors'] );
$result['paths'] = array_merge( $result['paths'], $tmpRes['paths'] );
}
}
$result['paths'][$path] = ( count( $result['errors'] ) ? ezcWebdavRequest::DEPTH_ZERO : ezcWebdavRequest::DEPTH_INFINITY );
}
else
{
// Only a resource, so depth infinity does not make sense
$result['paths'][$path] = ezcWebdavRequest::DEPTH_ZERO;
}
}
return $result;
}
/**
* Returns an error response to indicate failed authorization.
*
* This method returns an instance of {@link ezcWebdavErrorResponse} with a
* corresponding status code, indicating that the request to $path was not
* authorized. In case the user did not provide authentication at all, the
* status code 401 (Unauthorized) is used to give the possibility of
* authenticating. Otherwise 403 (Forbidden) is used, since the
* authenticated user simply does not have access.
*
* @param string $path
* @param ezcWebdavAuthBasic|ezcWebdavAúthDigest $authHeader
* @return ezcWebdavErrorResponse
*/
private function createUnauthorizedResponse( $path, $authHeader = null )
{
// Check for anonymous auth
if ( $authHeader === null || $authHeader->username === '' )
{
return ezcWebdavServer::getInstance()->createUnauthenticatedResponse(
$path,
'Authorization failed.'
);
}
// Authenticated user does not have access
return ezcWebdavServer::getInstance()->createUnauthorizedResponse(
$path,
'Authorization failed.'
);
}
}
?>