예제 #1
0
 /**
  * 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;
         }
     }
 }
예제 #2
0
 /**
  * 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;
 }
예제 #4
0
 /**
  * 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;
 }
예제 #5
0
 /**
  * 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;
 }
예제 #6
0
 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:'));
 }
예제 #7
0
 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);
 }
예제 #8
0
파일: simple.php 프로젝트: bmdevel/ezc
 /**
  * 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));
 }
예제 #9
0
 /**
  * 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);
 }
예제 #10
0
 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);
 }
예제 #11
0
 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);
 }
예제 #12
0
 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
 }