stream wrapper interface is now eGW VFS interface

- if you already run the 1.5.003 update (AND modified anything in the VFS), you have to re-run it, to not loose your modifications or risk an inconsistent VFS (DB does not match filesystem)
- to re-run the 1.5.003 update (only if your version is already 1.5.003 or bigger!) run the following sql:
UPDATE egw_applications SET app_version=1.5.002 WHERE app_name=phpgwapi
- the new vfs supports now an extended ACL, if that is supported by the backend (sqlfs only currently)
- eacl allows to set separate recursive acl rights for different users or groups on a directory (and subdirs)
- former group grants of group dirs are converted to eacl, thought we only support read or read+write access (no extra add or delete)
- attachments via the links class now also use a stream wrapper interface (links_stream_wrapper) and WebDAV as download handler (which requires no longer filemanager run rights)
This commit is contained in:
Ralf Becker 2008-04-14 05:52:24 +00:00
parent 540901e9c7
commit 501df49cbb
11 changed files with 786 additions and 456 deletions

View File

@ -21,9 +21,6 @@
*/
class bolink extends egw_link
{
var $public_functions = array(
'get_file' => true,
);
/**
* @deprecated use egw_link::VFS_APPNAME
*/
@ -39,51 +36,6 @@ class bolink extends egw_link
*/
function __construct()
{
error_log('Call to depricated bolink class from '.function_backtrace(1));
}
/**
* Download an attached file
*
* @todo replace it with egw_vfs::download_url, once egw_vfs/webdav supports the attachments
* @param array $link=null
* @return array with params (eg. menuaction) for download link
*/
function get_file(array $link=null)
{
if (is_array($link))
{
return array(
'menuaction' => 'phpgwapi.bolink.get_file',
'app' => $link['app2'],
'id' => $link['id2'],
'filename' => $link['id']
);
}
$app = $_GET['app'];
$id = $_GET['id'];
$filename = $_GET['filename'];
if (empty($app) || empty($id) || empty($filename) || !$this->title($app,$id))
{
$GLOBALS['egw']->framework->render('<h1 style="text-align: center; color: red;">'.lang('Access not permitted')." !!!</h1>\n",
lang('Access not permitted'),true);
$GLOBALS['egw']->common->egw_exit();
}
$browser = new browser();
$local = $this->attached_local($app,$id,$filename,$_SERVER['REMOTE_ADDR'],$browser->is_windows());
if ($local)
{
Header('Location: ' . $local);
}
else
{
$info = $this->info_attached($app,$id,$filename);
$browser->content_header($filename,$info['type']);
echo $this->read_attached($app,$id,$filename);
}
$GLOBALS['egw']->common->egw_exit();
}
}

View File

