public function testValidateHeadersFailure() { $req = new ezcWebdavMoveRequest('/foo', '/bar'); $req->setHeader('Overwrite', null); try { $req->validateHeaders(); $this->fail('Exception not thrown on missing Overwrite header.'); } catch (ezcWebdavMissingHeaderException $e) { } $req->setHeader('Overwrite', 'A'); try { $req->validateHeaders(); $this->fail('Exception not thrown on invalid Overwrite header.'); } catch (ezcWebdavInvalidHeaderException $e) { } }
public function move(ezcWebdavMoveRequest $request) { global $prefs; global $filegallib; include_once 'lib/filegals/filegallib.php'; print_debug("-- HTTP method: MOVE --\n"); $this->acquireLock(); // 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($dest, $request->getHeader('Authorization'), ezcWebdavAuthorizer::ACCESS_WRITE)) { $this->freeLock(); return $this->createUnauthorizedResponse($dest, $request->getHeader('Authorization')); } // Check if resource is available if (!$this->nodeExists($source)) { $this->freeLock(); return new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_404, $source); } // If source and destination are equal, the request should always fail. if ($source === $dest) { $this->freeLock(); 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)) { $this->freeLock(); return new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_412, $dest); } // Check if the destination parent directory already exists, otherwise // bail out. if (!$this->nodeExists($destDir = dirname($dest))) { $this->freeLock(); return new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_409, $dest); } // Verify If-[None-]Match headers on the $dest if it exists if ($this->nodeExists($dest) && ($res = $this->checkIfMatchHeaders($request, $dest)) !== null) { $this->freeLock(); return $res; } elseif (($res = $this->checkIfMatchHeaders($request, $destDir)) !== null) { // Verify If-[None-]Match headers on the on $dests parent dir, if it // does not exist $this->freeLock(); 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 $this->freeLock(); return $authState['errors'][0]; } $replaced = true; if (count($delteErrors = $this->performDelete($dest)) > 0) { $this->freeLock(); return new ezcWebdavMultistatusResponse($delteErrors); } } // All checks are passed, we can actually move now. $infos = array(); $doRename = false; $doMove = false; foreach (array('source', 'dest') as $k) { // Get source and dest infos if (($infos[$k] = $filegallib->get_objectid_from_virtual_path(${$k})) !== false) { switch ($infos[$k]['type']) { case 'filegal': $infos[$k]['infos'] = $filegallib->get_file_gallery_info($infos[$k]['id']); $infos[$k]['parentId'] = $infos[$k]['infos']['parentId']; $infos[$k]['name'] = $infos[$k]['infos']['name']; break; case 'file': ///TODO: Throw an error if dest is a file, but source is a filegal $infos[$k]['infos'] = $filegallib->get_file($infos[$k]['id']); $infos[$k]['parentId'] = $infos[$k]['infos']['galleryId']; $infos[$k]['name'] = $infos[$k]['infos']['filename']; break; } } elseif ($k == 'dest') { // If dest doesn't exist, it usually means that the file / filegal has to be renamed ///TODO: Throw an error if dest is a new filegal, but source is a file if (($objectId = $filegallib->get_objectid_from_virtual_path(dirname(${$k}))) !== false && $objectId['type'] == 'filegal') { $infos[$k] = array('id' => $infos['source']['id'], 'type' => $infos['source']['type'], 'infos' => $infos['source']['infos'], 'parentId' => $objectId['id'], 'name' => basename(${$k})); switch ($infos[$k]['type']) { case 'filegal': $infos[$k]['infos']['name'] = $infos[$k]['name']; $infos[$k]['infos']['parentId'] = $infos[$k]['parentId']; break; case 'file': $infos[$k]['infos']['name'] = $infos[$k]['name']; $infos[$k]['infos']['filename'] = $infos[$k]['name']; $infos[$k]['infos']['galleryId'] = $infos[$k]['parentId']; break; } $doRename = true; } } else { // If source doesn't exist, we stop here break; } } $doMove = $infos['source']['parentId'] != $infos['dest']['parentId']; $noErrors = true; switch ($infos['source']['type']) { case 'filegal': if ($doRename) { $noErrors = (bool) $filegallib->replace_file_gallery($infos['dest']['infos']); } elseif ($doMove) { // Move is not needed if the rename occurred, since filegal renaming function handle the move already $noErrors = (bool) $filegallib->move_file_gallery($infos['source']['id'], $infos['dest']['parentId']); } break; case 'file': if ($doRename) { if ($prefs['fgal_use_db'] === 'n') { $newPath = md5($infos['dest']['name']); do { $newPath = md5(uniqid($newPath)); } while (file_exists($this->root . '/' . $newPath)); if (@rename($this->root . '/' . $infos['source']['infos']['path'], $this->root . '/' . $newPath) === false || @file_put_contents($this->root . '/' . $infos['source']['infos']['path'], '') === false) { $this->freeLock(); return false; } } else { $newPath = ''; } global $user; $noErrors = (bool) $filegallib->replace_file($infos['source']['id'], $infos['dest']['name'], $infos['source']['infos']['description'], $infos['dest']['name'], $infos['source']['infos']['data'], $infos['source']['infos']['filesize'], $infos['source']['infos']['filetype'], $user, $newPath, '', $filegallib->get_file_gallery_info($infos['source']['parentId']), false, $infos['source']['infos']['author'], $infos['source']['infos']['created'], $infos['source']['infos']['lockedby']); } if ($doMove && $noErrors) { $noErrors = (bool) $filegallib->set_file_gallery($infos['source']['id'], $infos['dest']['parentId']); } break; } $this->freeLock(); // Send proper response on success if ($noErrors) { $return = new ezcWebdavMoveResponse($replaced); } else { $return = new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_500); } print_debug("-- HTTP method: MOVE end --\n"); return $return; }
public function testMoveCollectionWithInvalidETag() { $testSourcePath = '/collection'; $testSource = "{$testSourcePath}/deep_collection"; $testDestPath = $testSourcePath; $testDest = "{$testDestPath}/copied_collection"; $backend = new ezcWebdavFileBackend($this->tempDir . 'backend/'); // Initialize all property directories $req = new ezcWebdavPropFindRequest($testSource); $req->allProp = true; $req->setHeader('Depth', ezcWebdavRequest::DEPTH_INFINITY); $req->validateHeaders(); $backend->propFind($req); $eTag = $backend->getProperty($testDestPath, 'getetag')->etag; $req = new ezcWebdavMoveRequest($testSource, $testDest); $req->setHeader('If-None-Match', array('abc23', $eTag)); $req->validateHeaders(); $res = $backend->move($req); $this->assertEquals(new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_412, '/collection', 'If-None-Match header check failed.'), $res, 'Expected response does not match real response.', 0, 20); }
public function testResourceMoveDepthInfinityErrors() { $backend = new ezcWebdavMemoryBackend(false); $backend->addContents(array('bar' => array('_1' => 'contents', '_2' => 'contents', '_3' => 'contents', '_4' => 'contents', '_5' => 'contents'))); $backend->options->failingOperations = ezcWebdavMemoryBackendOptions::REQUEST_COPY; $backend->options->failForRegexp = '(_[24]$)'; $request = new ezcWebdavMoveRequest('/bar', '/foo'); $request->setHeader('Depth', ezcWebdavRequest::DEPTH_INFINITY); $request->validateHeaders(); $response = $backend->move($request); $this->assertEquals(new ezcWebdavMultistatusResponse(new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_423, '/bar/_2'), new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_423, '/bar/_4')), $response, 'Expected response does not match real response.', 0, 20); $content = $this->readAttribute($backend, 'content'); $this->assertEquals(array('/' => array('/bar', '/foo'), '/bar' => array('/bar/_1', '/bar/_2', '/bar/_3', '/bar/_4', '/bar/_5'), '/bar/_1' => 'contents', '/bar/_2' => 'contents', '/bar/_3' => 'contents', '/bar/_4' => 'contents', '/bar/_5' => 'contents', '/foo' => array('/foo/_1', '/foo/_3', '/foo/_5'), '/foo/_1' => 'contents', '/foo/_3' => 'contents', '/foo/_5' => 'contents'), $content); }
public function testResourceMoveDepthInfinityErrors() { $backend = new ezcWebdavFileBackend($this->tempDir . 'backend/'); // Cause error by making file not readable chmod($this->tempDir . 'backend/collection/test.txt', 0); $this->assertTrue(is_dir($this->tempDir . 'backend/collection'), 'Expected existing collection before request.'); $request = new ezcWebdavMoveRequest('/collection', '/new_collection'); $request->setHeader('Depth', ezcWebdavRequest::DEPTH_INFINITY); $request->validateHeaders(); $response = $backend->move($request); $this->assertEquals($response, new ezcWebdavMultistatusResponse(new ezcWebdavErrorResponse(ezcWebdavResponse::STATUS_423, '/collection/test.txt')), 'Expected response does not match real response.', 0, 20); $this->assertTrue(is_dir($this->tempDir . 'backend/collection'), 'Expected collection not to be removed.'); $this->assertTrue(is_dir($this->tempDir . 'backend/new_collection'), 'Expected created collection.'); $this->assertFalse(is_file($this->tempDir . 'backend/new_collection/test.txt'), 'Expected file in collection not to be created.'); $this->assertTrue(is_file($this->tempDir . 'backend/new_collection/deep_collection/deep_test.txt'), 'Expected created deep file in collection.'); chmod($this->tempDir . 'backend/collection/test.txt', 0777); }
/** * 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, 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; } 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); }
/** * Parses the MOVE request and returns a request object. * * This method is responsible for parsing the MOVE 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 * ezcWebdavMoveRequest} object. * * This method may be overwritten to adjust it to special client behaviour. * * @param string $path * @param string $body * @return ezcWebdavMoveRequest */ protected function parseMoveRequest($path, $body) { $headers = ezcWebdavServer::getInstance()->headerHandler->parseHeaders(array('Destination', 'Depth', 'Overwrite')); if (!isset($headers['Destination'])) { throw new ezcWebdavMissingHeaderException('Destination'); } $request = new ezcWebdavMoveRequest($path, $headers['Destination']); $request->setHeaders($headers); if (trim($body) === '') { // No body present return $request; } try { $dom = ezcWebdavServer::getInstance()->xmlTool->createDom($body); } catch (ezcWebdavInvalidXmlException $e) { throw new ezcWebdavInvalidRequestBodyException('MOVE', $e->getMessage()); } if ($dom->documentElement->localName !== 'propertybehavior') { throw new ezcWebdavInvalidRequestBodyException('MOVE', "Expected XML element <propertybehavior />, received <{$dom->documentElement->localName} />."); } return $this->parsePropertyBehaviourContent($dom, $request); }