diff --git a/lib/Kolab/DAV/Collection.php b/lib/Kolab/DAV/Collection.php index b148ece..bec17a3 100644 --- a/lib/Kolab/DAV/Collection.php +++ b/lib/Kolab/DAV/Collection.php @@ -1,227 +1,229 @@ * * Copyright (C) 2013, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Kolab\DAV; use \Exception; /** * Collection class */ -class Collection extends \Kolab\DAV\Node implements \Sabre\DAV\ICollection +class Collection extends Node implements \Sabre\DAV\ICollection { const ROOT_DIRECTORY = 'files'; public $children; function getChildren() { // @TODO: maybe children array is too big to keep it in memory? if (is_array($this->children)) { return $this->children; } $path_len = strlen($this->path); $this->children = array(); try { // @TODO: This should be cached too (out of this class) $folders = $this->backend->folder_list(); $folders = $folders['list']; } catch (Exception $e) { + $this->throw_exception($e); } - // get subfolders foreach ($folders as $folder) { // need root-folders or subfolders of specified folder if (!$path_len || strpos($folder, $this->path . '/') === 0) { $virtual = false; // remove path suffix, the list might contain folders (roots) that // do not exist e.g.: // Files // Files/Sub // Other Users/machniak/Files // Other Users/machniak/Files/Sub // the list is sorted so we can do this in such a way if ($pos = strpos($folder, '/', $path_len + 1)) { $folder = substr($folder, 0, $pos); $virtual = true; } $path = Collection::ROOT_DIRECTORY . '/' . $folder; if ($path_len) { $folder = substr($folder, $path_len + 1); } if (!array_key_exists($folder, $this->children)) { $this->children[$folder] = new Collection($path, $this, array('virtual' => $virtual)); } } } // non-root existing folder, get files list if ($path_len && empty($this->data['virtual'])) { try { $files = $this->backend->file_list($this->path); } catch (Exception $e) { } foreach ($files as $filename => $file) { $path = Collection::ROOT_DIRECTORY . '/' . $filename; // remove path prefix $filename = substr($filename, $path_len + 1); $this->children[$filename] = new File($path, $this, $file); } } return $this->children; } /** * Returns a child object, by its name. * * This method makes use of the getChildren method to grab all the child * nodes, and compares the name. * Generally its wise to override this, as this can usually be optimized * * This method must throw Sabre\DAV\Exception\NotFound if the node does not * exist. * * @param string $name - * @throws Sabre\DAV\Exception\NotFound + * @throws Sabre\DAV\Exception * @return INode */ public function getChild($name) { // no support for hidden system files if ($name[0] == '.') { throw new \Sabre\DAV\Exception\NotFound('File not found: ' . $name); } $children = $this->getChildren(); if (array_key_exists($name, $children)) { return $children[$name]; } throw new \Sabre\DAV\Exception\NotFound('File not found: ' . $name); } /** * Checks if a child-node exists. * * It is generally a good idea to try and override this. Usually it can be optimized. * * @param string $name * @return bool */ public function childExists($name) { try { $this->getChild($name); return true; } catch (\Sabre\DAV\Exception\NotFound $e) { return false; } } /** * Creates a new file in the directory * * Data will either be supplied as a stream resource, or in certain cases * as a string. Keep in mind that you may have to support either. * * After succesful creation of the file, you may choose to return the ETag * of the new file here. * * The returned ETag must be surrounded by double-quotes (The quotes should * be part of the actual string). * * If you cannot accurately determine the ETag, you should not return it. * If you don't store the file exactly as-is (you're transforming it * somehow) you should also not return an ETag. * * This means that if a subsequent GET to this new file does not exactly * return the same contents of what was submitted here, you are strongly * recommended to omit the ETag. * * @param string $name Name of the file * @param resource|string $data Initial payload + * + * @throws Sabre\DAV\Exception * @return null|string */ public function createFile($name, $data = null) { // no support for hidden system files if ($name[0] == '.') { throw new \Sabre\DAV\Exception\Forbidden('Hidden files are not accepted'); } $filename = $this->path . '/' . $name; $filedata = $this->fileData($name, $data); try { $this->backend->file_create($filename, $filedata); } catch (Exception $e) { -// throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } // reset cache $this->children = null; } /** * Creates a new subdirectory * * @param string $name - * @throws Exception\Forbidden + * @throws Sabre\DAV\Exception * @return void */ public function createDirectory($name) { // no support for hidden system files if ($name[0] == '.') { throw new \Sabre\DAV\Exception\Forbidden('Hidden files are not accepted'); } $folder = $this->path . '/' . $name; try { $this->backend->folder_create($folder); } catch (Exception $e) { - throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } // reset cache $this->children = null; } } diff --git a/lib/Kolab/DAV/File.php b/lib/Kolab/DAV/File.php index e6575c0..d7a6dfd 100644 --- a/lib/Kolab/DAV/File.php +++ b/lib/Kolab/DAV/File.php @@ -1,198 +1,201 @@ * * Copyright (C) 2013, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Kolab\DAV; -use \rcube; use \Exception; use \DateTime; /** * File class */ class File extends Node implements \Sabre\DAV\IFile, \Sabre\DAV\IProperties { /** * Updates the data * * The data argument is a readable stream resource. * * After a succesful put operation, you may choose to return an ETag. The * etag must always be surrounded by double-quotes. These quotes must * appear in the actual string you're returning. * * Clients may use the ETag from a PUT request to later on make sure that * when they update the file, the contents haven't changed in the mean * time. * * If you don't plan to store the file byte-by-byte, and you return a * different object on a subsequent GET you are strongly recommended to not * return an ETag, and just return null. * * @param resource $data + * @throws Sabre\DAV\Exception * @return string|null */ public function put($data) { $filedata = $this->fileData($this->path, $data); try { $this->backend->file_update($this->path, $filedata); } catch (Exception $e) { -// throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } try { $this->data = $this->backend->file_info($this->path); } catch (Exception $e) { + $this->throw_exception($e); } return $this->getETag(); } /** * Returns the file content * * This method may either return a string or a readable stream resource * + * @throws Sabre\DAV\Exception * @return mixed */ public function get() { try { $fp = fopen('php://temp', 'bw+'); $this->backend->file_get($this->path, array(), $fp); rewind($fp); } catch (Exception $e) { -// throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } return $fp; } /** * Delete the current file * + * @throws Sabre\DAV\Exception * @return void */ public function delete() { try { $this->backend->file_delete($this->path); } catch (Exception $e) { -// throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } // reset cache if ($this->parent) { $this->parent->children = null; } } /** * Returns the size of the node, in bytes * * @return int */ public function getSize() { return $this->data['size']; } /** * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined * * @return mixed */ public function getETag() { return sprintf('"%s-%d"', substr(md5($this->path . ':' . $this->data['size']), 0, 16), $this->data['modified']); } /** * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream * * @return mixed */ public function getContentType() { return $this->data['type']; } /** * Updates properties on this node. * * This method received a PropPatch object, which contains all the * information about the update. * * To update specific properties, call the 'handle' method on this object. * Read the PropPatch documentation for more information. * * @param PropPatch $propPatch * @return void */ function propPatch(\Sabre\DAV\PropPatch $propPatch) { // not supported return false; } /** * Returns a list of properties for this node. * * The properties list is a list of propertynames the client requested, * encoded in clark-notation {xmlnamespace}tagname * * If the array is empty, it means 'all properties' were requested. * * Note that it's fine to liberally give properties back, instead of * conforming to the list of requested properties. * The Server class will filter out the extra. * * @param array $properties * @return void */ function getProperties($properties) { $result = array(); if ($this->data['created']) { $result['{DAV:}creationdate'] = \Sabre\HTTP\Util::toHTTPDate(new DateTime('@'.$this->data['created'])); } return $result; } } diff --git a/lib/Kolab/DAV/Node.php b/lib/Kolab/DAV/Node.php index beba2a9..9e72f8f 100644 --- a/lib/Kolab/DAV/Node.php +++ b/lib/Kolab/DAV/Node.php @@ -1,196 +1,215 @@ * * Copyright (C) 2013, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Kolab\DAV; -use \rcube; -use \rcube_mime; use \Exception; /** * Node class */ class Node implements \Sabre\DAV\INode { /** * The path to the current node * * @var string */ protected $path; /** * The file API backend class * * @var Kolab\DAV\Backend */ protected $backend; /** * Internal node data (e.g. file parameters) * * @var array */ protected $data; /** * Parent node * * @var Kolab\DAV\Node */ protected $parent; /** * @brief Sets up the node, expects a full path name * @param string $path Node name with path * @param Kolab\DAV\Node $parent Parent node * @param array $data Node data * * @return void */ public function __construct($path, $parent = null, $data = array()) { $this->data = $data; $this->path = $path; $this->parent = $parent; $this->backend = Backend::get_instance(); if ($this->path == Collection::ROOT_DIRECTORY) { $this->path = ''; } else if (strpos($this->path, Collection::ROOT_DIRECTORY . '/') === 0) { $this->path = substr($this->path, strlen(Collection::ROOT_DIRECTORY . '/')); } } /** * Returns the last modification time * * In this case, it will simply return the current time * * @return int */ public function getLastModified() { return $this->data['modified'] ? $this->data['modified'] : null; } /** * Deletes the current node (folder) * - * @throws Sabre\DAV\Exception\Forbidden + * @throws Sabre\DAV\Exception * @return void */ public function delete() { try { $this->backend->folder_delete($this->path); } catch (Exception $e) { - throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } // reset cache if ($this->parent) { $this->parent->children = null; } } /** * Renames the node * - * @throws Sabre\DAV\Exception\Forbidden + * @throws Sabre\DAV\Exception * @param string $name The new name * @return void */ public function setName($name) { $path = explode('/', $this->path); array_pop($path); $newname = implode('/', $path) . '/' . $name; $method = (is_a($this, 'Kolab\\DAV\\File') ? 'file' : 'folder') . '_move'; try { $this->backend->$method($this->path, $newname); } catch (Exception $e) { - throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); + $this->throw_exception($e); } // reset cache if ($this->parent) { $this->parent->children = null; } } /** * @brief Returns the name of the node * @return string */ public function getName() { if ($this->path === '') { return Collection::ROOT_DIRECTORY; } return array_pop(explode('/', $this->path)); } /** * Build file data array to pass into backend */ protected function fileData($name, $data = null) { if ($this->data && $this->data['type']) { $type = $this->data['type']; } else { $type = 'application/octet-stream'; } // $data can be a resource or a string if (is_resource($data)) { rewind($data); // $data can be php://input or php://temp // php://input is not seekable, we need to "convert" // it to seekable resource, fstat/rewind later will work $meta = stream_get_meta_data($data); if (!$meta['seekable']) { $new_data = fopen('php://temp','r+'); stream_copy_to_stream($data, $new_data); rewind($new_data); $data = $new_data; } } $filedata = array( 'content' => $data, 'type' => $type, ); return $filedata; } + + /** + * Convert Chwala exceptions to Sabre exceptions + * + * @param Exception Chwala exception + * @throws Sabre\DAV\Exception + */ + protected function throw_exception($e) + { + $error = $e->getCode(); + $msg = $e->getMessage(); + + if ($error == \file_storage::ERROR_UNAVAILABLE) { + throw new \Sabre\DAV\Exception\ServiceUnavailable($msg); + } + else if ($error == \kolab_storage::ERROR_FORBIDDEN) { + throw new \Sabre\DAV\Exception\Forbidden($msg); + } + + throw new \Sabre\DAV\Exception($msg); + } }