@ -46,7 +46,8 @@
* 'add_id' => 'link_id', // --------------------- " ------------------- id
* 'add_popup' => '400x300', // size of popup (XxY), if add is in popup
* 'notify' => 'app.class.method', // method to be called if an other applications liks or unlinks with app: notify(array $data)
* );
* 'file_access' => 'app.class.method', // method to be called to check file access rights, see links_stream_wrapper class
* ); // boolean file_access(string $id,int $check,string $rel_path)
* }
* All entries are optional, thought you only get conected functionality, if you implement them ...
*
@ -62,12 +63,6 @@
*/
class egw_link extends solink
{
/**
* Basepath for attached files
*
* @todo change to '/apps' once the new vfs is complete
*/
const VFS_BASEDIR = '/infolog';
/**
* appname used for returned attached files (!= 'filemanager'!)
*/
@ -90,14 +85,6 @@ class egw_link extends solink
'add_popup' => '700x750',
),
);
/**
* Instance of the vfs class
*
* @var vfs
*/
private static $vfs;
private static $link_pathes = array();
private static $send_file_ips = array();
/**
* Caches link titles for a better performance
*
@ -119,11 +106,6 @@ class egw_link extends solink
*/
static function init_static( )
{
self::$vfs = new vfs();
self::$link_pathes =& $GLOBALS['egw_info']['server']['link_pathes'];
self::$send_file_ips =& $GLOBALS['egw_info']['server']['send_file_ips'];
// other apps can participate in the linking by implementing a search_link hook, which
// has to return an array in the format of an app_register entry
// for performance reasons, we do it only once / cache it in the session
@ -557,19 +539,7 @@ class egw_link extends solink
}
if (is_array($link))
{
$size = $link['size'];
if ($size_k = (int)($size / 1024))
{
if ((int)($size_k / 1024))
{
$size = sprintf('%3.1dM',doubleval($size_k)/1024.0);
}
else
{
$size = $size_k.'k';
}
}
$extra = ': '.$link['type'] . ' '.$size;
$extra = ': '.$link['type'] . ' '.egw_vfs::hsize($link['size']);
}
return self::$title_cache[$app.':'.$id] = $id.$extra;
}
@ -662,7 +632,6 @@ class egw_link extends solink
/**
* view entry $id of $app
*
* @ToDo use webdav url of new vfs, once ACL for /apps is ready
* @param string $app appname
* @param string $id id in $app
* @param array $link=null link-data for file-attachments
@ -672,12 +641,7 @@ class egw_link extends solink
{
if ($app == self::VFS_APPNAME && !empty($id) && is_array($link))
{
return array(
'menuaction' => 'phpgwapi.bolink.get_file',
'app' => $link['app2'],
'id' => $link['id2'],
'filename' => $link['id']
);
return egw_vfs::download_url(parse_url(self::vfs_path($link['app2'],$link['id2'],$link['id']),PHP_URL_PATH));
}
if ($app == '' || !is_array($reg = self::$app_register[$app]) || !isset($reg['view']) || !isset($reg['view_id']))
{
@ -710,11 +674,21 @@ class egw_link extends solink
*/
static function is_popup($app,$action='view')
{
if (!($reg = self::$app_register[$app]) || !$reg[$action.'_popup'])
{
return false;
}
return $reg[$action.'_popup'];
return self::get_registry($app,$action.'_popup');
}
/**
* Check if $app is in the registry and has an entry for $name
*
* @param string $app app-name
* @param string $name name / key in the registry, eg. 'view'
* @return boolean/string false if $app is not registered, otherwise string with the value for $name
*/
static function get_registry($app,$name)
{
$reg = self::$app_register[$app];
return isset($reg) ? $reg[$name] : false;
}
/**
@ -723,26 +697,30 @@ class egw_link extends solink
* All link-files are based in the vfs-subdir '/infolog'. For other apps
* separate subdirs with name app are created.
*
* @ToDo change to new VFS_BASEDIR /apps (incl. /apps/infolog)
* @param string $app appname
* @param string $id='' id in $app
* @param string $file='' filename
* @param boolean/array $relatives=False return path as array with path in string incl. relatives
* @return string/array path or array with path and relatives, depending on $relatives
*/
static function vfs_path($app,$id='',$file='',$relatives=False)
static function vfs_path($app,$id='',$file='')
{
$path = self::VFS_BASEDIR . ($app == '' || $app == 'infolog' ? '' : '/'.$app) .
($id != '' ? '/' . $id : '') . ($file != '' ? '/' . $file : '');
if (self::DEBUG)
$path = links_stream_wrapper::BASEURL;
if ($app)
{
echo "<p>egw_link::vfs_path('$app','$id','$file') = '$path'</p>\n";
$path .= '/'.$app;
if ($id)
{
$path .= '/'.$id;
if ($file)
{
$path .= '/'.$file;
}
}
}
return $relatives ? array(
'string' => $path,
'relatives' => is_array($relatives) ? $relatives : array($relatives)
) : $path;
//error_log(__METHOD__."($app,$id,$file)=$path");
return $path;
}
/**
@ -757,7 +735,8 @@ class egw_link extends solink
* $file['path'] path of the file on the client computer
* $file['ip'] of the client (path and ip are only needed if u want a symlink (if possible))
* @param string $comment='' comment to add to the link
* @return int negative id of phpgw_vfs table as negative link-id's are for vfs attachments
* @todo remark/comment from the vfs
* @return int negative id of egw_sqlfs table as negative link-id's are for vfs attachments
*/
static function attach_file($app,$id,$file,$comment='')
{
@ -765,73 +744,35 @@ class egw_link extends solink
{
echo "<p>attach_file: app='$app', id='$id', tmp_name='$file[tmp_name]', name='$file[name]', size='$file[size]', type='$file[type]', path='$file[path]', ip='$file[ip]', comment='$comment'</p>\n";
}
// create the root for attached files in infolog, if it does not exists
$vfs_data = array('string'=>self::VFS_BASEDIR,'relatives'=>array(RELATIVE_ROOT));
if (!(self::$vfs->file_exists($vfs_data)))
{
self::$vfs->override_acl = 1;
self::$vfs->mkdir($vfs_data);
self::$vfs->override_acl = 0;
}
$app_dir = self::vfs_path($app);
$vfs_data = self::vfs_path($app,False,False,RELATIVE_ROOT);
if (!(self::$vfs->file_exists($vfs_data)))
{
self::$vfs->override_acl = 1;
self::$vfs->mkdir($vfs_data);
self::$vfs->override_acl = 0;
}
$vfs_data = self::vfs_path($app,$id,False,RELATIVE_ROOT);
if (!(self::$vfs->file_exists($vfs_data)))
{
self::$vfs->override_acl = 1;
self::$vfs->mkdir($vfs_data);
self::$vfs->override_acl = 0;
}
$fname = self::vfs_path($app,$id,$file['name']);
$tfname = '';
if (!empty($file['path']) && is_array(self::$link_pathes) && count(self::$link_pathes))
{
$file['path'] = str_replace('\\\\','/',$file['path']); // vfs uses only '/'
@reset(self::$link_pathes);
while ((list($valid,$trans) = @each(self::$link_pathes)) && !$tfname)
{ // check case-insensitive for WIN etc.
$check = $valid[0] == '\\' || strpos(':',$valid) !== false ? 'eregi' : 'ereg';
$valid2 = str_replace('\\','/',$valid);
//echo "<p>attach_file: ereg('".self::$send_file_ips[$valid]."', '$file[ip]')=".ereg(self::$send_file_ips[$valid],$file['ip'])."</p>\n";
if ($check('^('.$valid2.')(.*)$',$file['path'],$parts) &&
ereg(self::$send_file_ips[$valid],$file['ip']) && // right IP
self::$vfs->file_exists(array('string'=>$trans.$parts[2],'relatives'=>array(RELATIVE_NONE|VFS_REAL))))
{
$tfname = $trans.$parts[2];
}
//echo "<p>attach_file: full_fname='$file[path]', valid2='$valid2', trans='$trans', check=$check, tfname='$tfname', parts=(x,'${parts[1]}','${parts[2]}')</p>\n";
}
if ($tfname && !self::$vfs->securitycheck(array('string'=>$tfname)))
{
return False; //lang('Invalid filename').': '.$tfname;
}
}
self::$vfs->override_acl = 1;
self::$vfs->cp(array(
'symlink' => !!$tfname, // try a symlink
'from' => $tfname ? $tfname : $file['tmp_name'],
'to' => $fname,
'relatives' => array(RELATIVE_NONE|VFS_REAL,RELATIVE_ROOT),
));
self::$vfs->set_attributes(array(
'string' => $fname,
'relatives' => array (RELATIVE_ROOT),
'attributes' => array (
'mime_type' => $file['type'],
'comment' => stripslashes ($comment),
'app' => $app
)));
self::$vfs->override_acl = 0;
// we dont want an owner, as this would give rights independent of the apps ACL
$current_user = egw_vfs::$user; egw_vfs::$user = 0;
$link = self::info_attached($app,$id,$file['name']);
return is_array($link) ? $link['link_id'] : False;
$Ok = true;
if (!file_exists($app_dir))
{
egw_vfs::$is_root = true;
$Ok = mkdir($app_dir,0700,true);
if (!$Ok) echo "<p>Can't mkdir($app_dir,0700,true)!</p>\n";
egw_vfs::$is_root = false;
}
$entry_dir = self::vfs_path($app,$id);
if ($Ok && !file_exists($entry_dir))
{
$Ok = mkdir($entry_dir,0700);
}
egw_vfs::$user = $current_user;
if ($Ok)
{
$Ok = copy($file['tmp_name'],$fname = $entry_dir.'/'.$file['name']) &&
($stat = links_stream_wrapper::url_stat($fname,0));
}
// todo: set comment
return $Ok ? -$stat['ino'] : false;
}
/**
@ -845,34 +786,24 @@ class egw_link extends solink
{
if ((int)$app > 0) // is file_id
{
$link = self::fileinfo2link($file_id=$app);
$app = $link['app2'];
$id = $link['id2'];
$fname = $link['id'];
$url = links_stream_wrapper::PREFIX.links_stream_wrapper::id2path($app);
}
else
{
if (empty($app) || empty($id))
{
return False; // dont delete more than all attachments of an entry
}
$url = self::vfs_path($app,$id,$fname);
}
if (self::DEBUG)
{
echo "<p>egw_link::delete_attached('$app','$id','$fname') file_id=$file_id</p>\n";
echo "<p>egw_link::delete_attached('$app','$id','$fname') url=$url</p>\n";
}
if (empty($app) || empty($id))
if (($Ok = egw_vfs::remove($url,true)) && ((int)$app > 0 || $fname))
{
return False; // dont delete more than all attachments of an entry
}
$vfs_data = self::vfs_path($app,$id,$fname,RELATIVE_ROOT);
$Ok = false;
if (self::$vfs->file_exists($vfs_data))
{
self::$vfs->override_acl = 1;
$Ok = self::$vfs->delete($vfs_data);
self::$vfs->override_acl = 0;
}
// if filename given (and now deleted) check if dir is empty and remove it in that case
if ($fname && !count(self::$vfs->ls($vfs_data=self::vfs_path($app,$id,'',RELATIVE_ROOT))))
{
self::$vfs->override_acl = 1;
self::$vfs->delete($vfs_data);
self::$vfs->override_acl = 0;
// try removing the dir, in case it's empty
@rmdir(egw_vfs::dirname($url));
}
return $Ok;
}
@ -887,52 +818,44 @@ class egw_link extends solink
*/
static function info_attached($app,$id,$filename)
{
self::$vfs->override_acl = 1;
$attachments = self::$vfs->ls(self::vfs_path($app,$id,$filename,RELATIVE_NONE));
self::$vfs->override_acl = 0;
if (!count($attachments) || !$attachments[0]['name'])
$url = self::vfs_path($app,$id,$filename);
if (!($stat = links_stream_wrapper::url_stat($url,STREAM_URL_STAT_QUIET)))
{
return False;
return false;
}
return self::fileinfo2link($attachments[0]);
return self::fileinfo2link($stat,$url);
}
/**
* converts a fileinfo (row in the vfs-db-table) in a link
*
* @param array/int $fileinfo a row from the vfs-db-table (eg. returned by the vfs ls static function) or a file_id of that table
* @todo remark/comment from the vfs
* @return array a 'kind' of link-array
*/
static function fileinfo2link($fileinfo)
static function fileinfo2link($fileinfo,$url=null)
{
if (!is_array($fileinfo))
{
$fileinfo = self::$vfs->ls(array('file_id' => $fileinfo));
list(,$fileinfo) = each($fileinfo);
if (!is_array($fileinfo))
$url = links_stream_wrapper::id2path($fileinfo);
if (!($fileinfo = links_stream_wrapper::url_stat($url,STREAM_URL_STAT_QUIET)))
{
return False;
return false;
}
}
$lastmod = $fileinfo[!empty($fileinfo['modified']) ? 'modified' : 'created'];
list($y,$m,$d) = explode('-',$lastmod);
$lastmod = mktime(0,0,0,$m,$d,$y);
$dir_parts = array_reverse(explode('/',$fileinfo['directory']));
list(,,,,$app,$id) = explode('/',$url); // links://apps/$app/$id
return array(
'app' => self::VFS_APPNAME,
'id' => $fileinfo['name'],
'app2' => $dir_parts[1],
'id2' => $dir_parts[0],
'remark' => $fileinfo['comment'],
'owner' => $fileinfo['owner_id'],
'link_id' => -$fileinfo['file_id'],
'lastmod' => $lastmod,
'app2' => $app,
'id2' => $id,
'remark' => '',
'owner' => $fileinfo['uid'],
'link_id' => -$fileinfo['ino'],
'lastmod' => $fileinfo['mtime'],
'size' => $fileinfo['size'],
'type' => $fileinfo['mime_type']
'type' => $fileinfo['mime'],
);
}
@ -945,97 +868,18 @@ class egw_link extends solink
*/
static function list_attached($app,$id)
{
self::$vfs->override_acl = 1;
$attachments = self::$vfs->ls(self::vfs_path($app,$id,False,RELATIVE_ROOT));
self::$vfs->override_acl = 0;
$url = self::vfs_path($app,$id);
//error_log(__METHOD__."($app,$id) url=$url");
if (!count($attachments) || !$attachments[0]['name'])
$attached = array();
foreach(egw_vfs::find($url,array('url'=>true,'need_mime'=>true,'type'=>'f'),true) as $url => $fileinfo)
{
return False;
}
foreach($attachments as $fileinfo)
{
$link = self::fileinfo2link($fileinfo);
$link = self::fileinfo2link($fileinfo,$url);
$attached[$link['link_id']] = $link;
}
return $attached;
}
/**
* checks if path starts with a '\\' or has a ':' in it
*
* @param string $path path to check
* @return boolean true if windows path, false otherwise
*/
static function is_win_path($path)
{
return $path{0} == '\\' || $path{1} == ':';
}
/**
* reads the attached file and returns the content
*
* @param string $app appname
* @param string $id id in app
* @param string $filename filename
* @return string/boolean content of the attached file, null if $id not found, false if no view perms
*/
static function read_attached($app,$id,$filename)
{
$ret = null;
if (empty($app) || !$id || empty($filename) || !($ret = self::title($app,$id)))
{
return $ret;
}
self::$vfs->override_acl = 1;
$data = self::$vfs->read(self::vfs_path($app,$id,$filename,RELATIVE_ROOT));
self::$vfs->override_acl = 0;
return $data;
}
/**
* Checks if filename should be local availible and if so returns
*
* @param string $app appname
* @param string $id id in app
* @param string $filename filename
* @param string $id ip-address of user
* @param boolean $win_user true if user is on windows, otherwise false
* @return string 'file:/path' for HTTP-redirect else return False
*/
static function attached_local($app,$id,$filename,$ip,$win_user)
{
//echo "<p>attached_local(app=$app, id='$id', filename='$filename', ip='$ip', win_user='$win_user', count(send_file_ips)=".count(self::$send_file_ips).")</p>\n";
if (!$id || !$filename || /* !self::check_access($info_id,EGW_ACL_READ) || */
!count(self::$send_file_ips))
{
return False;
}
$link = self::$vfs->ls(self::vfs_path($app,$id,$filename,RELATIVE_ROOT)+array('readlink'=>True));
$link = @$link[0]['symlink'];
if ($link && is_array(self::$link_pathes))
{
reset(self::$link_pathes); $fname = '';
while ((list($valid,$trans) = each(self::$link_pathes)) && !$fname)
{
if (!self::is_win_path($valid) == !$win_user && // valid for this OS
$win_user && // only for IE/windows atm
eregi('^'.$trans.'(.*)$',$link,$parts) && // right path
ereg(self::$send_file_ips[$valid],$ip)) // right IP
{
$fname = $valid . $parts[1];
$fname = !$win_user ? str_replace('\\','/',$fname) : str_replace('/','\\',$fname);
return 'file:'.($win_user ? '//' : '' ).$fname;
}
//echo "<p>attached_local: link=$link, valid=$valid, trans='$trans', fname='$fname', parts=(x,'${parts[1]}','${parts[2]}')</p>\n";
}
}
return False;
}
/**
* reverse static function of htmlspecialchars()
*

View File

@ -84,6 +84,12 @@ class egw_vfs extends vfs_stream_wrapper
* @var int
*/
static $user;
/**
* Current user is an eGW admin
*
* @var boolean
*/
static $is_admin = false;
/**
* Total of last find call
*
@ -322,7 +328,7 @@ class egw_vfs extends vfs_stream_wrapper
$is_dir = is_dir($path);
if (!isset($options['remove']))
{
$options['remove'] = count($base) == 1 ? ($path == '/' ? 1 : strlen($path)+1) : 0;
$options['remove'] = count($base) == 1 ? strlen($path)+(int)(substr($path,-1)!='/') : 0;
}
if ((int)$options['mindepth'] == 0 && (!$dirs_last || !$is_dir))
{
@ -332,8 +338,8 @@ class egw_vfs extends vfs_stream_wrapper
{
while($file = readdir($dir))
{
$file = $path.'/'.$file;
$file = self::concat($path,$file);
if ((int)$options['mindepth'] <= 1)
{
self::_check_add($options,$file,$result,1);
@ -416,7 +422,7 @@ class egw_vfs extends vfs_stream_wrapper
}
return $result;
}
//echo "egw_vfs::find($path)="; _debug_array(array_keys($result));
//error_log("egw_vfs::find($path)=".print_r(array_keys($result),true));
if ($exec !== true)
{
return array_keys($result);
@ -511,11 +517,13 @@ 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 boolean $allow_urls=false allow to use url's, default no only pathes (to stay within the vfs)
* @return array
*/
static function remove($urls)
static function remove($urls,$allow_urls=false)
{
return self::find($urls,array('depth'=>true),array(__CLASS__,'_rm_rmdir'));
//error_log(__METHOD__.'('.print_r($urls).')');
return self::find($urls,array('depth'=>true,'url'=>$allow_urls),array(__CLASS__,'_rm_rmdir'));
}
/**
@ -543,45 +551,52 @@ class egw_vfs extends vfs_stream_wrapper
*/
static function is_readable($path,$check = 4)
{
if (!($stat = self::stat($path)))
{
return false;
}
return self::check_access($stat,$check);
return self::check_access($path,$check);
}
/**
* 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 $stat
* @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
* @return boolean
*/
static function check_access($stat,$check)
static function check_access($path,$check,$stat=null)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)");
if (self::$is_root)
{
return true;
}
// 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)!');
}
// query stat array, if not given
if (is_null($stat))
{
$stat = self::url_stat($path,0);
}
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)");
if (!$stat)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!");
//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 other rights grant access
if (($stat['mode'] & $check) == $check)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!");
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!");
return true;
}
// check if there's owner access and we are the owner
if (($stat['mode'] & ($check << 6)) == ($check << 6) && $stat['uid'] && $stat['uid'] == self::$user)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via owner rights!");
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via owner rights!");
return true;
}
// check if there's a group access and we have the right membership
@ -594,14 +609,15 @@ class egw_vfs extends vfs_stream_wrapper
}
if (in_array(-abs($stat['gid']),$memberships))
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!");
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!");
return true;
}
}
// here could be a check for extended acls ...
// check backend for extended acls (only if path given)
$ret = $path && self::_call_on_backend('check_extended_acl',array($path,$check),true); // true = fail silent if backend does not support
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no access!");
return false;
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) ".($ret ? 'backend extended acl granted access.' : 'no access!!!'));
return $ret;
}
/**
@ -628,6 +644,38 @@ class egw_vfs extends vfs_stream_wrapper
return self::is_readable($path,1);
}
/**
* Set or delete extended acl for a given path and owner (or delete them if is_null($rights)
*
* Does NOT check if user has the rights to set the extended acl for the given url/path!
*
* @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
* @return boolean true if acl is set/deleted, false on error
*/
static function eacl($url,$rights=null,$owner=null)
{
$params = func_get_args();
return self::_call_on_backend('eacl',$params);
}
/**
* Get all ext. ACL set for a path
*
* 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
*/
static function get_eacl($path)
{
$params = func_get_args();
return self::_call_on_backend('get_eacl',$params);
}
/**
* Private constructor to prevent instanciating this class, only it's static methods should be used
*/
@ -786,6 +834,59 @@ class egw_vfs extends vfs_stream_wrapper
return array_pop($parts);
}
/**
* Get the directory / parent of a given path or url(!), return false for '/'!
*
* @param string $path path or url
* @return string/boolean parent or false if there's none ($path == '/')
*/
static function dirname($url)
{
if ($url == '/' || $url[0] != '/' && parse_url($url,PHP_URL_PATH) == '/')
{
return false;
}
$parts = explode('/',$url);
if (substr($url,-1) == '/') array_pop($parts);
array_pop($parts);
if ($url[0] != '/' && count($parts) == 3)
{
array_push($parts,''); // scheme://host is wrong (no path), has to be scheme://host/
}
//error_log(__METHOD__."($url)=".implode('/',$parts));
return implode('/',$parts);
}
/**
* Check if the current use has owner rights for the given path or stat
*
* We define all eGW admins the owner of the group directories!
*
* @param string $path
* @param array $stat=null stat for path, default queried by this function
* @return boolean
*/
static function has_owner_rights($path,array $stat=null)
{
if (!$stat) $stat = self::url_stat($path,0);
return $stat['uid'] == self::$user || // user is the owner
self::$is_root || // class runs with root rights
!$stat['uid'] && $stat['gid'] && self::$is_admin; // group directory and user is an eGW admin
}
/**
* Concat a relative path to an url, taking into account, that the url might already end with a slash
*
* @param string $url base url or path, might end in a /
* @param string $relative relative path to add to $url
* @return string
*/
static function concat($url,$relative)
{
return substr($url,-1) == '/' ? $url.$relative : $url.'/'.$relative;
}
/**
* URL to download a file
@ -802,6 +903,15 @@ class egw_vfs extends vfs_stream_wrapper
{
return '/filemanager/webdav.php'.$path;
}
/**
* Initialise our static vars
*/
static function init_static()
{
self::$user = (int) $GLOBALS['egw_info']['user']['account_id'];
self::$is_admin = isset($GLOBALS['egw_info']['user']['apps']['admin']);
}
}
egw_vfs::$user = (int) $GLOBALS['egw_info']['user']['account_id'];
egw_vfs::init_static();

