/** * Performs actual purging of locks. * * Iterates over {@link $lockProperties} and purges all locks of which the * tokens have been collected in {$locksToPurge}. * * @return void * * @throws ezcWebdavLockAdministrationException * in case purging of a lock failed. */ protected function performPurge() { foreach ($this->lockProperties as $path => $lockDiscoveryProp) { $removeIds = array(); foreach ($lockDiscoveryProp->activeLock as $id => $activeLock) { if (isset($this->locksToPurge[(string) $activeLock->token])) { $removeIds[] = $id; } } if ($removeIds !== array()) { foreach ($removeIds as $id) { $lockDiscoveryProp->activeLock->offsetUnset($id); } $propPatchReq = new ezcWebdavPropPatchRequest($path); $propPatchReq->updates = new ezcWebdavFlaggedPropertyStorage(); $propPatchReq->updates->attach($lockDiscoveryProp, ezcWebdavPropPatchRequest::SET); $propPatchReq->validateHeaders(); $propPatchRes = $this->backend->propPatch($propPatchReq); if (!$propPatchRes instanceof ezcWebdavPropPatchResponse) { throw new ezcWebdavLockAdministrationException("PROPPATCH to remove timedout lock failed for '{$path}'.", $propPatchRes); } } } }
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); }
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:')); }
/** * 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)); }
/** * Parses the PROPPATCH request and returns a request object. * * This method is responsible for parsing the PROPPATCH request. It * retrieves the current request URI in $path and the request body as * $body. The return value, if no exception is thrown, is a valid {@link * ezcWebdavPropPatchRequest} object. * * This method may be overwritten to adjust it to special client behaviour. * * @param string $path * @param string $body * @return ezcWebdavPropPatchRequest */ protected function parsePropPatchRequest($path, $body) { $request = new ezcWebdavPropPatchRequest($path); try { $dom = ezcWebdavServer::getInstance()->xmlTool->createDom($body); } catch (ezcWebdavInvalidXmlException $e) { throw new ezcWebdavInvalidRequestBodyException('PROPPATCH', $e->getMessage()); } if ($dom->documentElement->localName !== 'propertyupdate') { throw new ezcWebdavInvalidRequestBodyException('PROPPATCH', "Expected XML element <propertyupdate />, received <{$dom->documentElement->localName} />."); } $propElements = $dom->documentElement->getElementsByTagNameNS(ezcWebdavXmlTool::XML_DEFAULT_NAMESPACE, 'prop'); try { foreach ($propElements as $propElement) { if ($propElement->hasChildNodes()) { ezcWebdavServer::getInstance()->propertyHandler->extractProperties($propElement->childNodes, $request->updates, $propElement->parentNode->localName === 'remove' ? ezcWebdavPropPatchRequest::REMOVE : ezcWebdavPropPatchRequest::SET); } } } catch (ezcBaseValueException $e) { throw new ezcWebdavInvalidRequestBodyException('PROPPATCH', "Property extraction produced value exception: '{$e->getMessage()}'."); } $request->setHeaders(ezcWebdavServer::getInstance()->headerHandler->parseHeaders()); return $request; }
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); }