From 5be3ba8436cbe998bdadc2404ebc7aafa05f94df Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Sun, 29 Jun 2014 16:00:21 +0200 Subject: [PATCH 1/4] Fixed a wrong timestamp update when adding a file. When a file was uploaded the timestamp of the containing folder should get updated, but the timestamp of the first file in the folder was mistakenly updated. There are some more wrong timestamps, but still looking for the bug. --- include/attach.php | 8 ++++---- include/reddav.php | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/attach.php b/include/attach.php index f5eaaa448..fdb9c0d56 100644 --- a/include/attach.php +++ b/include/attach.php @@ -552,9 +552,9 @@ function z_readdir($channel_id, $observer_hash, $pathname, $parent_hash = '') { /** * @function attach_mkdir($channel,$observer_hash,$arr); - * + * * @brief Create directory. - * + * * @param $channel channel array of owner * @param $observer_hash hash of current observer * @param $arr parameter array to fulfil request @@ -641,7 +641,7 @@ function attach_mkdir($channel, $observer_hash, $arr = null) { $path .= $arr['hash']; - $created = datetime_convert(); + $created = datetime_convert(); $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", @@ -678,7 +678,7 @@ function attach_mkdir($channel, $observer_hash, $arr = null) { $ret['message'] = t('database storage failed.'); return $ret; - + } /** diff --git a/include/reddav.php b/include/reddav.php index c9b238efb..2c2ddcb1c 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -113,8 +113,13 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { throw new DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found.'); } + /** + * @brief Returns the name of the directory. + * + * @return string + */ public function getName() { - logger('RedDirectory::getName returns: ' . basename($this->red_path), LOGGER_DATA); + logger('RedDirectory::getName() returns: ' . basename($this->red_path), LOGGER_DATA); return (basename($this->red_path)); } @@ -204,7 +209,7 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { ); // update the folder's lastmodified timestamp - $e = q("UPDATE attach SET edited = '%s' WHERE folder = '%s' AND uid = %d LIMIT 1", + $e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", dbesc($edited), dbesc($this->folder_hash), intval($c[0]['channel_id']) @@ -250,8 +255,9 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { if ($r) { $result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash)); - if (! $result['success']) + if (! $result['success']) { logger('RedDirectory::createDirectory(): ' . print_r($result, true), LOGGER_DEBUG); + } } } From 322091cd12ba972d1f3fee0df47ffb4e49a452ce Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Sun, 29 Jun 2014 17:49:46 +0200 Subject: [PATCH 2/4] Fixed some more timestamp bugs in RedDAV. Fixed an SQL-query in RedFile::put(), where parameters where in wrong order. --- include/attach.php | 43 +++++++++++++++++++++++++----------- include/reddav.php | 12 +++++----- view/tpl/cloud_directory.tpl | 23 ++++++++----------- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/include/attach.php b/include/attach.php index fdb9c0d56..a7b094500 100644 --- a/include/attach.php +++ b/include/attach.php @@ -665,17 +665,25 @@ function attach_mkdir($channel, $observer_hash, $arr = null) { ); if($r) { - if(mkdir($path,STORAGE_DEFAULT_PERMISSIONS, true)) { + if(mkdir($path, STORAGE_DEFAULT_PERMISSIONS, true)) { $ret['success'] = true; $ret['data'] = $arr; + + // update the parent folder's lastmodified timestamp + $e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", + dbesc($created), + dbesc($arr['folder']), + intval($channel_id) + ); } else { logger('attach_mkdir: ' . mkdir . ' ' . $path . 'failed.'); $ret['message'] = t('mkdir failed.'); } } - else + else { $ret['message'] = t('database storage failed.'); + } return $ret; @@ -727,31 +735,30 @@ function attach_change_permissions($channel_id, $resource, $allow_cid, $allow_gi return; } - + /** - * @brief Delete a file. + * @brief Delete a file/directory. * - * @param $channel_id - * @param $resource + * @param int $channel_id + * @param string $resource a hash to delete */ function attach_delete($channel_id, $resource) { - - $c = q("select channel_address from channel where channel_id = %d limit 1", + $c = q("SELECT channel_address FROM channel WHERE channel_id = %d LIMIT 1", intval($channel_id) ); $channel_address = (($c) ? $c[0]['channel_address'] : 'notfound'); - $r = q("select hash, flags from attach where hash = '%s' and uid = %d limit 1", + $r = q("SELECT hash, flags, folder FROM attach WHERE hash = '%s' AND uid = %d limit 1", dbesc($resource), intval($channel_id) ); - if(! $r) return; + // If resource is a directory delete everything in the directory recursive if($r[0]['flags'] & ATTACH_FLAG_DIR) { $x = q("select hash, flags from attach where folder = '%s' and uid = %d", dbesc($resource), @@ -763,8 +770,10 @@ function attach_delete($channel_id, $resource) { } } } + + // delete a file from filesystem if($r[0]['flags'] & ATTACH_FLAG_OS) { - $y = q("select data from attach where hash = '%s' and uid = %d limit 1", + $y = q("SELECT data FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", dbesc($resource), intval($channel_id) ); @@ -778,14 +787,22 @@ function attach_delete($channel_id, $resource) { } } - $z = q("delete from attach where hash = '%s' and uid = %d limit 1", + // delete from database + $z = q("DELETE FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", dbesc($resource), intval($channel_id) ); + // update the parent folder's lastmodified timestamp + $e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", + dbesc(datetime_convert()), + dbesc($r[0]['folder']), + intval($channel_id) + ); + return; } - + /** * @brief Returns path to file in cloud/. * diff --git a/include/reddav.php b/include/reddav.php index 2c2ddcb1c..1eaf7cd02 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -476,8 +476,8 @@ class RedFile extends DAV\Node implements DAV\IFile { // @todo only 3 values are needed $c = q("SELECT * FROM channel WHERE channel_id = %d AND NOT (channel_pageflags & %d) LIMIT 1", - intval(PAGE_REMOVED), - intval($this->auth->owner_id) + intval($this->auth->owner_id), + intval(PAGE_REMOVED) ); $r = q("SELECT flags, folder, data FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", @@ -518,7 +518,7 @@ class RedFile extends DAV\Node implements DAV\IFile { ); // update the folder's lastmodified timestamp - $e = q("UPDATE attach SET edited = '%s' WHERE folder = '%s' AND uid = %d LIMIT 1", + $e = q("UPDATE attach SET edited = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", dbesc($edited), dbesc($r[0]['folder']), intval($c[0]['channel_id']) @@ -628,12 +628,14 @@ class RedFile extends DAV\Node implements DAV\IFile { } /** - * @brief Delete the current file. + * @brief Delete the file. * * @throw DAV\Exception\Forbidden * @return void */ public function delete() { + logger('RedFile::delete(): ' . basename($this->name), LOGGER_DEBUG); + if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) { throw new DAV\Exception\Forbidden('Permission denied.'); } @@ -732,7 +734,7 @@ function RedCollectionData($file, &$auth) { $permission_error = false; for ($x = 1; $x < count($path_arr); $x++) { - $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d) $perms limit 1", + $r = q("SELECT id, hash, filename, flags FROM attach WHERE folder = '%s' AND filename = '%s' AND uid = %d AND (flags & %d) $perms LIMIT 1", dbesc($folder), dbesc($path_arr[$x]), intval($channel_id), diff --git a/view/tpl/cloud_directory.tpl b/view/tpl/cloud_directory.tpl index cdcab1990..fc6b3309a 100644 --- a/view/tpl/cloud_directory.tpl +++ b/view/tpl/cloud_directory.tpl @@ -9,7 +9,6 @@ {{t('Last modified')}}
- {{if $parentpath}} {{$parentpath.icon}} @@ -20,29 +19,25 @@ {{/if}} - - {{foreach $entries as $item}} {{$item.icon}} - {{$item.displayName}} - + {{$item.displayName}} {{if $item.is_owner}} - {{$item.attachIcon}} - - + {{$item.attachIcon}} + + {{else}} {{/if}} - {{$item.type}} - {{$item.sizeFormatted}} - {{$item.lastmodified}} - + {{$item.type}} + {{$item.sizeFormatted}} + {{$item.lastmodified}} + {{/foreach}} -
{{if $quota.limit || $quota.used}}

{{t('Total')}} {{$quota.desc}}

-{{/if}} +{{/if}} \ No newline at end of file From aacb293f2e0e7a6995e66202313856500f503120 Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Sun, 29 Jun 2014 21:15:37 +0200 Subject: [PATCH 3/4] Return a correct timestamp when a folder is empty. If a folder was empty a zero timestamp was returned. Now it will return the timestamp of the folder itself. --- include/reddav.php | 32 +++++++++++++++++++-------- mod/cloud.php | 54 +++++++++++++++++++++++----------------------- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/include/reddav.php b/include/reddav.php index 1eaf7cd02..ba08ec332 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -356,9 +356,13 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { } /** - * @brief Returns the last modification time for the directory, as a unix + * @brief Returns the last modification time for the directory, as a UNIX * timestamp. * + * It looks for the last edited file in the folder. If it is an empty folder + * it returns the lastmodified time of the folder itself, to prevent zero + * timestamps. + * * @return int last modification time in UNIX timestamp */ public function getLastModified() { @@ -366,9 +370,15 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { dbesc($this->folder_hash), intval($this->auth->owner_id) ); - if ($r) - return datetime_convert('UTC', 'UTC', $r[0]['edited'], 'U'); - return ''; + if (! $r) { + $r = q("SELECT edited FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", + dbesc($this->folder_hash), + intval($this->auth->owner_id) + ); + if (! $r) + return ''; + } + return datetime_convert('UTC', 'UTC', $r[0]['edited'], 'U'); } /** @@ -997,11 +1007,17 @@ class RedBasicAuth extends DAV\Auth\Backend\AbstractBasic { $this->currentUser = $name; } - function setBrowserPlugin($browser) { + /** + * @brief Set browser plugin. + * + * @see RedBrowser::set_writeable() + * @param DAV\Browser\Plugin $browser + */ + public function setBrowserPlugin($browser) { $this->browser = $browser; } - // internal? loggin function + // internal? logging function function log() { logger('dav: auth: channel_name ' . $this->channel_name, LOGGER_DATA); logger('dav: auth: channel_id ' . $this->channel_id, LOGGER_DATA); @@ -1045,9 +1061,7 @@ class RedBrowser extends DAV\Browser\Plugin { } /** - * Creates the directory listing for the given path. - * - * @todo Why not use RedDirectory for this? + * @brief Creates the directory listing for the given path. * * @param string $path which should be displayed */ diff --git a/mod/cloud.php b/mod/cloud.php index 51cedd2fd..25773066a 100644 --- a/mod/cloud.php +++ b/mod/cloud.php @@ -1,13 +1,16 @@ get_channel(); - $a->page['htmlhead'] .= '' . "\r\n" ; + $a->page['htmlhead'] .= '' . "\r\n" ; if($which) - profile_load($a,$which,$profile); - - - + profile_load($a, $which, $profile); $auth = new RedBasicAuth(); @@ -64,15 +65,13 @@ function cloud_init(&$a) { $auth->channel_account_id = $channel['channel_account_id']; if($channel['channel_timezone']) $auth->timezone = $channel['channel_timezone']; - } + } $auth->observer = $ob_hash; - } + } if($_GET['davguest']) $_SESSION['davguest'] = true; - - $_SERVER['QUERY_STRING'] = str_replace(array('?f=','&f='),array('',''),$_SERVER['QUERY_STRING']); $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']); $_SERVER['QUERY_STRING'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism','',$_SERVER['QUERY_STRING']); @@ -81,8 +80,11 @@ function cloud_init(&$a) { $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); $_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism','',$_SERVER['REQUEST_URI']); - $rootDirectory = new RedDirectory('/',$auth); + $rootDirectory = new RedDirectory('/', $auth); + + // A SabreDAV server-object $server = new DAV\Server($rootDirectory); + // prevent overwriting changes each other with a lock backend $lockBackend = new DAV\Locks\Backend\File('store/[data]/locks'); $lockPlugin = new DAV\Locks\Plugin($lockBackend); @@ -95,11 +97,11 @@ function cloud_init(&$a) { // In order to avoid prompting for passwords for viewing a DIRECTORY, add the URL query parameter 'davguest=1' $isapublic_file = false; - $davguest = ((x($_SESSION,'davguest')) ? true : false); + $davguest = ((x($_SESSION, 'davguest')) ? true : false); if((! $auth->observer) && ($_SERVER['REQUEST_METHOD'] === 'GET')) { try { - $x = RedFileData('/' . $a->cmd,$auth); + $x = RedFileData('/' . $a->cmd, $auth); if($x instanceof RedFile) $isapublic_file = true; } @@ -113,21 +115,19 @@ function cloud_init(&$a) { $auth->Authenticate($server, t('Red Matrix - Guests: Username: {your email address}, Password: +++')); } catch ( Exception $e) { - logger('mod_cloud: auth exception' . $e->getMessage()); - http_status_exit($e->getHTTPCode(),$e->getMessage()); + logger('mod_cloud: auth exception' . $e->getMessage()); + http_status_exit($e->getHTTPCode(), $e->getMessage()); } } -// $browser = new DAV\Browser\Plugin(); + // provide a directory view for the cloud in Red Matrix $browser = new RedBrowser($auth); $auth->setBrowserPlugin($browser); - $server->addPlugin($browser); - // All we need to do now, is to fire up the server $server->exec(); From ffea67524026caec14da923bafe8ad34ec63ef08 Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Mon, 30 Jun 2014 01:08:29 +0200 Subject: [PATCH 4/4] Add rename support for DAV directories. This works only over DAV right now, no GUI yet. --- include/attach.php | 6 ++-- include/reddav.php | 72 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/include/attach.php b/include/attach.php index a7b094500..da22e8ed5 100644 --- a/include/attach.php +++ b/include/attach.php @@ -555,9 +555,9 @@ function z_readdir($channel_id, $observer_hash, $pathname, $parent_hash = '') { * * @brief Create directory. * - * @param $channel channel array of owner - * @param $observer_hash hash of current observer - * @param $arr parameter array to fulfil request + * @param array $channel channel array of owner + * @param string $observer_hash hash of current observer + * @param array $arr parameter array to fulfil request * Required: * $arr['filename'] * $arr['folder'] // hash of parent directory, empty string for root directory diff --git a/include/reddav.php b/include/reddav.php index ba08ec332..c4b249598 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -23,22 +23,33 @@ require_once('include/attach.php'); */ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { + /** + * @brief The path inside /cloud + */ private $red_path; private $folder_hash; - // @todo I think this is not used anywhere, we always strip '/cloud' and only use it in debug + /** + * @brief The full path as seen in the browser. + * /cloud + $red_path + * @todo I think this is not used anywhere, we always strip '/cloud' and only use it in debug + */ private $ext_path; private $root_dir = ''; private $auth; + /** + * @brief The real path on the filesystem. + * The actual path in store/ with the hashed names. + */ private $os_path = ''; /** - * Sets up the directory node, expects a full path. + * @brief Sets up the directory node, expects a full path. * * @param string $ext_path a full path - * @param $auth_plugin + * @param RedBasicAuth &$auth_plugin */ public function __construct($ext_path, &$auth_plugin) { - logger('RedDirectory::__construct() ' . $ext_path, LOGGER_DEBUG); + logger('RedDirectory::__construct() ' . $ext_path, LOGGER_DATA); $this->ext_path = $ext_path; // remove "/cloud" from the beginning of the path $this->red_path = ((strpos($ext_path, '/cloud') === 0) ? substr($ext_path, 6) : $ext_path); @@ -54,7 +65,7 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { } } - function log() { + private function log() { logger('RedDirectory::log() ext_path ' . $this->ext_path, LOGGER_DATA); logger('RedDirectory::log() os_path ' . $this->os_path, LOGGER_DATA); logger('RedDirectory::log() red_path ' . $this->red_path, LOGGER_DATA); @@ -122,6 +133,40 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { logger('RedDirectory::getName() returns: ' . basename($this->red_path), LOGGER_DATA); return (basename($this->red_path)); } + + /** + * @brief Renames the directory. + * + * @todo handle duplicate directory name + * + * @throw DAV\Exception\Forbidden + * @param string $name The new name of the directory. + * @return void + */ + public function setName($name) { + logger('RedDirectory::setName(): ' . basename($this->red_path) . ' -> ' . $name, LOGGER_DATA); + + if ((! $name) || (! $this->auth->owner_id)) { + logger('RedDirectory::setName(): permission denied'); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + + if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) { + logger('RedDirectory::setName(): permission denied'); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + + list($parent_path, ) = DAV\URLUtil::splitPath($this->red_path); + $new_path = $parent_path . '/' . $name; + + $r = q("UPDATE attach SET filename = '%s' WHERE hash = '%s' AND uid = %d LIMIT 1", + dbesc($name), + dbesc($this->folder_hash), + intval($this->auth->owner_id) + ); + + $this->red_path = $new_path; + } /** * @brief Creates a new file in the directory. @@ -135,18 +180,18 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { * @throws DAV\Exception\Forbidden * @param string $name Name of the file * @param resource|string $data Initial payload - * @return null|string + * @return null|string ETag */ public function createFile($name, $data = null) { - logger('RedDirectory::createFile(): ' . $name, LOGGER_DEBUG); + logger('RedDirectory::createFile(): ' . $name, LOGGER_DATA); if (! $this->auth->owner_id) { - logger('createFile: permission denied'); + logger('RedDirectory::createFile(): permission denied'); throw new DAV\Exception\Forbidden('Permission denied.'); } if (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage')) { - logger('createFile: permission denied'); + logger('RedDirectory::createFile(): permission denied'); throw new DAV\Exception\Forbidden('Permission denied.'); } @@ -311,7 +356,7 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { if (! $path_arr) return; - logger('RedDirectory::getDir(): path: ' . print_r($path_arr, true), LOGGER_DEBUG); + logger('RedDirectory::getDir(): path: ' . print_r($path_arr, true), LOGGER_DATA); $channel_name = $path_arr[0]; @@ -321,7 +366,7 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { ); if (! $r) { - throw new DAV\Exception\NotFound('The file with name: ' . $channel_name . ' could not be found'); + throw new DAV\Exception\NotFound('The file with name: ' . $channel_name . ' could not be found.'); return; } @@ -534,7 +579,10 @@ class RedFile extends DAV\Node implements DAV\IFile { intval($c[0]['channel_id']) ); - // @todo do we really want to remove the whole file if an update fails because of maxfilesize or quota? + // @todo do we really want to remove the whole file if an update fails + // because of maxfilesize or quota? + // There is an Exception "InsufficientStorage" or "PaymentRequired" for + // our service class from SabreDAV we could use. $maxfilesize = get_config('system', 'maxfilesize'); if (($maxfilesize) && ($size > $maxfilesize)) {