View File

@ -863,14 +863,7 @@ class html
{
if (substr($name,0,5) == 'vfs:/') // vfs pseudo protocoll
{
$parts = explode('/',substr($name,4));
$file = array_pop($parts);
$path = implode('/',$parts);
$name = array(
'menuaction' => 'filemanager.uifilemanager.view',
'path' => rawurlencode(base64_encode($path)),
'file' => rawurlencode(base64_encode($file)),
);
$name = egw_vfs::download_url(substr($name,4));
}
if (is_array($name)) // menuaction and other get-vars
{

View File

@ -0,0 +1,147 @@
<?php
/**
* eGroupWare API: VFS - stream wrapper for linked files
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage vfs
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id: class.sqlfs_stream_wrapper.inc.php 24997 2008-03-02 21:44:15Z ralfbecker $
*/
/**
* eGroupWare API: stream wrapper for linked files
*
* The files stored by the sqlfs_stream_wrapper in a /apps/$app/$id directory
*
* The links stream wrapper extends the sqlfs one, to implement an own ACL based on the access
* of the entry the files are linked to.
*
* Applications can define a 'file_access' method in the link registry with the following signature:
*
* boolean function file_access(string $id,int $check,string $rel_path)
*
* If the do not implement such a function the title function is used to test if the user has
* at least read access to an entry, and if true full (write) access to the files is granted.
*
* The stream wrapper interface is according to the docu on php.net
*
* @link http://de.php.net/manual/de/function.stream-wrapper-register.php
*/
class links_stream_wrapper extends sqlfs_stream_wrapper
{
/**
* Scheme / protocoll used for this stream-wrapper
*/
const SCHEME = 'links';
/**
* Prefix to predend to get an url from a path
*/
const PREFIX = 'links://default';
/**
* Base url to store links
*/
const BASEURL = 'links://default/apps';
/**
* Enable some debug output to the error_log
*/
const DEBUG = false;
/**
* Implements ACL based on the access of the user to the entry the files are linked to.
*
* @param string $url url to check
* @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable
* @return boolean
*/
static function check_extended_acl($url,$check)
{
$path = parse_url($url,PHP_URL_PATH);
list(,$apps,$app,$id,$rel_path) = explode('/',$path,5);
if ($apps != 'apps')
{
$access = false; // no access to anything, but /apps
}
elseif (!$app)
{
$access = !($check & egw_vfs::WRITABLE); // always grant read access to /apps
}
elseif(!isset($GLOBALS['egw_info']['user']['apps'][$app]))
{
$access = false; // user has no access to the $app application
}
elseif (!$id)
{
$access = true; // grant read&write access to /apps/$app
}
// allow applications to implement their own access control to the file storage
elseif(($method = egw_link::get_registry($app,'file_access')))
{
$access = ExecMethod2($method,$id,$check,$rel_path);
}
// otherwise use the title method to check if user has (at least read access) to the entry
// which gives him then read AND write access to the file store of the entry
else
{
$access = !!egw_link::title($app,$id);
}
if (self::DEBUG) error_log(__METHOD__."($url,$check) ".($access?"access granted ($app:$id:$rel_path)":'no access!!!'));
return $access;
}
/**
* This method is called in response to stat() calls on the URL paths associated with the wrapper.
*
* Reimplemented from sqlfs, as we have to pass the value of check_extends_acl(), due to the lack of late static binding.
*
* @param string $path
* @param int $flags holds additional flags set by the streams API. It can hold one or more of the following values OR'd together:
* - STREAM_URL_STAT_LINK For resources with the ability to link to other resource (such as an HTTP Location: forward,
* or a filesystem symlink). This flag specified that only information about the link itself should be returned,
* not the resource pointed to by the link.
* This flag is set in response to calls to lstat(), is_link(), or filetype().
* - STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this flag is not set,
* you are responsible for reporting errors using the trigger_error() function during stating of the path.
* stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper!
* @return array
*/
static function url_stat ( $url, $flags )
{
return parent::url_stat($url,$flags,self::check_extended_acl($url,egw_vfs::READABLE));
}
/**
* Set or delete extended acl for a given path and owner (or delete them if is_null($rights)
*
* Reimplemented, to NOT call the sqlfs functions, as we dont allow to modify the ACL (defined by the apps)
*
* @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 $fs_id=null fs_id to use, to not query it again (eg. because it's already deleted)
* @return boolean true if acl is set/deleted, false on error
*/
static function eacl($path,$rights=null,$owner=null,$fs_id=null)
{
return false;
}
/**
* Get all ext. ACL set for a path
*
* Reimplemented, to NOT call the sqlfs functions, as we dont allow to modify the ACL (defined by the apps)
*
* @param string $path
* @return array/boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found
*/
function get_eacl($path)
{
return false;
}
}
stream_register_wrapper(links_stream_wrapper::SCHEME ,'links_stream_wrapper');

View File

@ -100,21 +100,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
*/
protected $opened_dir;
/**
* Constructor, only called for the non-static stream_* methods!
*
* @return oldvfs_stream_wrapper
*/
function __construct()
{
if (self::LOG_LEVEL > 1) error_log('oldvfs_stream_wrapper::__construct()');
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
}
/**
* This method is called immediately after your stream object is created.
*
@ -447,10 +432,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
$data=array(
'string' => $path,
'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root
@ -488,10 +469,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$path_from = parse_url($url_from,PHP_URL_PATH);
$path_to = parse_url($url_to,PHP_URL_PATH);
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
$data_from = array(
'string' => $path_from,
'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root
@ -554,10 +531,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
// check if we should also create all non-existing path components and our parent does not exist,
// if yes call ourself recursive with the parent directory
if (($options & STREAM_MKDIR_RECURSIVE) && $path != '/' && !self::$old_vfs->file_exists(array(
@ -606,10 +579,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
$data=array(
'string' => $path,
'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root
@ -657,10 +626,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
$data=array(
'string' => $path,
'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root
@ -683,10 +648,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$this->opened_dir = null;
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
$path = parse_url($url,PHP_URL_PATH);
$files = self::$old_vfs->ls(array(
@ -699,7 +660,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
));
if (!is_array($files) ||
// we also need to return false, if $url is not a directory!
count($files) == 1 && $path == $files[0]['directory'].'/'.$files[0]['name'] &&
count($files) == 1 && $path == egw_vfs::concat($files[0]['directory'],$files[0]['name']) &&
$files[0]['mime_type'] != self::DIR_MIME_TYPE)
{
self::_remove_password($url);
@ -711,7 +672,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
foreach($files as $file)
{
$this->opened_dir[] = $file['name'];
self::$stat_cache[$file['directory'].'/'.$file['name']] = $file;
self::$stat_cache[egw_vfs::concat($file['directory'],$file['name'])] = $file;
}
//print_r($this->opened_dir);
reset($this->opened_dir);
@ -757,10 +718,6 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
return self::_vfsinfo2stat(self::$stat_cache[$path]);
}
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
}
list($info) = self::$old_vfs->ls(array(
'string' => $path,
'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root
@ -894,6 +851,21 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper
$parts['host'].$parts['path'];
}
}
/**
* Initialise our static vars
*
*/
function init_static()
{
stream_register_wrapper(self::SCHEME ,__CLASS__);
if (!is_object(self::$old_vfs))
{
self::$old_vfs =& new vfs_home();
self::$old_vfs->override_acl =& egw_vfs::$is_root;
}
}
}
stream_register_wrapper(oldvfs_stream_wrapper::SCHEME ,'oldvfs_stream_wrapper');
oldvfs_stream_wrapper::init_static();

View File

@ -22,7 +22,7 @@
*
* I use the PDO DB interface, as it allows to access BLOB's as streams (avoiding to hold them complete in memory).
*
* The interface is according to the docu on php.net
* The stream wrapper interface is according to the docu on php.net
*
* @link http://de.php.net/manual/de/function.stream-wrapper-register.php
* @ToDo compare (and optimize) performance with old vfs system (eg. via webdav)
@ -152,18 +152,17 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)");
$path = parse_url($url,PHP_URL_PATH);
$dir = egw_vfs::dirname($url);
$this->opened_path = $path;
$this->opened_mode = $mode;
$this->opened_stream = null;
$stat = self::url_stat($url,0);
if (!($stat = self::url_stat($path,0)) || $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
!egw_vfs::check_access(($dir_stat = self::url_stat(dirname($path),0)),egw_vfs::WRITABLE)) // or we are not allowed to create it
!egw_vfs::check_access($dir,egw_vfs::WRITABLE,$dir_stat=self::url_stat($dir,0))) // 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!");
@ -212,7 +211,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
}
else
{
if ($mode != 'r' && !egw_vfs::check_access($stat,egw_vfs::WRITABLE)) // we are not allowed to edit it
if ($mode != 'r' && !egw_vfs::check_access($url,egw_vfs::WRITABLE,$stat)) // we are not allowed to edit it
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file can not be edited!");
@ -440,8 +439,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
if (!($stat = self::url_stat($path,0)) || !($dir_stat = self::url_stat(dirname($path),0)) ||
!egw_vfs::check_access($dir_stat,egw_vfs::WRITABLE))
if (!($stat = self::url_stat($path,0)) || !egw_vfs::check_access(dirname($path),egw_vfs::WRITABLE))
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."($url) (type=$type) permission denied!");
@ -475,16 +473,16 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$path_from = parse_url($url_from,PHP_URL_PATH);
$path_to = parse_url($url_to,PHP_URL_PATH);
$to_dir = dirname($path_to);
if (!($from_stat = self::url_stat($path_from,0)) ||
!($from_dir_stat = self::url_stat(dirname($path_from),0)) || !egw_vfs::check_access($from_dir_stat,egw_vfs::WRITABLE))
if (!($from_stat = self::url_stat($path_from,0)) || !egw_vfs::check_access(dirname($path_from),egw_vfs::WRITABLE))
{
self::_remove_password($url_from);
self::_remove_password($url_to);
if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $path_from permission denied!");
return false; // no permission or file does not exist
}
if (!($to_dir_stat = self::url_stat(dirname($path_to),0)) || !egw_vfs::check_access($to_dir_stat,egw_vfs::WRITABLE))
if (!egw_vfs::check_access($to_dir,egw_vfs::WRITABLE,$to_dir_stat = self::url_stat($to_dir,0)))
{
self::_remove_password($url_from);
self::_remove_password($url_to);
@ -544,19 +542,19 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
return false;
}
$parent = self::url_stat(dirname($path),STREAM_URL_STAT_QUIET);
$parent = self::url_stat($parent_path=dirname($path),STREAM_URL_STAT_QUIET);
// check if we should also create all non-existing path components and our parent does not exist,
// if yes call ourself recursive with the parent directory
if (($options & STREAM_MKDIR_RECURSIVE) && $path != '/' && !$parent)
{
if (!self::mkdir(dirname($path),$mode,$options))
if (!self::mkdir($parent_path,$mode,$options))
{
return false;
}
$parent = self::url_stat(dirname($path),0);
$parent = self::url_stat($parent_path=dirname($path),0);
}
if (!$parent || !egw_vfs::check_access($parent,egw_vfs::WRITABLE))
if (!$parent || !egw_vfs::check_access($parent_path,egw_vfs::WRITABLE,$parent))
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) permission denied!");
@ -601,9 +599,10 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)");
$path = parse_url($url,PHP_URL_PATH);
$parent = dirname($path);
if (!($stat = self::url_stat($path,0)) || $stat['mime'] != self::DIR_MIME_TYPE ||
!($parent = self::url_stat(dirname($path),0)) || !egw_vfs::check_access($parent,egw_vfs::WRITABLE))
!egw_vfs::check_access($parent,egw_vfs::WRITABLE))
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$options) (type=$type) permission denied!");
@ -630,6 +629,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$stmt = self::$pdo->prepare('DELETE FROM '.self::TABLE.' WHERE fs_id=?');
if (($ret = $stmt->execute(array($stat['ino']))) && $operation == self::STORE2FS)
{
self::eacl($path,null,false,$stat['ino']); // remove all (=false) evtl. existing extended acl for that dir
rmdir(self::_fs_path($path));
}
return $ret;
@ -651,17 +651,15 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET)))
{
// file does not exist --> create an empty one
if (($f = fopen(self::SCHEME.'://default'.$path,'w')) && fclose($f))
{
if (!is_null($time))
{
$stat = self::url_stat($path,0);
}
}
else
if (!($f = fopen(self::SCHEME.'://default'.$path,'w')) || !fclose($f))
{
return false;
}
if (is_null($time))
{
return true; // new (empty) file created with current mod time
}
$stat = self::url_stat($path,0);
}
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_modified=:fs_modified WHERE fs_id=:fs_id');
@ -801,15 +799,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
*/
function dir_opendir ( $url, $options )
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$options)");
$this->opened_dir = null;
$path = parse_url($url,PHP_URL_PATH);
if (!($stat = self::url_stat($url,0)) || // dir not found
$stat['mime'] != self::DIR_MIME_TYPE || // no dir
!egw_vfs::check_access($stat,egw_vfs::EXECUTABLE|egw_vfs::READABLE)) // no access
!egw_vfs::check_access($url,egw_vfs::EXECUTABLE|egw_vfs::READABLE,$stat)) // no access
{
self::_remove_password($url);
$msg = $stat['mime'] != self::DIR_MIME_TYPE ? "$url is no directory" : 'permission denied';
@ -819,12 +815,14 @@ 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 FROM '.self::TABLE.' WHERE fs_dir=?';
// only return readable files, if dir is not writable by user
if (!egw_vfs::check_access($stat,egw_vfs::WRITABLE))
/* to value/check the extended acl later on, we have to return all files!
if (egw_vfs::HIDE_UNREADABLES && !egw_vfs::check_access($url,egw_vfs::WRITABLE,$stat))
{
$query .= ' AND '.self::_sql_readable();
}
$query = "/* sqlfs::dir_opendir($path) */ ".$query;
}*/
//$query = "/* sqlfs::dir_opendir($path) */ ".$query;
$stmt = self::$pdo->prepare($query);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
@ -833,9 +831,10 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
foreach($stmt as $file)
{
$this->opened_dir[] = $file['fs_name'];
self::$stat_cache[$path.'/'.$file['fs_name']] = $file;
self::$stat_cache[egw_vfs::concat($path,$file['fs_name'])] = $file;
}
}
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$options): ".implode(', ',$this->opened_dir));
//print_r($this->opened_dir);
reset($this->opened_dir);
@ -866,12 +865,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
* - STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this flag is not set,
* you are responsible for reporting errors using the trigger_error() function during stating of the path.
* stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper!
* @param boolean $eacl_access=null allows extending classes to pass the value of their check_extended_acl() method (no lsb!)
* @return array
*/
static function url_stat ( $url, $flags )
static function url_stat ( $url, $flags, $eacl_access=null )
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags)");
$path = parse_url($url,PHP_URL_PATH);
// webdav adds a trailing slash to dirs, which causes url_stat to NOT find the file otherwise
@ -891,6 +891,12 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
}
$base_query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_name=? 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
if (is_null($eacl_access))
{
$eacl_access = self::check_extended_acl($path,egw_vfs::READABLE); // should be static::check_extended_acl, but no lsb!
}
foreach($parts as $n => $name)
{
if ($n == 0)
@ -901,9 +907,15 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
{
$query = 'SELECT fs_id FROM '.self::TABLE.' WHERE fs_dir=('.$query.') AND fs_name='.self::$pdo->quote($name);
// if we are not root, we need to make sure the user has the right to tranverse all partent directories (read-rights)
if (!egw_vfs::$is_root)
// 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)
{
if (!egw_vfs::$user)
{
self::_remove_password($url);
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags) permission denied, no user-id and not root!");
return false;
}
$query .= ' AND '.self::_sql_readable();
}
}
@ -912,7 +924,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$query = str_replace('fs_name=?','fs_name='.self::$pdo->quote($name),$base_query).'('.$query.')';
}
}
$query = "/* sqlfs::url_stat($path) */ ".$query;
//$query = "/* sqlfs::url_stat($path) */ ".$query;
//echo "query=$query\n";
if (!($result = self::$pdo->query($query)) || !($info = $result->fetch(PDO::FETCH_ASSOC)))
@ -923,6 +935,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
}
self::$stat_cache[$path] = $info;
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$flags)=".str_replace("\n",'',print_r($info,true)));
return self::_vfsinfo2stat($info);
}
@ -964,7 +977,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
return $file;
}
/**
* This method is called in response to rewinddir().
*
@ -1002,6 +1015,223 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
return true;
}
private static $extended_acl;
/**
* Check if extendes ACL (stored in eGW's ACL table) grants access
*
* The extended ACL is inherited, so it's valid for all subdirs and the included files!
* The used algorithm break on the first match. It could be used, to disallow further access.
*
* @param string $url url to check
* @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable
* @return boolean
*/
static function check_extended_acl($url,$check)
{
$url_path = parse_url($url,PHP_URL_PATH);
if (is_null(self::$extended_acl))
{
self::_read_extended_acl();
}
$access = false;
foreach(self::$extended_acl as $path => $rights)
{
if ($path == $url_path || substr($url_path,0,strlen($path)+1) == $path.'/')
{
$access = ($rights & $check) == $check;
break;
}
}
//error_log(__METHOD__."($url,$check) ".($access?"access granted by $path=$rights":'no access!!!'));
return $access;
}
/**
* Read the extended acl via acl::get_grants('sqlfs')
*
*/
private static function _read_extended_acl()
{
if ((self::$extended_acl = $GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME)) != false)
{
return; // ext. ACL read from session.
}
self::$extended_acl = array();
if (($rights = $GLOBALS['egw']->acl->get_all_location_rights(egw_vfs::$user,self::EACL_APPNAME)))
{
$pathes = self::id2path(array_keys($rights));
}
foreach($rights as $fs_id => $right)
{
$path = $pathes[$fs_id];
if (isset($path))
{
self::$extended_acl[$path] = (int)$right;
}
}
// sort by length descending, to allow more specific pathes to have precedence
uksort(self::$extended_acl,create_function('$a,$b','return strlen($b)-strlen($a);'));
$GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl);
//echo __METHOD__; print_r(self::$extended_acl);
}
/**
* Appname used with the acl class to store the extended acl
*/
const EACL_APPNAME = 'sqlfs';
/**
* Set or delete extended acl for a given path and owner (or delete them if is_null($rights)
*
* Only root, the owner of the path or an eGW admin (only if there's no owner but a group) are allowd to set eACL's!
*
* @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 $fs_id=null fs_id to use, to not query it again (eg. because it's already deleted)
* @return boolean true if acl is set/deleted, false on error
*/
static function eacl($path,$rights=null,$owner=null,$fs_id=null)
{
if ($path[0] != '/')
{
$path = parse_url($path,PHP_URL_PATH);
}
if (is_null($fs_id))
{
if (!($stat = self::url_stat($path,0)))
{
error_log(__METHOD__."($path,$rights,$owner,$fs_id) no such file or directory!");
return false; // $path not found
}
if (!egw_vfs::has_owner_rights($path,$stat)) // not group dir and user is eGW admin
{
error_log(__METHOD__."($path,$rights,$owner,$fs_id) permission denied!");
return false; // permission denied
}
$fs_id = $stat['ino'];
}
if (is_null($owner))
{
$owner = egw_vfs::$user;
}
if (is_null($rights) || $owner === false)
{
// delete eacl
if (is_null($owner) || $owner == egw_vfs::$user)
{
unset(self::$extended_acl[$path]);
}
$ret = $GLOBALS['egw']->acl->delete_repository(self::EACL_APPNAME,$fs_id,(int)$owner);
}
else
{
if (isset(self::$extended_acl) && ($owner == egw_vfs::$user ||
$owner < 0 && egw_vfs::$user && in_array($owner,$GLOBALS['egw']->accounts->memberships(egw_vfs::$user,true))))
{
// set rights for this class, if applicable
self::$extended_acl[$path] = $rights;
}
$ret = $GLOBALS['egw']->acl->add_repository(self::EACL_APPNAME,$fs_id,$owner,$rights);
}
if ($ret)
{
$GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl);
}
//error_log(__METHOD__."($path,$rights,$owner,$fs_id)=".(int)$ret);
return $ret;
}
/**
* Get all ext. ACL set for a path
*
* 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
*/
function get_eacl($path)
{
if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET)))
{
return false; // not found
}
$eacls = array();
foreach($GLOBALS['egw']->acl->get_all_rights($stat['ino'],self::EACL_APPNAME) as $owner => $rights)
{
$eacls[] = array(
'path' => $path,
'owner' => $owner,
'rights' => $rights,
'ino' => $stat['ino'],
);
}
if (($path = egw_vfs::dirname($path)))
{
return array_merge(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"]);'));
return $eacls;
}
/**
* Return the path of given fs_id(s)
*
* Calls itself recursive to to determine the path of the parent/directory
*
* @param int/array $fs_ids integer fs_id or array of them
* @return string/array path or array or pathes indexed by fs_id
*/
static function id2path($fs_ids)
{
//error_log(__METHOD__.'('.print_r($fs_id,true).')');
$ids = (array)$fs_ids;
if (count($ids) > 1) array_map(create_function('&$v','$v = (int)$v;'),$ids);
$query = 'SELECT fs_id,fs_dir,fs_name FROM '.self::TABLE.' WHERE fs_id'.
(count($ids) == 1 ? '='.(int)$ids[0] : ' IN ('.implode(',',$ids).')');
if (!is_object(self::$pdo))
{
self::_pdo();
}
//$query = '/* sqlfs::id2path('.implode(',',$ids).') */ '.$query;
$stmt = self::$pdo->prepare($query);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
if (!$stmt->execute())
{
return false; // not found
}
$parents = array();
foreach($stmt as $row)
{
if ($row['fs_dir'] > 1 && !in_array($row['fs_dir'],$parents))
{
$parents[] = $row['fs_dir'];
}
$rows[$row['fs_id']] = $row;
}
unset($stmt);
if ($parents && !($parents = self::id2path($parents)))
{
return false; // parent not found, should never happen ...
}
$pathes = array();
foreach($rows as $fs_id => $row)
{
$parent = $row['fs_dir'] > 1 ? $parents[$row['fs_dir']] : '';
$pathes[$fs_id] = $parent . '/' . $row['fs_name'];
}
//error_log(__METHOD__.'('.print_r($fs_ids,true).')='.print_r($pathes,true));
return is_array($fs_ids) ? $pathes : array_shift($pathes);
}
/**
* Convert a sqlfs-file-info into a stat array
*
@ -1035,22 +1265,22 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
*/
static private function _pdo()
{
$server =& $GLOBALS['egw_info']['server'];
$egw_db = isset($GLOBALS['egw_setup']) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db;
switch($server['db_type'])
switch($egw_db->Type)
{
default:
$dsn = $server['db_type'].':host='.$server['db_host'].';dbname='.$server['db_name'];
$dsn = $egw_db->Type.':host='.$egw_db->Host.';dbname='.$egw_db->Database;
break;
}
self::$pdo = new PDO($dsn,$server['db_user'],$server['db_pass']);
self::$pdo = new PDO($dsn,$egw_db->User,$egw_db->Password);
// set client charset of the connection
$charset = $GLOBALS['egw']->translation->charset();
switch($server['db_type'])
{
case 'mysql':
if (isset($GLOBALS['egw']->db->Link_ID->charset2mysql[$charset])) $charset = $GLOBALS['egw']->db->Link_ID->charset2mysql[$charset];
if (isset($egw_db->Link_ID->charset2mysql[$charset])) $charset = $egw_db->Link_ID->charset2mysql[$charset];
// fall throught
case 'pgsql':
$query = "SET NAMES '$charset'";

View File

@ -38,6 +38,11 @@ class vfs_stream_wrapper implements iface_stream_wrapper
* Mime type of directories, the old vfs used 'Directory', while eg. WebDAV uses 'httpd/unix-directory'
*/
const DIR_MIME_TYPE = 'httpd/unix-directory';
/**
* Should unreadable entries in a not writable directory be hidden, default yes
*/
const HIDE_UNREADABLES = true;
/**
* optional context param when opening the stream, null if no context passed
*
@ -53,8 +58,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper
* @var array
*/
protected static $fstab = array(
// '/' => 'sqlfs://$user:$pass@$host/',
'/' => 'oldvfs://$user:$pass@$host/',
'/' => 'sqlfs://$user:$pass@$host/',
'/apps' => 'links://$user:$pass@$host/apps',
// '/' => 'oldvfs://$user:$pass@$host/',
// '/files' => 'oldvfs://$user:$pass@$host/home/Default',
// '/images' => 'http://localhost/egroupware/phpgwapi/templates/idots/images',
// '/home/ralf/linux' => '/home/ralf', // we probably need to forbid direct filesystem access for security reasons!
@ -126,11 +132,15 @@ class vfs_stream_wrapper implements iface_stream_wrapper
}
$parts = array_merge(parse_url($path),$defaults);
if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME)
{
return $path; // path is already a non-vfs url --> nothing to do
}
if (empty($parts['path'])) $parts['path'] = '/';
foreach(array_reverse(self::$fstab) as $mounted => $url)
{
if ($mounted == substr($parts['path'],0,strlen($mounted)))
if ($mounted == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1))
{
$scheme = parse_url($url,PHP_URL_SCHEME);
if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers))
@ -141,6 +151,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper
$url = str_replace(array('$user','$pass','$host'),array($parts['user'],$parts['pass'],$parts['host']),$url);
if ($parts['query']) $url .= '?'.$parts['query'];
if ($parts['fragment']) $url .= '#'.$parts['fragment'];
//error_log(__METHOD__."($path) = $url");
return $cache[$path] = $url;
}
@ -384,8 +397,11 @@ class vfs_stream_wrapper implements iface_stream_wrapper
*
* @param string $name
* @param array $params first param has to be the path, otherwise we can not determine the correct wrapper
* @param boolean $fail_silent=false should only false be returned if function is not supported by the backend,
* or should an E_USER_WARNING error be triggered (default)
* @return mixed return value of backend or false if function does not exist on backend
*/
static private function _call_on_backend($name,$params)
static protected function _call_on_backend($name,$params,$fail_silent=false)
{
$path = $params[0];
@ -397,7 +413,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
if (!class_exists($class = $scheme.'_stream_wrapper') || !method_exists($class,$name))
{
trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING);
if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING);
return false;
}
$params[0] = $url;
@ -540,7 +556,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return false;
}
$this->opened_dir_writable = ($stat = @stat($this->opened_dir_url)) && egw_vfs::check_access($stat,egw_vfs::WRITABLE);
$this->opened_dir_writable = egw_vfs::check_access($this->opened_dir_url,egw_vfs::WRITABLE);
// check our fstab if we need to add some of the mountpoints
$basepath = parse_url($this->opened_dir_url,PHP_URL_PATH);
@ -589,7 +605,16 @@ class vfs_stream_wrapper implements iface_stream_wrapper
return false;
}
//error_log(__METHOD__."('$path',$flags) calling stat($url)");
return @stat($url); // suppressed the stat failed warnings
return $stat = @stat($url); // suppressed the stat failed warnings
// Todo: if we hide non readables, we should return false on url_stat for consitency (if dir is not writabel)
// Problem: this does NOT stop (calles itself infinit recursive)!
if (self::HIDE_UNREADABLES && !egw_vfs::check_access($path,egw_vfs::READABLE,$stat) &&
!egw_vfs::check_access(dirname($path,egw_vfs::WRITABLE)))
{
return false;
}
return $stat;
}
/**
@ -610,14 +635,10 @@ class vfs_stream_wrapper implements iface_stream_wrapper
}
// only return children readable by the user, if dir is not writable
do {
if (($file = readdir($this->opened_dir)) !== false && !$this->opened_dir_writable)
{
$stat = stat($this->opened_dir_url.'/'.$file);
}
//echo __METHOD__."() opened_dir_writable=$this->opened_dir_writable, file=$file, readable=".(int)egw_vfs::check_access($stat,egw_vfs::READABLE).", loop=".
// (int)($file !== false && $stat && !egw_vfs::check_access($stat,egw_vfs::READABLE))."\n";
$file = readdir($this->opened_dir);
}
while($file !== false && $stat && !egw_vfs::check_access($stat,egw_vfs::READABLE));
while($file !== false && self::HIDE_UNREADABLES && !$this->opened_dir_writable &&
!egw_vfs::check_access(egw_vfs::concat($this->opened_dir_url,$file),egw_vfs::READABLE));
return $file;
}
@ -669,16 +690,19 @@ class vfs_stream_wrapper implements iface_stream_wrapper
require_once('HTTP/WebDAV/Client.php');
self::$wrappers[] = 'webdav';
break;
case 'oldvfs':
case 'sqlfs':
require_once(EGW_API_INC.'/class.'.$scheme.'_stream_wrapper.inc.php');
self::$wrappers[] = $scheme;
break;
case '':
return true; // default file, always loaded
break; // default file, always loaded
default:
trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING);
return false;
// check if scheme is buildin in php or one of our own stream wrappers
if (in_array($scheme,stream_get_wrappers()) || class_exists($scheme.'_stream_wrapper'))
{
self::$wrappers[] = $scheme;
}
else
{
trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING);
return false;
}
}
}
return true;

View File

@ -14,7 +14,6 @@
*/
require_once('HTTP/WebDAV/Server/Filesystem.php');
require_once(EGW_API_INC.'/class.egw_vfs.inc.php');
/**
* FileManger - WebDAV access using the new stream wrapper VFS interface
@ -33,7 +32,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
*
* @var string
*/
var $base = 'vfs://default';
var $base = egw_vfs::PREFIX;
/**
* Debug level: 0 = nothing, 1 = function calls, 2 = more info, eg. complete $_SERVER array

View File

@ -14,7 +14,7 @@
/* Basic information about this app */
$setup_info['phpgwapi']['name'] = 'phpgwapi';
$setup_info['phpgwapi']['title'] = 'eGroupWare API';
$setup_info['phpgwapi']['version'] = '1.5.004';
$setup_info['phpgwapi']['version'] = '1.5.006';
$setup_info['phpgwapi']['versions']['current_header'] = '1.28';
$setup_info['phpgwapi']['enable'] = 3;
$setup_info['phpgwapi']['app_order'] = 1;
@ -55,14 +55,6 @@
$setup_info['phpgwapi']['tables'][] = 'egw_addressbook2list';
$setup_info['phpgwapi']['tables'][] = 'egw_sqlfs';
// hooks used by vfs_home to manage user- and group-directories
$setup_info['phpgwapi']['hooks']['addaccount'] = 'phpgwapi.vfs_home.addAccount';
$setup_info['phpgwapi']['hooks']['deleteaccount'] = 'phpgwapi.vfs_home.deleteAccount';
$setup_info['phpgwapi']['hooks']['editaccount'] = 'phpgwapi.vfs_home.editAccount';
$setup_info['phpgwapi']['hooks']['addgroup'] = 'phpgwapi.vfs_home.addGroup';
$setup_info['phpgwapi']['hooks']['deletegroup'] = 'phpgwapi.vfs_home.deleteGroup';
$setup_info['phpgwapi']['hooks']['editgroup'] = 'phpgwapi.vfs_home.editGroup';
// hooks used by vfs_home_hooks to manage user- and group-directories for the new stream based VFS
$setup_info['phpgwapi']['hooks']['addaccount'] = 'phpgwapi.vfs_home_hooks.addAccount';
$setup_info['phpgwapi']['hooks']['deleteaccount'] = 'phpgwapi.vfs_home_hooks.deleteAccount';

