From 8bca2eb07f30160e446b58030359f6e4a8f32739 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 10 Apr 2012 07:39:10 +0000 Subject: [PATCH] * WebDAV/Filemanager: fixed not working home-directores --- phpgwapi/inc/class.egw_vfs.inc.php | 460 ++++++++++++++++-- .../inc/class.sqlfs_stream_wrapper.inc.php | 114 +++-- phpgwapi/inc/class.vfs_webdav_server.inc.php | 125 ++++- webdav.php | 19 +- 4 files changed, 589 insertions(+), 129 deletions(-) diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index 3ba568356f..db82a925f8 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage vfs * @author Ralf Becker - * @copyright (c) 2008-9 by Ralf Becker + * @copyright (c) 2008-10 by Ralf Becker * @version $Id$ */ @@ -34,11 +34,11 @@ * * The two following methods can be used to persitently mount further filesystems (without editing the code): * - * - boolean/array egw_vfs::mount($url,$path) to mount $ur on $path or to return the fstab when called without argument + * - boolean|array egw_vfs::mount($url,$path) to mount $ur on $path or to return the fstab when called without argument * - boolean egw_vfs::umount($path) to unmount a path or url * * The stream wrapper interface allows to access hugh files in junks to not be limited by the - * memory_limit setting of php. To do you should path a resource to the opened file and not the content: + * memory_limit setting of php. To do you should pass the opened file as resource and not the content: * * $file = egw_vfs::fopen('/home/user/somefile','r'); * $content = fread($file,1024); @@ -180,20 +180,42 @@ class egw_vfs extends vfs_stream_wrapper { $ret = false; - if (($from_fp = self::fopen($from,'r')) && - ($to_fp = self::fopen($to,'w'))) + $old_props = self::file_exists($to) ? self::propfind($to,null) : array(); + // copy properties (eg. file comment), if there are any and evtl. existing old properties + $props = self::propfind($from,null); + + foreach($old_props as $prop) { - $ret = stream_copy_to_stream($from_fp,$to_fp) !== false; + if (!self::find_prop($props,$prop)) + { + $prop['val'] = null; // null = delete prop + $props[] = $prop; + } } - if ($from_fp) - { - fclose($from_fp); - } - if ($to_fp) - { - fclose($to_fp); - } - return $ret; + // using self::copy_uploaded() to treat copying incl. properties as atomar operation in respect of notifications + return self::copy_uploaded(self::PREFIX.$from,$to,$props,false); // false = no is_uploaded_file check! + } + + /** + * Find a specific property in an array of properties (eg. returned by propfind) + * + * @param array &$props + * @param array|string $name property array or name + * @param string $ns=self::DEFAULT_PROP_NAMESPACE namespace, only if $prop is no array + * @return &array reference to property in $props or null if not found + */ + static function &find_prop(array &$props,$name,$ns=self::DEFAULT_PROP_NAMESPACE) + { + if (is_array($name)) + { + $ns = $name['ns']; + $name = $name['name']; + } + foreach($props as &$prop) + { + if ($prop['name'] == $name && $prop['ns'] == $ns) return $prop; + } + return null; } /** @@ -276,10 +298,14 @@ class egw_vfs extends vfs_stream_wrapper * * @param string $url=null url of the filesystem to mount, eg. oldvfs://default/ * @param string $path=null path to mount the filesystem in the vfs, eg. / - * @return array/boolean array with fstab, if called without parameter or true on successful mount + * @param boolean $check_url=null check if url is an existing directory, before mounting it + * default null only checks if url does not contain a $ as used in $user or $pass + * @return array|boolean array with fstab, if called without parameter or true on successful mount */ - static function mount($url=null,$path=null) + static function mount($url=null,$path=null,$check_url=null) { + if (is_null($check_url)) $check_url = strpos($url,'$') === false; + if (!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup { $api_config = config::read('phpgwapi'); @@ -306,7 +332,7 @@ class egw_vfs extends vfs_stream_wrapper } self::load_wrapper(parse_url($url,PHP_URL_SCHEME)); - if (!file_exists($url) || opendir($url) === false) + if ($check_url && (!file_exists($url) || opendir($url) === false)) { if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') url does NOT exist!'); return false; // url does not exist @@ -317,7 +343,11 @@ class egw_vfs extends vfs_stream_wrapper config::save_value('vfs_fstab',self::$fstab,'phpgwapi'); $GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab; - + // invalidate session cache + if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited + { + $GLOBALS['egw']->invalidate_session_cache(); + } if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns true (successful new mount).'); return true; } @@ -343,15 +373,32 @@ class egw_vfs extends vfs_stream_wrapper config::save_value('vfs_fstab',self::$fstab,'phpgwapi'); $GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab; - + // invalidate session cache + if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited + { + $GLOBALS['egw']->invalidate_session_cache(); + } if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns true (successful unmount).'); return true; } + /** + * Check if file is hidden: name starts with a '.' or is Thumbs.db + * + * @param string $path + * @return boolean + */ + public static function is_hidden($path) + { + $file = self::basename($path); + + return $file[0] == '.' || $file == 'Thumbs.db'; + } + /** * find = recursive search over the filesystem * - * @param string/array $base base of the search + * @param string|array $base base of the search * @param array $options=null the following keys are allowed: * - type => {d|f|F} d=dirs, f=files (incl. symlinks), F=files (incl. symlinks to files), default all * - depth => {true|false(default)} put the contents of a dir before the dir itself @@ -371,7 +418,7 @@ class egw_vfs extends vfs_stream_wrapper * - limit => N,[n=0] return N entries from position n on, which defaults to 0 * - follow => {true|false(default)} follow symlinks * - hidden => {true|false(default)} include hidden files (name starts with a '.' or is Thumbs.db) - * @param string/array/true $exec=null function to call with each found file/dir as first param and stat array as last param or + * @param string|array/true $exec=null function to call with each found file/dir as first param and stat array as last param or * true to return file => stat pairs * @param array $exec_params=null further params for exec as array, path is always the first param and stat the last! * @return array of pathes if no $exec, otherwise path => stat pairs @@ -447,7 +494,7 @@ class egw_vfs extends vfs_stream_wrapper { if ($file == '.' || $file == '..') continue; // ignore current and parent dir! - if (($file[0] == '.' || $file == 'Thumbs.db') && !$options['hidden']) continue; // ignore hidden files + if (self::is_hidden($file) && !$options['hidden']) continue; // ignore hidden files $file = self::concat($path,$file); @@ -571,7 +618,7 @@ class egw_vfs extends vfs_stream_wrapper if ($options['url']) { - $stat = lstat($path); + $stat = @lstat($path); } else { @@ -653,7 +700,7 @@ class egw_vfs extends vfs_stream_wrapper /** * Recursiv remove all given url's, including it's content if they are files * - * @param string/array $urls url or array of url's + * @param string|array $urls url or array of url's * @param boolean $allow_urls=false allow to use url's, default no only pathes (to stay within the vfs) * @return array */ @@ -685,9 +732,9 @@ class egw_vfs extends vfs_stream_wrapper } if (is_dir($url) && !is_link($url)) { - return rmdir($url); + return egw_vfs::rmdir($url,0); } - return unlink($url); + return egw_vfs::unlink($url); } /** @@ -695,10 +742,11 @@ class egw_vfs extends vfs_stream_wrapper * which is wrong in case of our vfs, as we use the current users id and memberships * * @param string $path - * @param int $check=4 mode to check: 4 = read, 2 = write, 1 = executable + * @param int $check mode to check: one or more or'ed together of: 4 = egw_vfs::READABLE, + * 2 = egw_vfs::WRITABLE, 1 = egw_vfs::EXECUTABLE * @return boolean */ - static function is_readable($path,$check = 4) + static function is_readable($path,$check = self::READABLE) { return self::check_access($path,$check); } @@ -707,13 +755,52 @@ class egw_vfs extends vfs_stream_wrapper * The stream_wrapper interface checks is_{readable|writable|executable} against the webservers uid, * which is wrong in case of our vfs, as we use the current users id and memberships * - * @param array/string $path stat array or path - * @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable - * @param array $stat=null stat array, to not query it again + * @param string $path path + * @param int $check mode to check: one or more or'ed together of: 4 = egw_vfs::READABLE, + * 2 = egw_vfs::WRITABLE, 1 = egw_vfs::EXECUTABLE + * @param array|boolean $stat=null stat array or false, to not query it again + * @param int $user=null user used for check, if not current user (egw_vfs::$user) * @return boolean */ - static function check_access($path,$check,$stat=null) + static function check_access($path, $check, $stat=null, $user=null) { + if (is_null($stat) && $user && $user != self::$user) + { + static $path_user_stat = array(); + + $backup_user = self::$user; + self::$user = $user; + + if (!isset($path_user_stat[$path]) || !isset($path_user_stat[$path][$user])) + { + self::clearstatcache($path); + + $path_user_stat[$path][$user] = self::url_stat($path, 0); + + self::clearstatcache($path); // we need to clear the stat-cache after the call too, as the next call might be the regular user again! + } + if (($stat = $path_user_stat[$path][$user])) + { + // some backend mounts use $user:$pass in their url, for them we have to deny access! + if (strpos(self::resolve_url($path, false, false, false), '$user') !== false) + { + $ret = false; + } + else + { + $ret = self::check_access($path, $check, $stat); + } + } + else + { + $ret = false; // no access, if we can not stat the file + } + self::$user = $backup_user; + + //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check,$user) ".array2string($ret)); + return $ret; + } + if (self::$is_root) { return true; @@ -722,7 +809,7 @@ class egw_vfs extends vfs_stream_wrapper // throw exception if stat array is used insead of path, can be removed soon if (is_array($path)) { - throw new egw_exception_wrong_parameter('path has to be string, use check_acces($path,$check,$stat=null)!'); + throw new egw_exception_wrong_parameter('path has to be string, use check_access($path,$check,$stat=null)!'); } // query stat array, if not given if (is_null($stat)) @@ -736,6 +823,20 @@ class egw_vfs extends vfs_stream_wrapper //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!"); return false; // file not found } + // check if we use an EGroupwre stream wrapper, or a stock php one + // if it's not an EGroupware one, we can NOT use uid, gid and mode! + if (($scheme = parse_url($stat['url'],PHP_URL_SCHEME)) && !(class_exists(self::scheme2class($scheme)))) + { + switch($check) + { + case self::READABLE: + return is_readable($stat['url']); + case self::WRITABLE: + return is_writable($stat['url']); + case self::EXECUTABLE: + return is_executable($stat['url']); + } + } // check if other rights grant access if (($stat['mode'] & $check) == $check) { @@ -751,12 +852,7 @@ class egw_vfs extends vfs_stream_wrapper // check if there's a group access and we have the right membership if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid']) { - static $memberships; - if (is_null($memberships)) - { - $memberships = $GLOBALS['egw']->accounts->memberships(self::$user,true); - } - if (in_array(-abs($stat['gid']),$memberships)) + if (($memberships = $GLOBALS['egw']->accounts->memberships(self::$user, true)) && in_array(-abs($stat['gid']), $memberships)) { //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!"); return true; @@ -778,7 +874,7 @@ class egw_vfs extends vfs_stream_wrapper */ static function is_writable($path) { - return self::is_readable($path,2); + return self::is_readable($path,self::WRITABLE); } /** @@ -790,7 +886,7 @@ class egw_vfs extends vfs_stream_wrapper */ static function is_executable($path) { - return self::is_readable($path,1); + return self::is_readable($path,self::EXECUTABLE); } /** @@ -811,7 +907,7 @@ class egw_vfs extends vfs_stream_wrapper * * @param string $path string with path * @param int $rights=null rights to set, or null to delete the entry - * @param int/boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path + * @param int|boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path * @return boolean true if acl is set/deleted, false on error */ static function eacl($url,$rights=null,$owner=null) @@ -825,7 +921,7 @@ class egw_vfs extends vfs_stream_wrapper * Calls itself recursive, to get the parent directories * * @param string $path - * @return array/boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found + * @return array|boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found */ static function get_eacl($path) { @@ -836,7 +932,7 @@ class egw_vfs extends vfs_stream_wrapper * Store properties for a single ressource (file or dir) * * @param string $path string with path - * @param array $props array or array with values for keys 'name', 'ns', 'val' (null to delete the prop) + * @param array $props array of array with values for keys 'name', 'ns', 'val' (null to delete the prop) * @return boolean true if props are updated, false otherwise (eg. ressource not found) */ static function proppatch($path,array $props) @@ -874,7 +970,7 @@ class egw_vfs extends vfs_stream_wrapper /** * Convert a symbolic mode string or octal mode to an integer * - * @param string/int $set comma separated mode string to set [ugo]+[+=-]+[rwx]+ + * @param string|int $set comma separated mode string to set [ugo]+[+=-]+[rwx]+ * @param int $mode=0 current mode of the file, necessary for +/- operation * @return int */ @@ -1022,6 +1118,10 @@ class egw_vfs extends vfs_stream_wrapper { $img = $GLOBALS['egw']->common->image('etemplate',$icon='mime'.$size.'_unknown'); } + if ($et_image === 'url') + { + return $img; + } if ($et_image) { return 'etemplate/'.$icon; @@ -1030,7 +1130,7 @@ class egw_vfs extends vfs_stream_wrapper } /** - * Human readable size values in k or M + * Human readable size values in k, M or G * * @param int $size * @return string @@ -1039,7 +1139,8 @@ class egw_vfs extends vfs_stream_wrapper { if ($size < 1024) return $size; if ($size < 1024*1024) return sprintf('%3.1lfk',(float)$size/1024); - return sprintf('%3.1lfM',(float)$size/(1024*1024)); + if ($size < 1024*1024*1024) return sprintf('%3.1lfM',(float)$size/(1024*1024)); + return sprintf('%3.1lfG',(float)$size/(1024*1024*1024)); } /** @@ -1059,8 +1160,10 @@ class egw_vfs extends vfs_stream_wrapper /** * Get the directory / parent of a given path or url(!), return false for '/'! * + * Also works around PHP under Windows returning dirname('/something') === '\\', which is NOT understood by EGroupware's VFS! + * * @param string $path path or url - * @return string/boolean parent or false if there's none ($path == '/') + * @return string|boolean parent or false if there's none ($path == '/') */ static function dirname($url) { @@ -1169,7 +1272,7 @@ class egw_vfs extends vfs_stream_wrapper } // we do NOT need to encode % itself, as our path are already url encoded, with the exception of ' ' and '+' // we urlencode double quotes '"', as that fixes many problems in html markup - return '/webdav.php'.strtr($path,array('+' => '%2B',' ' => '%20','"' => '%22')); + return '/webdav.php'.strtr($path,array('+' => '%2B',' ' => '%20','"' => '%22')).($force_download ? '?download' : ''); } /** @@ -1337,6 +1440,35 @@ class egw_vfs extends vfs_stream_wrapper return self::$lock_cache[$path] = $result; } + /** + * Get backend specific information (data and etemplate), to integrate as tab in filemanagers settings dialog + * + * @param string $path + * @param array $content=null + * @return array|boolean array with values for keys 'data','etemplate','name','label','help' or false if not supported by backend + */ + static function getExtraInfo($path,array $content=null) + { + $extra = array(); + if (($extra_info = self::_call_on_backend('extra_info',array($path,$content),true))) // true = fail silent if backend does NOT support it + { + $extra[] = $extra_info; + } + + if (($vfs_extra = $GLOBALS['egw']->hooks->process(array( + 'location' => 'vfs_extra', + 'path' => $path, + 'content' => $content, + )))) + { + foreach($vfs_extra as $app => $data) + { + $extra = $extra ? array_merge($extra, $data) : $data; + } + } + return $extra; + } + /** * Mapps entries of applications to a path for the locking * @@ -1412,6 +1544,234 @@ class egw_vfs extends vfs_stream_wrapper self::$db = isset($GLOBALS['egw_setup']->db) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db; self::$lock_cache = array(); } + + /** + * Returns the URL to the thumbnail of the given file. The thumbnail may simply + * be the mime-type icon, or - if activated - the preview with the given thsize. + * + * @param string $file name of the file + * @param int $thsize the size of the preview - false if the default should be used. + * @param string $mime if you already know the mime type of the file, you can supply + * it here. Otherwise supply "false". + */ + public static function thumbnail_url($file, $thsize = false, $mime = false) + { + // Retrive the mime-type of the file + if (!$mime) + { + $mime = egw_vfs::mime_content_type($file); + } + + $image = ""; + + // Seperate the mime type into the primary and the secondary part + list($mime_main, $mime_sub) = explode('/', $mime); + + if ($mime_main == 'egw') + { + $image = $GLOBALS['egw']->common->image($mime_sub, 'navbar'); + } + else if ($file && $mime_main == 'image' && in_array($mime_sub, array('png','jpeg','jpg','gif','bmp')) && + (string)$GLOBALS['egw_info']['server']['link_list_thumbnail'] != '0' && + (string)$GLOBALS['egw_info']['user']['preferences']['common']['link_list_thumbnail'] != '0' && + (!is_array($value) && ($stat = egw_vfs::stat($file)) ? $stat['size'] : $value['size']) < 1500000) + { + if (substr($file, 0, 6) == '/apps/') + { + $file = parse_url(egw_vfs::resolve_url_symlinks($path), PHP_URL_PATH); + } + + //Assemble the thumbnail parameters + $thparams = array(); + $thparams['path'] = $file; + if ($thsize) + { + $thparams['thsize'] = $thsize; + } + $image = $GLOBALS['egw']->link('/etemplate/thumbnail.php', $thparams); + } + else + { + list($app, $name) = explode("/", egw_vfs::mime_icon($mime), 2); + $image = $GLOBALS['egw']->common->image($app, $name); + } + + return $image; + } + + /** + * Get the configured start directory for the current user + * + * @return string + */ + static public function get_home_dir() + { + $start = '/home/'.$GLOBALS['egw_info']['user']['account_lid']; + + // check if user specified a valid startpath in his prefs --> use it + if (($path = $GLOBALS['egw_info']['user']['preferences']['filemanager']['startfolder']) && + $path[0] == '/' && egw_vfs::is_dir($path) && egw_vfs::check_access($path, egw_vfs::READABLE)) + { + $start = $path; + } + return $start; + } + + /** + * Copies the files given in $src to $dst. + * + * @param array $src contains the source file + * @param string $dst is the destination directory + */ + static public function copy_files(array $src, $dst, &$errs, array &$copied) + { + if (self::is_dir($dst)) + { + foreach ($src as $file) + { + // Check whether the file has already been copied - prevents from + // recursion + if (!in_array($file, $copied)) + { + // Calculate the target filename + $target = egw_vfs::concat($dst, egw_vfs::basename($file)); + + if (self::is_dir($file)) + { + if ($file !== $target) + { + // Create the target directory + egw_vfs::mkdir($target,null,STREAM_MKDIR_RECURSIVE); + + $files = egw_vfs::find($file, array( + "hidden" => true + )); + + $copied[] = $file; + $copied[] = $target; // < newly created folder must not be copied again! + if (egw_vfs::copy_files(egw_vfs::find($file), $target, + $errs, $copied)) + { + continue; + } + } + + $errs++; + } + else + { + // Copy a single file - check whether the file should be + // copied onto itself. + // TODO: Check whether target file already exists and give + // return those files so that a dialog might be displayed + // on the client side which lets the user decide. + if ($target !== $file && egw_vfs::copy($file, $target)) + { + $copied[] = $file; + } + else + { + $errs++; + } + } + } + } + } + + return $errs == 0; + } + + /** + * Moves the files given in src to dst + */ + static public function move_files(array $src, $dst, &$errs, array &$moved) + { + if (egw_vfs::is_dir($dst)) + { + foreach($src as $file) + { + $target = egw_vfs::concat($dst, egw_vfs::basename($file)); + + if ($file != $target && egw_vfs::rename($file, $target)) + { + $moved[] = $file; + } + else + { + ++$errs; + } + } + + return $errs == 0; + } + + return false; + } + + /** + * Copy an uploaded file into the vfs, optionally set some properties (eg. comment or other cf's) + * + * Treat copying incl. properties as atomar operation in respect of notifications (one notification about an added file). + * + * @param array|string $src path to uploaded file or etemplate file array (value for key 'tmp_name') + * @param string $target path or directory to copy uploaded file + * @param array|string $props=null array with properties (name => value pairs, eg. 'comment' => 'FooBar','#cfname' => 'something'), + * array as for proppatch (array of array with values for keys 'name', 'val' and optional 'ns') or string with comment + * @param boolean $check_is_uploaded_file=true should method perform an is_uploaded_file check, default yes + * @return boolean|array stat array on success, false on error + */ + static public function copy_uploaded($src,$target,$props=null,$check_is_uploaded_file=true) + { + $tmp_name = is_array($src) ? $src['tmp_name'] : $src; + + if (self::stat($target) && self::is_dir($target)) + { + $target = self::concat($target, self::encodePathComponent(is_array($src) ? $src['name'] : basename($tmp_name))); + } + if ($check_is_uploaded_file && !is_uploaded_file($tmp_name)) + { + if (self::LOG_LEVEL) error_log(__METHOD__."($tmp_name, $target, ".array2string($props).",$check_is_uploaded_file) returning FALSE !is_uploaded_file()"); + return false; + } + if (!(self::is_writable($target) || self::is_writable(self::dirname($target)))) + { + if (self::LOG_LEVEL) error_log(__METHOD__."($tmp_name, $target, ".array2string($props).",$check_is_uploaded_file) returning FALSE !writable"); + return false; + } + if ($props) + { + if (!is_array($props)) $props = array(array('name' => 'comment','val' => $props)); + + // if $props is name => value pairs, convert it to internal array or array with values for keys 'name', 'val' and optional 'ns' + if (!isset($props[0])) + { + foreach($props as $name => $val) + { + if (($name == 'comment' || $name[0] == '#') && $val) // only copy 'comment' and cfs + { + $vfs_props[] = array( + 'name' => $name, + 'val' => $val, + ); + } + } + $props = $vfs_props; + } + } + if ($props) + { + // set props before copying the file, so notifications already contain them + if (!self::stat($target)) + { + self::touch($target); // create empty file, to be able to attach properties + self::$treat_as_new = true; // notify as new + } + self::proppatch($target, $props); + } + $ret = copy($tmp_name,self::PREFIX.$target) ? self::stat($target) : false; + if (self::LOG_LEVEL > 1 || !$ret && self::LOG_LEVEL) error_log(__METHOD__."($tmp_name, $target, ".array2string($props).") returning ".array2string($ret)); + return $ret; + } } egw_vfs::init_static(); diff --git a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php index 61c31e8f7d..602278872c 100644 --- a/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.sqlfs_stream_wrapper.inc.php @@ -30,7 +30,6 @@ * The stream wrapper interface is according to the docu on php.net * * @link http://www.php.net/manual/en/function.stream-wrapper-register.php - * @ToDo versioning */ class sqlfs_stream_wrapper implements iface_stream_wrapper { @@ -80,10 +79,6 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ const LOG_LEVEL = 1; - /** - * We do versioning AND store the content in the db, NOT YET IMPLEMENTED - */ - const VERSIONING = 0; /** * We store the content in the DB (no versioning) */ @@ -142,7 +137,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * * @var array $path => info-array pairs */ - static private $stat_cache = array(); + static protected $stat_cache = array(); /** * Reference to the PDO object we use * @@ -165,6 +160,21 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ static public $extra_columns = ',fs_link'; + /** + * Clears our stat-cache + * + * Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes! + * + * @param string $path='/' + */ + public static function clearstatcache($path='/') + { + //error_log(__METHOD__."('$path')"); + self::$stat_cache = array(); + + $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl = null); + } + /** * This method is called immediately after your stream object is created. * @@ -174,10 +184,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * - STREAM_USE_PATH If path is relative, search for the resource using the include_path. * - STREAM_REPORT_ERRORS If this flag is set, you are responsible for raising errors using trigger_error() during opening of the stream. * If this flag is not set, you should not raise any errors. - * @param string $opened_path full path of the file/resource, if the open was successfull and STREAM_USE_PATH was set + * @param string &$opened_path full path of the file/resource, if the open was successfull and STREAM_USE_PATH was set + * @param array $overwrite_new=null if set create new file with values overwriten by the given ones + * @param string $class=__CLASS__ class to use to call static methods, eg url_stat (workaround for no late static binding in php < 5.3) + * @todo remove $class parameter and use static::url_stat() once we require PHP5.3! * @return boolean true if the ressource was opened successful, otherwise false */ - function stream_open ( $url, $mode, $options, &$opened_path ) + function stream_open ( $url, $mode, $options, &$opened_path, array $overwrite_new=null, $class=__CLASS__ ) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)"); @@ -189,16 +202,16 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper $this->opened_mode = $mode = str_replace('b','',$mode); // we are always binary, like every Linux system $this->opened_stream = null; - if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET)) || $mode[0] == 'x') // file not found or file should NOT exist + if (!is_null($overwrite_new) || !($stat = call_user_func(array($class,'url_stat'),$path,STREAM_URL_STAT_QUIET)) || $mode[0] == 'x') // file not found or file should NOT exist { if ($mode[0] == 'r' || // does $mode require the file to exist (r,r+) $mode[0] == 'x' || // or file should not exist, but does - !($dir_stat=self::url_stat($dir,STREAM_URL_STAT_QUIET)) || // or parent dir does not exist create it + !($dir_stat=call_user_func(array($class,'url_stat'),$dir,STREAM_URL_STAT_QUIET)) || // or parent dir does not exist create it !egw_vfs::check_access($dir,egw_vfs::WRITABLE,$dir_stat)) // or we are not allowed to create it { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file does not exist or can not be created!"); - if (!($options & STREAM_URL_STAT_QUIET)) + if (($options & STREAM_REPORT_ERRORS)) { trigger_error(__METHOD__."($url,$mode,$options) file does not exist or can not be created!",E_USER_WARNING); } @@ -207,8 +220,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } // new file --> create it in the DB $new_file = true; - $query = 'INSERT INTO '.self::TABLE.' (fs_name,fs_dir,fs_mode,fs_uid,fs_gid,fs_created,fs_modified,fs_creator,fs_mime,fs_size'. - ') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_created,:fs_modified,:fs_creator,:fs_mime,:fs_size)'; + $query = 'INSERT INTO '.self::TABLE.' (fs_name,fs_dir,fs_mode,fs_uid,fs_gid,fs_created,fs_modified,fs_creator,fs_mime,fs_size,fs_active'. + ') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_created,:fs_modified,:fs_creator,:fs_mime,:fs_size,:fs_active)'; if (self::LOG_LEVEL > 2) $query = '/* '.__METHOD__.': '.__LINE__.' */ '.$query; $stmt = self::$pdo->prepare($query); $values = array( @@ -225,7 +238,9 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper 'fs_creator' => egw_vfs::$user, 'fs_mime' => 'application/octet-stream', // required NOT NULL! 'fs_size' => 0, + 'fs_active' => self::_pdo_boolean(true), ); + if ($overwrite_new) $values = array_merge($values,$overwrite_new); if (!$stmt->execute($values) || !($this->opened_fs_id = self::$pdo->lastInsertId('egw_sqlfs_fs_id_seq'))) { $this->opened_stream = $this->opened_path = $this->opened_mode = null; @@ -245,6 +260,17 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper self::mkdir_recursive($fs_dir,0700,true); } } + // check if opend file is a directory + elseif($stat && ($stat['mode'] & self::MODE_DIR) == self::MODE_DIR) + { + if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) Is a directory!"); + if (($options & STREAM_REPORT_ERRORS)) + { + trigger_error(__METHOD__."($url,$mode,$options) Is a directory!",E_USER_WARNING); + } + $this->opened_stream = $this->opened_path = $this->opened_mode = null; + return false; + } else { if ($mode == 'r' && !egw_vfs::check_access($url,egw_vfs::READABLE ,$stat) ||// we are not allowed to read @@ -253,7 +279,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper self::_remove_password($url); $op = $mode == 'r' ? 'read' : 'edited'; if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file can not be $op!"); - if (!($options & STREAM_URL_STAT_QUIET)) + if (($options & STREAM_REPORT_ERRORS)) { trigger_error(__METHOD__."($url,$mode,$options) file can not be $op!",E_USER_WARNING); } @@ -514,13 +540,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * @param string $url * @return boolean TRUE on success or FALSE on failure */ - static function unlink ( $url ) + static function unlink ( $url, $parent_stat=null ) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)"); $path = parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,STREAM_URL_STAT_LINK)) || !egw_vfs::check_access(egw_vfs::dirname($path),egw_vfs::WRITABLE)) + if (!($stat = self::url_stat($path,STREAM_URL_STAT_LINK)) || !egw_vfs::check_access(egw_vfs::dirname($path),egw_vfs::WRITABLE, $parent_stat)) { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url) permission denied!"); @@ -569,12 +595,14 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url_from,$url_to)"); $path_from = parse_url($url_from,PHP_URL_PATH); + $from_dir = egw_vfs::dirname($path_from); $path_to = parse_url($url_to,PHP_URL_PATH); $to_dir = egw_vfs::dirname($path_to); $operation = self::url2operation($url_from); // we have to use array($class,'url_stat'), as $class.'::url_stat' requires PHP 5.2.3 and we currently only require 5.2+ - if (!($from_stat = call_user_func(array($class,'url_stat'),$path_from,0)) || !egw_vfs::check_access(dirname($path_from),egw_vfs::WRITABLE)) + if (!($from_stat = call_user_func(array($class,'url_stat'),$path_from,0)) || + !egw_vfs::check_access($from_dir,egw_vfs::WRITABLE,$from_dir_stat = call_user_func(array($class,'url_stat'),$from_dir,0))) { self::_remove_password($url_from); self::_remove_password($url_to); @@ -609,11 +637,12 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper unset(self::$stat_cache[$path_from]); unset(self::$stat_cache[$path_to]); - $stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_dir=:fs_dir,fs_name=:fs_name WHERE fs_id=:fs_id'); + $stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_dir=:fs_dir,fs_name=:fs_name WHERE fs_dir=:old_dir AND fs_name=:old_name'); $ok = $stmt->execute(array( - 'fs_dir' => $to_dir_stat['ino'], - 'fs_name' => egw_vfs::basename($path_to), - 'fs_id' => $from_stat['ino'], + 'fs_dir' => $to_dir_stat['ino'], + 'fs_name' => egw_vfs::basename($path_to), + 'old_dir' => $from_dir_stat['ino'], + 'old_name' => $from_stat['name'], )); unset($stmt); @@ -635,7 +664,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper /** * due to problems with recursive directory creation, we have our own here */ - function mkdir_recursive($pathname, $mode, $depth=0) + private static function mkdir_recursive($pathname, $mode, $depth=0) { $maxdepth=10; $depth2propagate = (int)$depth + 1; @@ -665,7 +694,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) already exist!"); - if (!($options & STREAM_URL_STAT_QUIET)) + if (!($options & STREAM_REPORT_ERRORS)) { //throw new Exception(__METHOD__."('$url',$mode,$options) already exist!"); trigger_error(__METHOD__."('$url',$mode,$options) already exist!",E_USER_WARNING); @@ -691,7 +720,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) permission denied!"); - if (!($options & STREAM_URL_STAT_QUIET)) + if (!($options & STREAM_REPORT_ERRORS)) { trigger_error(__METHOD__."('$url',$mode,$options) permission denied!",E_USER_WARNING); } @@ -756,7 +785,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper $err_msg = __METHOD__."($url,$options) ".(!$stat ? 'not found!' : ($stat['mime'] != self::DIR_MIME_TYPE ? 'not a directory!' : 'permission denied!')); if (self::LOG_LEVEL) error_log($err_msg); - if (!($options & STREAM_URL_STAT_QUIET)) + if (!($options & STREAM_REPORT_ERRORS)) { trigger_error($err_msg,E_USER_WARNING); } @@ -768,7 +797,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$options) dir is not empty!"); - if (!($options & STREAM_URL_STAT_QUIET)) + if (!($options & STREAM_REPORT_ERRORS)) { trigger_error(__METHOD__."('$url',$options) dir is not empty!",E_USER_WARNING); } @@ -971,7 +1000,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } $this->opened_dir = array(); $query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified'.self::$extra_columns. - ' FROM '.self::TABLE." WHERE fs_dir=? ORDER BY fs_mime='httpd/unix-directory' DESC, fs_name ASC"; + ' FROM '.self::TABLE.' WHERE fs_dir=? AND fs_active='.self::_pdo_boolean(true). + " ORDER BY fs_mime='httpd/unix-directory' DESC, fs_name ASC"; //if (self::LOG_LEVEL > 2) $query = '/* '.__METHOD__.': '.__LINE__.' */ '.$query; if (self::LOG_LEVEL > 2) $query = '/* '.__METHOD__."($url,$options)".' */ '.$query; @@ -1020,7 +1050,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ static function url_stat ( $url, $flags, $eacl_access=null ) { - if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags)"); + if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags,$eacl_access)"); $path = parse_url($url,PHP_URL_PATH); @@ -1044,7 +1074,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper self::_pdo(); } $base_query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified'.self::$extra_columns. - ' FROM '.self::TABLE.' WHERE fs_name'.self::$case_sensitive_equal.'? AND fs_dir='; + ' FROM '.self::TABLE.' WHERE fs_active='.self::_pdo_boolean(true). + ' AND fs_name'.self::$case_sensitive_equal.'? AND fs_dir='; $parts = explode('/',$path); // if we have extendes acl access to the url, we dont need and can NOT include the sql for the readable check @@ -1075,7 +1106,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper // we also store negatives (all methods creating new files/dirs have to unset the stat-cache!) return self::$stat_cache[$path] = false; } - $query = 'SELECT fs_id FROM '.self::TABLE.' WHERE fs_dir=('.$query.') AND fs_name'.self::$case_sensitive_equal.self::$pdo->quote($name); + $query = 'SELECT fs_id FROM '.self::TABLE.' WHERE fs_dir=('.$query.') AND fs_active='. + self::_pdo_boolean(true).' AND fs_name'.self::$case_sensitive_equal.self::$pdo->quote($name); // if we are not root AND have no extended acl access, we need to make sure the user has the right to tranverse all parent directories (read-rights) if (!egw_vfs::$is_root && !$eacl_access) @@ -1118,7 +1150,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * * @return string */ - private function _sql_readable() + protected function _sql_readable() { static $sql_read_acl; @@ -1283,7 +1315,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * Read the extended acl via acl::get_grants('sqlfs') * */ - private static function _read_extended_acl() + static protected function _read_extended_acl() { if ((self::$extended_acl = $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME)) != false) { @@ -1385,7 +1417,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper */ function get_eacl($path) { - if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET))) + if (!($stat = self::url_stat($_path=$path,STREAM_URL_STAT_QUIET))) { error_log(__METHOD__.__LINE__.' '.array2string($path).' not found!'); return false; // not found @@ -1402,11 +1434,11 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } if (($path = egw_vfs::dirname($path))) { - return array_merge((array)self::get_eacl($path),$eacls); + $eacls = array_merge((array)self::get_eacl($path),$eacls); } // sort by length descending, to show precedence usort($eacls,create_function('$a,$b','return strlen($b["path"])-strlen($a["path"]);')); - + //error_log(__METHOD__."('$_path') returning ".array2string($eacls)); return $eacls; } @@ -1486,7 +1518,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * @param array $info * @return array */ - static private function _vfsinfo2stat($info) + static protected function _vfsinfo2stat($info) { $stat = array( 'ino' => $info['fs_id'], @@ -1563,7 +1595,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper self::$pdo = new PDO($dsn,$egw_db->User,'$egw_db->Password'); } // set client charset of the connection - $charset = $GLOBALS['egw']->translation->charset(); + $charset = translation::charset(); switch(self::$pdo_type) { case 'mysql': @@ -1586,7 +1618,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * @param mixed $time * @return string Y-m-d H:i:s */ - static private function _pdo_timestamp($time) + static protected function _pdo_timestamp($time) { if (is_numeric($time)) { @@ -1668,7 +1700,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * * @param string &$url */ - static private function _remove_password(&$url) + static protected function _remove_password(&$url) { $parts = parse_url($url); @@ -1711,7 +1743,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper * Store properties for a single ressource (file or dir) * * @param string|int $path string with path or integer fs_id - * @param array $props array or array with values for keys 'name', 'ns', 'val' (null to delete the prop) + * @param array $props array of array with values for keys 'name', 'ns', 'val' (null to delete the prop) * @return boolean true if props are updated, false otherwise (eg. ressource not found) */ static function proppatch($path,array $props) @@ -1814,7 +1846,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper } if (!is_array($path_ids)) { - $props = $props[$row['fs_id']]; + $props = $props[$row['fs_id']] ? $props[$row['fs_id']] : array(); // return empty array for no props } elseif ($props && isset($stat)) // need to map fs_id's to pathes { diff --git a/phpgwapi/inc/class.vfs_webdav_server.inc.php b/phpgwapi/inc/class.vfs_webdav_server.inc.php index a1232a7c00..f69bfda3b6 100644 --- a/phpgwapi/inc/class.vfs_webdav_server.inc.php +++ b/phpgwapi/inc/class.vfs_webdav_server.inc.php @@ -62,16 +62,9 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem // special treatment for litmus compliance test // reply on its identifier header // not needed for the test itself but eases debugging - if (function_exists('apache_request_headers')) - { - foreach (apache_request_headers() as $key => $value) - { - if (stristr($key, 'litmus')) - { - error_log("Litmus test $value"); - header('X-Litmus-reply: '.$value); - } - } + if (isset($this->_SERVER['HTTP_X_LITMUS'])) { + error_log("Litmus test ".$this->_SERVER['HTTP_X_LITMUS']); + header("X-Litmus-reply: ".$this->_SERVER['HTTP_X_LITMUS']); } // let the base class do all the work HTTP_WebDAV_Server::ServeRequest(); @@ -135,7 +128,28 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem $source = $this->base .$options["path"]; if (!file_exists($source)) return "404 Not found"; + if (is_dir($source)) { // resource is a collection + switch ($options["depth"]) { + case "infinity": // valid + break; + case "0": // valid for COPY only + if ($del) { // MOVE? + return "400 Bad request"; + } + break; + case "1": // invalid for both COPY and MOVE + default: + return "400 Bad request"; + } + } + $dest = $this->base . $options["dest"]; + $destdir = dirname($dest); + + if (!file_exists($destdir) || !is_dir($destdir)) { + return "409 Conflict"; + } + $new = !file_exists($dest); $existing_col = false; @@ -165,11 +179,6 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem } } - if (is_dir($source) && ($options["depth"] != "infinity")) { - // RFC 2518 Section 9.2, last paragraph - return "400 Bad request"; - } - if ($del) { if (!rename($source, $dest)) { return "500 Internal server error"; @@ -189,9 +198,8 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem mysql_query($query); */ } else { - if (is_dir($source)) { - $files = System::find($source); - $files = array_reverse($files); + if (is_dir($source) && $options['depth'] == 'infinity') { + $files = egw_vfs::find($source,array('depth' => true,'url' => true)); // depth=true: return dirs first, url=true: allow urls! } else { $files = array($source); } @@ -243,6 +251,9 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem */ function fileinfo($path) { + // internally we require some url-encoding, as vfs_stream_wrapper uses URL's internally + $path = str_replace(array('#','?'),array('%23','%3F'),$path); + //error_log(__METHOD__."($path)"); // map URI path to filesystem path $fspath = $this->base . $path; @@ -251,15 +262,24 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem $info = array(); // TODO remove slash append code when base class is able to do it itself $info['path'] = is_dir($fspath) ? $this->_slashify($path) : $path; + + // remove all urlencoding we need internally in EGw, HTTP_WebDAV_Server will add it's own! + // rawurldecode does NOT touch + + $info['path'] = rawurldecode($info['path']); + $info['props'] = array(); // no special beautified displayname here ... - $info['props'][] = HTTP_WebDAV_Server::mkprop ('displayname', egw_vfs::basename(self::_unslashify($path))); + $info['props'][] = HTTP_WebDAV_Server::mkprop ('displayname', egw_vfs::basename(self::_unslashify($info['path']))); // creation and modification time $info['props'][] = HTTP_WebDAV_Server::mkprop ('creationdate', filectime($fspath)); $info['props'][] = HTTP_WebDAV_Server::mkprop ('getlastmodified', filemtime($fspath)); + // Microsoft extensions: last access time and 'hidden' status + $info["props"][] = HTTP_WebDAV_Server::mkprop("lastaccessed", fileatime($fspath)); + $info["props"][] = HTTP_WebDAV_Server::mkprop("ishidden", egw_vfs::is_hidden($fspath)); + // type and size (caller already made sure that path exists) if (is_dir($fspath)) { // directory (WebDAV collection) @@ -292,7 +312,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem */ // ToDo: etag from inode and modification time - //error_log(__METHOD__."($path) info=".print_r($info,true)); + //error_log(__METHOD__."($path) info=".array2string($info)); return $info; } @@ -395,6 +415,28 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem return egw_vfs::mime_content_type($path); } + /** + * Check if path is readable by current user + * + * @param string $fspath + * @return boolean + */ + function _is_readable($fspath) + { + return egw_vfs::is_readable($fspath); + } + + /** + * Check if path is writable by current user + * + * @param string $fspath + * @return boolean + */ + function _is_writable($fspath) + { + return egw_vfs::is_writable($fspath); + } + /** * PROPPATCH method handler * @@ -527,7 +569,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem { return egw_vfs::checkLock($path); } - + /** * GET method handler for directories * @@ -543,4 +585,45 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem parent::GetDir($fspath, $options); } + + private $force_download = false; + + /** + * Constructor + * + * Reimplement to add a Content-Disposition header, if ?download is appended to the REQUEST_URI + */ + function __construct() + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' && ($this->force_download = strpos($_SERVER['REQUEST_URI'],'?download'))) + { + $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'],0,$this->force_download); + } + parent::HTTP_WebDAV_Server(); + } + + /** + * GET method handler + * + * Reimplement to add a Content-Disposition header, if ?download is appended to the REQUEST_URI + * + * @param array parameter passing array + * @return bool true on success + */ + function GET(&$options) + { + if (($ok = parent::GET($options)) && $this->force_download) + { + if(html::$user_agent == 'msie') // && self::$ua_version == '5.5') + { + $attachment = ''; + } + else + { + $attachment = ' attachment;'; + } + header('Content-disposition:'.$attachment.' filename="'.egw_vfs::basename($options['path']).'"'); + } + return $ok; + } } \ No newline at end of file diff --git a/webdav.php b/webdav.php index 4d091fe99b..eb427dbf67 100644 --- a/webdav.php +++ b/webdav.php @@ -32,26 +32,11 @@ function check_access(&$account) return egw_digest_auth::autocreate_session_callback($account); } -// if we are called with a /apps/$app path, use that $app as currentapp, to not require filemanager rights for the links -$parts = explode('/',isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : $_SERVER['ORIG_PATH_INFO']); -//error_log("webdav: explode".print_r($parts,true)); -if(count($parts) == 1) -{ - error_log(__METHOD__. "Malformed Url: missing slash:\n".$_SERVER['SERVER_NAME']."\n PATH_INFO:".$_SERVER['PATH_INFO']. - "\n REQUEST_URI".$_SERVER['REQUEST_URI']."\n ORIG_SCRIPT_NAME:".$_SERVER['ORIG_SCRIPT_NAME']. - "\n REMOTE_ADDR:".$_SERVER['REMOTE_ADDR']."\n PATH_INFO:".$_SERVER['PATH_INFO']."\n HTTP_USER_AGENT:".$_SERVER['HTTP_USER_AGENT']) ; - header("HTTP/1.1 501 Not implemented"); - header("X-WebDAV-Status: 501 Not implemented", true); - exit; -} - -$app = count($parts) > 3 && $parts[1] == 'apps' ? $parts[2] : 'filemanager'; - $GLOBALS['egw_info'] = array( 'flags' => array( 'disable_Template_class' => True, 'noheader' => True, - 'currentapp' => $app, + 'currentapp' => preg_match('|/webdav.php/apps/([A-Za-z0-9_-]+)/|', $_SERVER['REQUEST_URI'], $matches) ? $matches[1] : 'filemanager', 'autocreate_session_callback' => 'check_access', 'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm) 'auth_realm' => 'EGroupware WebDAV server', // cant use vfs_webdav_server::REALM as autoloading and include path not yet setup! @@ -94,4 +79,4 @@ if (strstr($user_agent, 'microsoft-webdav') !== false || $webdav_server->cnrnd = true; } $webdav_server->ServeRequest(); -//error_log(sprintf("WebDAV %s request took %5.3f s (header include took %5.3f s)",$_SERVER['REQUEST_METHOD'],microtime(true)-$starttime,$headertime-$starttime)); +//error_log(sprintf('WebDAV %s request: status "%s", took %5.3f s'.($headertime?' (header include took %5.3f s)':''),$_SERVER['REQUEST_METHOD'].' '.$_SERVER['PATH_INFO'],$webdav_server->_http_status,microtime(true)-$starttime,$headertime-$starttime));