/** * Handles responses to the PROPFIND request. * * Checks if lock related properties were requested in the $request. If * this is the case, $responses will be manipulated accordingly: Requested * properties which are not handled by the backend are added to the 200 * status storage and removed from the 404 status storage. * * @param ezcWebdavResponse $response ezcWebdavMultistatusResponse * @return null */ public function generatedResponse(ezcWebdavResponse $response) { if (!$response instanceof ezcWebdavMultistatusResponse) { return; } // Cleanup and enhance all PROPSTAT responses foreach ($response->responses as $propFindRes) { $status200Storage = null; $status404Storage = null; // Collect property storages foreach ($propFindRes->responses as $propStatResponse) { if ($propStatResponse->status === ezcWebdavResponse::STATUS_200) { $status200Storage = $propStatResponse->storage; } if ($propStatResponse->status === ezcWebdavResponse::STATUS_404) { $status404Storage = $propStatResponse->storage; } } if ($status404Storage !== null || $this->request->allProp || $this->request->propName) { if ($this->request->allProp || $this->request->propName || $status404Storage->contains('lockdiscovery')) { if ($status200Storage === null) { $status200Storage = new ezcWebdavBasicPropertyStorage(); $responses = $propFindRes->responses; $responses[] = new ezcWebdavPropStatResponse($status200Storage); $propFindRes->responses = $responses; } $status200Storage->attach(new ezcWebdavLockDiscoveryProperty()); if ($status404Storage !== null) { $status404Storage->detach('lockdiscovery'); } } if ($this->request->allProp || $this->request->propName || $status404Storage->contains('supportedlock')) { if ($status200Storage === null) { $status200Storage = new ezcWebdavBasicPropertyStorage(); $responses = $propFindRes->responses; $responses[] = new ezcWebdavPropStatResponse($status200Storage); $propFindRes->responses = $responses; } $supportedLock = new ezcWebdavSupportedLockProperty(new ArrayObject($this->request->propName ? array() : array(new ezcWebdavSupportedLockPropertyLockentry(ezcWebdavLockRequest::TYPE_WRITE, ezcWebdavLockRequest::SCOPE_EXCLUSIVE), new ezcWebdavSupportedLockPropertyLockentry(ezcWebdavLockRequest::TYPE_WRITE, ezcWebdavLockRequest::SCOPE_SHARED)))); $status200Storage->attach($supportedLock); if ($status404Storage !== null) { $status404Storage->detach('supportedlock'); } } } if (count($status404Storage) === 0) { $responses = $propFindRes->responses; foreach ($responses as $id => $propStatRes) { if ($propStatRes->status === ezcWebdavResponse::STATUS_404) { unset($responses[$id]); } } $propFindRes->responses = $responses; } } }
/** * Return an initial set of properties for resources and collections. * * The second parameter indicates wheather the given resource is a * collection. The returned properties are used to initialize the property * arrays for the given content. * * @param string $name * @param bool $isCollection * @return array * * @access protected */ public function initializeProperties($name, $isCollection = false) { if ($this->fakeLiveProperties) { $propertyStorage = new ezcWebdavBasicPropertyStorage(); // Add default creation date $propertyStorage->attach(new ezcWebdavCreationDateProperty(new ezcWebdavDateTime('@1054034820'))); // Define default display name $propertyStorage->attach(new ezcWebdavDisplayNameProperty(basename(urldecode($name)))); // Define default language $propertyStorage->attach(new ezcWebdavGetContentLanguageProperty(array('en'))); // Define default content type $propertyStorage->attach(new ezcWebdavGetContentTypeProperty($isCollection ? 'httpd/unix-directory' : 'application/octet-stream')); // Define default ETag $propertyStorage->attach(new ezcWebdavGetEtagProperty($this->getETag($name))); // Define default modification time $propertyStorage->attach(new ezcWebdavGetLastModifiedProperty(new ezcWebdavDateTime('@1124118780'))); // Define content length if node is a resource. $propertyStorage->attach(new ezcWebdavGetContentLengthProperty($isCollection ? ezcWebdavGetContentLengthProperty::COLLECTION : (string) strlen($this->content[$name]))); $propertyStorage->attach(new ezcWebdavResourceTypeProperty($isCollection === true ? ezcWebdavResourceTypeProperty::TYPE_COLLECTION : ezcWebdavResourceTypeProperty::TYPE_RESOURCE)); } else { $propertyStorage = new ezcWebdavBasicPropertyStorage(); } return $propertyStorage; }
/** * Manually sets a property on a resource. * * Sets the given $propertyBackup for the resource identified by $path. * * @param string $path * @param ezcWebdavProperty $property * @return bool */ public function setProperty($path, ezcWebdavProperty $property) { if (!in_array($property->name, $this->handledLiveProperties, true)) { return false; } // @as @todo implement setting properties // @todo implement locking and unlocking based on the code // lock: // replace 30607 with your object ID // $object = eZContentObject::fetch( 30607 ); // $stateGroup = eZContentObjectStateGroup::fetchByIdentifier( 'ez_lock' ); // $state = eZContentObjectState::fetchByIdentifier( 'locked', $stateGroup->attribute( 'id' ) ); // $object->assignState( $state ); // unlock: // $state = eZContentObjectState::fetchByIdentifier( 'not_locked', $stateGroup->attribute( 'id' ) ); // $object->assignState( $state ); // Get namespace property storage $storage = new ezcWebdavBasicPropertyStorage(); // Attach property to store $storage->attach($property); // Store document back $this->storeProperties($path, $storage); return true; }
/** * Intersects between two property storages. * * Calculate and return an instance of {@link * ezcWebdavBasicPropertyStorage} which contains the intersection of two * property storages. This means a new property storage will be return * which contains all values, which are present in the current and the * given $properties property storage. * * @param ezcWebdavPropertyStorage $properties * @return ezcWebdavBasicPropertyStorage */ public function intersect(ezcWebdavPropertyStorage $properties) { $foreign = $properties->getAllProperties(); $intersection = new ezcWebdavBasicPropertyStorage(); foreach ($this->properties as $namespace => $properties) { foreach ($properties as $name => $property) { if (isset($foreign[$namespace][$name])) { // Only add properties to new property storage, which could // be found in both property storages. $intersection->attach($property); } } } return $intersection; }
/** * Returns extracted properties in an ezcWebdavPropertyStorage. * * This method receives a DOMNodeList $domNodes which must contain a set * of DOMElement objects, while each of those represents a WebDAV property. * * The list may contain live properties as well as dead ones. Live * properties ({@link ezcWebdavLiveProperty}) as defined in RFC 2518 are * currently recognized, except for locking related properties. All other * properties in the DAV: namespace are added as dead properties ({@link * ezcWebdavDeadProperty}). Dead properties are parsed generally in any * namespace. * * The extracted properties are stored in the given {@link * ezcWebdavPropertyStorage} $storage. If a $flag value is provided, this * one is submitted as the second parameter to {@link * ezcWebdavFlaggedPropertyStorage->attach()}. * * @param DOMNodeList $domNodes * @param ezcWebdavBasicPropertyStorage $storage * @param int $flag * @return ezcWebdavBasicPropertyStorage */ public final function extractProperties(DOMNodeList $domNodes, ezcWebdavBasicPropertyStorage $storage, $flag = null) { for ($i = 0; $i < $domNodes->length; ++$i) { $currentNode = $domNodes->item($i); if ($currentNode->nodeType !== XML_ELEMENT_NODE) { // Skip continue; } // Initialize $property = null; // DAV: namespace indicates live property! If parsing live fails, a dead property is returned if ($currentNode->namespaceURI === ezcWebdavXmlTool::XML_DEFAULT_NAMESPACE) { $property = $this->dispatchExtractLiveProperty($currentNode); } else { $property = $this->dispatchExtractDeadProperty($currentNode); } $flag === null ? $storage->attach($property) : $storage->attach($property, $flag); } return $storage; }
public function testDeadPropertyRetrieval() { $backend = new ezcWebdavFileBackend($this->tempDir . 'backend/'); $backend->options->useMimeExts = false; $newProperties = new ezcWebdavFlaggedPropertyStorage(); $newProperties->attach($prop = new ezcWebdavDeadProperty('foo:', 'bar', "<?xml version=\"1.0\"?>\n<bar xmlns=\"foo:\">some content</bar>\n"), ezcWebdavPropPatchRequest::SET); $request = new ezcWebdavPropPatchRequest('/resource'); $request->updates = $newProperties; $request->validateHeaders(); $response = $backend->proppatch($request); $this->assertTrue(is_file($this->tempDir . 'backend/.ezc/resource.xml'), 'Expected creation of property storage.'); $request = new ezcWebdavPropFindRequest('/resource'); $request->prop = $newProperties; $request->validateHeaders(); $response = $backend->propfind($request); $responseProperty = new ezcWebdavBasicPropertyStorage(); $responseProperty->attach($prop); $responseProperty->rewind(); $expectedResponse = new ezcWebdavMultistatusResponse(new ezcWebdavPropFindResponse(new ezcWebdavResource('/resource'), new ezcWebdavPropStatResponse($responseProperty))); $this->assertEquals($expectedResponse, $response, 'Expected response does not match real response.', 0, 20); $this->assertEquals(new ezcWebdavDeadProperty('foo:', 'bar', "<?xml version=\"1.0\"?>\n<bar xmlns=\"foo:\">some content</bar>\n"), $backend->getProperty('/resource', 'bar', 'foo:')); }
public function testPropPatchCombinedSetDeleteValidationError() { $backend = new ezcWebdavMemoryBackend(true); $backend->addContents(array('foo' => 'bar', 'bar' => array('blubb' => 'Somme blubb blubbs.'))); // First add some custom properties. $newProperties = new ezcWebdavFlaggedPropertyStorage(); $newProperties->attach($p_bar = new ezcWebdavDeadProperty('foo:', 'bar', 'some content'), ezcWebdavPropPatchRequest::SET); $newProperties->attach($p_blubb = new ezcWebdavDeadProperty('foo:', 'blubb', 'some other content'), ezcWebdavPropPatchRequest::SET); $request = new ezcWebdavPropPatchRequest('/foo'); $request->updates = $newProperties; $request->validateHeaders(); $response = $backend->proppatch($request); $resProps = new ezcWebdavBasicPropertyStorage(); $resProps->attach(new ezcWebdavDeadProperty('foo:', 'bar')); $resProps->attach(new ezcWebdavDeadProperty('foo:', 'blubb')); $this->assertEquals(new ezcWebdavPropPatchResponse(new ezcWebdavResource('/foo'), new ezcWebdavPropStatResponse($resProps)), $response, 'Expected property adding PROPPATCH response does not match real response.', 0, 20); // Then remove them again, with one live property in the middle to // check for proper failed dependency response codes. $updateProperties = new ezcWebdavFlaggedPropertyStorage(); $updateProperties->attach($p_blubb, ezcWebdavPropPatchRequest::REMOVE); $updateProperties->attach($p_length = new ezcWebdavGetContentLengthProperty(), ezcWebdavPropPatchRequest::REMOVE); // Cause validation error $p_length->length = 'not a number'; $updateProperties->attach($p_foo = new ezcWebdavDeadProperty('foo:', 'foo', 'random content'), ezcWebdavPropPatchRequest::SET); $updateProperties->attach($p_bar, ezcWebdavPropPatchRequest::REMOVE); $request = new ezcWebdavPropPatchRequest('/foo'); $request->updates = $updateProperties; $request->validateHeaders(); $response = $backend->proppatch($request); $failed = new ezcWebdavBasicPropertyStorage(); $failed->attach($p_length); $depError = new ezcWebdavBasicPropertyStorage(); $depError->attach($p_foo); $depError->attach($p_bar); $failed->rewind(); $depError->rewind(); $this->assertEquals(new ezcWebdavMultistatusResponse(new ezcWebdavPropPatchResponse(new ezcWebdavResource('/foo'), new ezcWebdavPropStatResponse(new ezcWebdavBasicPropertyStorage(), ezcWebdavResponse::STATUS_403), new ezcWebdavPropStatResponse($failed, ezcWebdavResponse::STATUS_409), new ezcWebdavPropStatResponse($depError, ezcWebdavResponse::STATUS_424))), $response, 'Expected property removing PROPPATCH response does not match real response.', 0, 20); }
/** * 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; } $successProps = new ezcWebdavBasicPropertyStorage(); foreach ($request->updates as $updatedProperty) { $successProp = clone $updatedProperty; $successProp->clear(); $successProps->attach($successProp); } // RFC update requires multi-status even if everything worked properly return new ezcWebdavPropPatchResponse($node, new ezcWebdavPropStatResponse($successProps)); }
/** * 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); }
public function testPropPatchCombinedSetDeleteFail() { $backend = new ezcWebdavFileBackend($this->tempDir . 'backend/'); // First add some custom properties. $newProperties = new ezcWebdavFlaggedPropertyStorage(); $newProperties->attach($p_bar = new ezcWebdavDeadProperty('foo:', 'bar', "<?xml version=\"1.0\"?>\n<bar xmlns=\"foo:\">some content</bar>\n"), ezcWebdavPropPatchRequest::SET); $newProperties->attach($p_blubb = new ezcWebdavDeadProperty('foo:', 'blubb', "<?xml version=\"1.0\"?>\n<blubb xmlns=\"foo:\">some other content</blubb>\n"), ezcWebdavPropPatchRequest::SET); $request = new ezcWebdavPropPatchRequest('/resource'); $request->updates = $newProperties; $request->validateHeaders(); $response = $backend->proppatch($request); $this->assertEquals(new ezcWebdavPropPatchResponse(new ezcWebdavResource('/resource')), $response, 'Expected property adding PROPPATCH response does not match real response.', 0, 20); // Then remove them again, with one live property in the middle to // check for proper failed dependency response codes. $updateProperties = new ezcWebdavFlaggedPropertyStorage(); $updateProperties->attach($p_blubb, ezcWebdavPropPatchRequest::REMOVE); $updateProperties->attach($p_length = new ezcWebdavGetContentLengthProperty(), ezcWebdavPropPatchRequest::REMOVE); $updateProperties->attach($p_foo = new ezcWebdavDeadProperty('foo:', 'foo', "<?xml version=\"1.0\"?>\n<foo xmlns=\"foo:\">random content</foo>\n"), ezcWebdavPropPatchRequest::SET); $updateProperties->attach($p_bar, ezcWebdavPropPatchRequest::REMOVE); $request = new ezcWebdavPropPatchRequest('/resource'); $request->updates = $updateProperties; $request->validateHeaders(); $response = $backend->proppatch($request); $failed = new ezcWebdavBasicPropertyStorage(); $failed->attach($p_length); $depError = new ezcWebdavBasicPropertyStorage(); $depError->attach($p_foo); $depError->attach($p_bar); $failed->rewind(); $depError->rewind(); $this->assertEquals(new ezcWebdavMultistatusResponse(new ezcWebdavPropPatchResponse(new ezcWebdavResource('/resource'), new ezcWebdavPropStatResponse($failed, ezcWebdavResponse::STATUS_403), new ezcWebdavPropStatResponse(new ezcWebdavBasicPropertyStorage(), ezcWebdavResponse::STATUS_409), new ezcWebdavPropStatResponse($depError, ezcWebdavResponse::STATUS_424))), $response, 'Expected property removing PROPPATCH response does not match real response.', 0, 20); }
public function testProppatchResourceWithValidETag() { $testPath = '/collection/test.txt'; $backend = new ezcWebdavFileBackend($this->tempDir . 'backend/'); $etag = $backend->getProperty($testPath, 'getetag')->etag; // Properties to patch $newProperties = new ezcWebdavFlaggedPropertyStorage(); $newProperties->attach($p1 = new ezcWebdavGetContentTypeProperty('text/xml'), ezcWebdavPropPatchRequest::SET); $newProperties->attach($p2 = new ezcWebdavDeadProperty('foo:', 'bar', "<?xml version=\"1.0\"?>\n<bar xmlns=\"foo:\">some content</bar>\n"), ezcWebdavPropPatchRequest::SET); $req = new ezcWebdavProppatchRequest($testPath); $req->updates = $newProperties; $req->setHeader('If-Match', array('abc23', $etag, 'foobar')); $req->validateHeaders(); $resProps = new ezcWebdavBasicPropertyStorage(); $resProps->attach(new ezcWebdavGetContentTypeProperty()); $resProps->attach(new ezcWebdavDeadProperty('foo:', 'bar')); $res = $backend->propPatch($req); $this->assertEquals(new ezcWebdavPropPatchResponse(new ezcWebdavResource($testPath), new ezcWebdavPropStatResponse($resProps)), $res, 'Expected response does not match real response.', 0, 20); }
public function testIteratorDetachedAll() { $storage = new ezcWebdavBasicPropertyStorage(); $storage->attach(new ezcWebdavLockDiscoveryProperty()); $storage->attach(new ezcWebdavDeadProperty('http://example.com/some/property', 'some', 'foobar')); $storage->detach('lockdiscovery'); $storage->detach('some', 'http://example.com/some/property'); foreach ($storage as $property) { $this->fail("Property {$property->namespace} -- {$property->name} not detached!"); } // All right, if no error occurs }