View File

@ -95,7 +95,10 @@
{
// import the current egw_vfs into egw_sqlfs
// ToDo: moving /infolog and /infolog/$app to /apps in the files dir!!!
$debug = true;
$debug = $GLOBALS['DEBUG'];
// delete the table in case this update runs multiple times
$GLOBALS['egw_setup']->db->delete('egw_vfs',false,__LINE__,__FILE__);
$query = $GLOBALS['egw_setup']->db->select('egw_vfs','*',"vfs_mime_type != 'journal' AND vfs_mime_type != 'journal-deleted'",__LINE__,__FILE__,false,'ORDER BY length(vfs_directory) ASC','phpgwapi');
if ($debug) echo "rows=<pre>\n";
@ -184,3 +187,67 @@
}
return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.5.004';
}
$test[] = '1.5.004';
function phpgwapi_upgrade1_5_004()
{
// convert the filemanager group grants into extended ACL
// delete all sqlfs entries from the ACL table, in case we run multiple times
$GLOBALS['egw_setup']->db->delete('egw_acl',array('acl_appname' => sqlfs_stream_wrapper::EACL_APPNAME),__LINE__,__FILE__);
$GLOBALS['egw_setup']->setup_account_object();
$accounts = $GLOBALS['egw_setup']->accounts;
$accounts = new accounts();
egw_vfs::$is_root = true; // we need root rights to set the extended acl, without being the owner
foreach($GLOBALS['egw_setup']->db->select('egw_acl','*',array(
'acl_appname' => 'filemanager',
"acl_location != 'run'",
),__LINE__,__FILE__) as $row)
{
$rights = egw_vfs::READABLE | egw_vfs::EXECUTABLE;
if($row['acl_rights'] > 1) $rights |= egw_vfs::WRITABLE;
if (($lid = $accounts->id2name($row['acl_account'])) && $accounts->exists($row['acl_location']))
{
$ret = sqlfs_stream_wrapper::eacl('/home/'.$lid,$rights,(int)$row['acl_location']);
//echo "<p>sqlfs_stream_wrapper::eacl('/home/$lid',$rights,$row[acl_location])=$ret</p>\n";
}
}
egw_vfs::$is_root = false;
return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.5.005';
}
$test[] = '1.5.005';
function phpgwapi_upgrade1_5_005()
{
// move /infolog/$app to /apps/$app and /infolog to /apps/infolog
$files_dir = $GLOBALS['egw_setup']->db->select('egw_config','config_value',array(
'config_name' => 'files_dir',
'config_app' => 'phpgwapi',
),__LINE__,__FILE__)->fetchSingle();
if ($files_dir && file_exists($files_dir) && file_exists($files_dir.'/infolog'))
{
mkdir($files_dir.'/apps',0700,true);
if (($dir = opendir($files_dir.'/infolog')))
{
while(($app = readdir($dir)))
{
if (!is_numeric($app) && $app[0] != '.') // ingore infolog entries and . or ..
{
rename($files_dir.'/infolog/'.$app,$files_dir.'/apps/'.$app);
}
}
closedir($dir);
rename($files_dir.'/infolog',$files_dir.'/apps/infolog');
}
}
return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.5.006';
}