some more refactoring and removing some permanent logs

This commit is contained in:
Ralf Becker 2020-09-16 19:15:25 +02:00
parent a30840b10d
commit 60ff7e8937
4 changed files with 477 additions and 542 deletions

View File

@ -69,23 +69,12 @@ use HTTP_WebDAV_Server;
class Vfs extends Vfs\Base
{
const PREFIX = Vfs\StreamWrapper::PREFIX;
/**
* Scheme / protocol used for this stream-wrapper
*/
const SCHEME = Vfs\StreamWrapper::SCHEME;
/**
* Name of the lock table
*/
const LOCK_TABLE = 'egw_locks';
/**
* How much should be logged to the apache error-log
*
* 0 = Nothing
* 1 = only errors
* 2 = all function calls and errors (contains passwords too!)
*/
const LOG_LEVEL = 1;
/**
* Current user has root rights, no access checks performed!
*
@ -306,45 +295,6 @@ class Vfs extends Vfs\Base
return $path[0] == '/' && file_exists(self::PREFIX.$path);
}
/**
* Mounts $url under $path in the vfs, called without parameter it returns the fstab
*
* The fstab is stored in the eGW configuration and used for all eGW users.
*
* @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. /
* @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
* @param boolean $persitent_mount =true create a persitent mount, or only a temprary for current request
* @param boolean $clear_fstab =false true clear current fstab, false (default) only add given mount
* @return array|boolean array with fstab, if called without parameter or true on successful mount
*/
static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true,$clear_fstab=false)
{
return Vfs\StreamWrapper::mount($url, $path, $check_url, $persitent_mount, $clear_fstab);
}
/**
* Unmounts a filesystem part of the vfs
*
* @param string $path url or path of the filesystem to unmount
*/
static function umount($path)
{
return Vfs\StreamWrapper::umount($path);
}
/**
* Returns mount url of a full url returned by resolve_url
*
* @param string $fullurl full url returned by resolve_url
* @return string|NULL mount url or null if not found
*/
static function mount_url($fullurl)
{
return Vfs\StreamWrapper::mount_url($fullurl);
}
/**
* Check if file is hidden: name starts with a '.' or is Thumbs.db or _gsdata_
*
@ -1295,21 +1245,12 @@ class Vfs extends Vfs\Base
* 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
* @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)
{
$vfs = new Vfs\StreamWrapper();
$stat = $vfs->url_stat($path,0);
}
return $stat['uid'] == self::$user && // (current) user is the owner
// in sharing current user != self::$user and should NOT have owner rights
$GLOBALS['egw_info']['user']['account_id'] == self::$user ||
self::$is_root || // class runs with root rights
!$stat['uid'] && $stat['gid'] && self::$is_admin; // group directory and user is an eGW admin
return (new Vfs\StreamWrapper())->has_owner_rights($path, $stat);
}
/**
@ -2131,21 +2072,6 @@ class Vfs extends Vfs\Base
return $vfs->resolve_url_symlinks($_path, $file_exists, $resolve_last_symlink, $stat);
}
/**
* Resolve the given path according to our fstab
*
* @param string $_path
* @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!)
* @param boolean $use_symlinkcache =true
* @param boolean $replace_user_pass_host =true replace $user,$pass,$host in url, default true, if false result is not cached
* @param boolean $fix_url_query =false true append relativ path to url query parameter, default not
* @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry
*/
static function resolve_url($_path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true,$fix_url_query=false)
{
return Vfs\StreamWrapper::resolve_url($_path, $do_symlink, $use_symlinkcache, $replace_user_pass_host, $fix_url_query);
}
/**
* This method is called in response to mkdir() calls on URL paths associated with the wrapper.
*
@ -2432,9 +2358,9 @@ class Vfs extends Vfs\Base
static function clearstatcache($path='/')
{
//error_log(__METHOD__."('$path')");
Vfs\StreamWrapper::clearstatcache($path);
parent::clearstatcache($path);
self::_call_on_backend('clearstatcache', array($path), true, 0);
Vfs\StreamWrapper::clearstatcache($path);
parent::clearstatcache($path);
}
/**

View File

@ -19,6 +19,10 @@ use EGroupware\Api\Vfs;
*/
class Base
{
/**
* Scheme / protocol used for this stream-wrapper
*/
const SCHEME = 'vfs';
/**
* Mime type of directories, the old vfs used 'Directory', while eg. WebDAV uses 'httpd/unix-directory'
*/
@ -40,6 +44,453 @@ class Base
*/
const MODE_LINK = 0120000;
/**
* How much should be logged to the apache error-log
*
* 0 = Nothing
* 1 = only errors
* 2 = all function calls and errors (contains passwords too!)
*/
const LOG_LEVEL = 1;
/**
* Our fstab in the form mount-point => url
*
* The entry for root has to be the first, or more general if you mount into subdirs the parent has to be before!
*
* @var array
*/
protected static $fstab = array(
'/' => 'sqlfs://$host/',
'/apps' => 'links://$host/apps',
);
/**
* Mounts $url under $path in the vfs, called without parameter it returns the fstab
*
* The fstab is stored in the eGW configuration and used for all eGW users.
*
* @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. /
* @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
* @param boolean $persitent_mount =true create a persitent mount, or only a temprary for current request
* @param boolean $clear_fstab =false true clear current fstab, false (default) only add given mount
* @return array|boolean array with fstab, if called without parameter or true on successful mount
*/
static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true,$clear_fstab=false)
{
if (is_null($check_url)) $check_url = strpos($url,'$') === false;
if (!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup
{
$api_config = Api\Config::read('phpgwapi');
if (isset($api_config['vfs_fstab']) && is_array($api_config['vfs_fstab']))
{
self::$fstab = $api_config['vfs_fstab'];
}
else
{
self::$fstab = array(
'/' => 'sqlfs://$host/',
'/apps' => 'links://$host/apps',
);
}
unset($api_config);
}
if (is_null($url) || is_null($path))
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns '.array2string(self::$fstab));
return self::$fstab;
}
if (!Vfs::$is_root)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') permission denied, you are NOT root!');
return false; // only root can mount
}
if ($clear_fstab)
{
self::$fstab = array();
}
if (isset(self::$fstab[$path]) && self::$fstab[$path] === $url)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.');
return true; // already mounted
}
self::load_wrapper(Vfs::parse_url($url,PHP_URL_SCHEME));
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
}
self::$fstab[$path] = $url;
uksort(self::$fstab, function($a, $b)
{
return strlen($a) - strlen($b);
});
if ($persitent_mount)
{
Api\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;
}
/**
* Unmounts a filesystem part of the vfs
*
* @param string $path url or path of the filesystem to unmount
*/
static function umount($path)
{
if (!Vfs::$is_root)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).','.array2string($path).') permission denied, you are NOT root!');
return false; // only root can mount
}
if (!isset(self::$fstab[$path]) && ($path = array_search($path,self::$fstab)) === false)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).') NOT mounted!');
return false; // $path not mounted
}
unset(self::$fstab[$path]);
Api\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($path).') returns true (successful unmount).');
return true;
}
/**
* Returns mount url of a full url returned by resolve_url
*
* @param string $fullurl full url returned by resolve_url
* @return string|NULL mount url or null if not found
*/
static function mount_url($fullurl)
{
foreach(array_reverse(self::$fstab) as $url)
{
list($url_no_query) = explode('?',$url);
if (substr($fullurl,0,1+strlen($url_no_query)) === $url_no_query.'/')
{
return $url;
}
}
return null;
}
/**
* Cache of already resolved urls
*
* @var array with path => target
*/
private static $resolve_url_cache = array();
private static $wrappers;
/**
* Resolve the given path according to our fstab
*
* @param string $_path
* @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!)
* @param boolean $use_symlinkcache =true
* @param boolean $replace_user_pass_host =true replace $user,$pass,$host in url, default true, if false result is not cached
* @param boolean $fix_url_query =false true append relativ path to url query parameter, default not
* @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry
*/
static function resolve_url($_path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true,$fix_url_query=false)
{
$path = self::get_path($_path);
// we do some caching here
if (isset(self::$resolve_url_cache[$path]) && $replace_user_pass_host)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '".self::$resolve_url_cache[$path]."' (from cache)");
return self::$resolve_url_cache[$path];
}
// check if we can already resolve path (or a part of it) with a known symlinks
if ($use_symlinkcache)
{
$path = self::symlinkCache_resolve($path,$do_symlink);
}
// setting default user, passwd and domain, if it's not contained int the url
$defaults = array(
'user' => $GLOBALS['egw_info']['user']['account_lid'],
'pass' => urlencode($GLOBALS['egw_info']['user']['passwd']),
'host' => $GLOBALS['egw_info']['user']['domain'],
'home' => str_replace(array('\\\\','\\'),array('','/'),$GLOBALS['egw_info']['user']['homedirectory']),
);
$parts = array_merge(Vfs::parse_url($path),$defaults);
if (!$parts['host']) $parts['host'] = 'default'; // otherwise we get an invalid url (scheme:///path/to/something)!
if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$path' (path is already an url)");
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 == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1))
{
$scheme = Vfs::parse_url($url,PHP_URL_SCHEME);
if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers))
{
self::load_wrapper($scheme);
}
if (($relative = substr($parts['path'],strlen($mounted))))
{
$url = Vfs::concat($url,$relative);
}
// if url contains url parameter, eg. from filesystem streamwrapper, we need to append relative path here too
$matches = null;
if ($fix_url_query && preg_match('|([?&]url=)([^&]+)|', $url, $matches))
{
$url = str_replace($matches[0], $matches[1].Vfs::concat($matches[2], substr($parts['path'],strlen($mounted))), $url);
}
if ($replace_user_pass_host)
{
$url = str_replace(array('$user','$pass','$host','$home'),array($parts['user'],$parts['pass'],$parts['host'],$parts['home']),$url);
}
if ($parts['query']) $url .= '?'.$parts['query'];
if ($parts['fragment']) $url .= '#'.$parts['fragment'];
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$url'");
if ($replace_user_pass_host) self::$resolve_url_cache[$path] = $url;
return $url;
}
}
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path') can't resolve path!\n");
trigger_error(__METHOD__."($path) can't resolve path!\n",E_USER_WARNING);
return false;
}
/**
* Cache of already resolved symlinks
*
* @var array with path => target
*/
private static $symlink_cache = array();
/**
* Add a resolved symlink to cache
*
* @param string $_path vfs path
* @param string $target target path
*/
static protected function symlinkCache_add($_path,$target)
{
$path = self::get_path($_path);
if (isset(self::$symlink_cache[$path])) return; // nothing to do
if ($target[0] != '/') $target = Vfs::parse_url($target,PHP_URL_PATH);
self::$symlink_cache[$path] = $target;
// sort longest path first
uksort(self::$symlink_cache, function($b, $a)
{
return strlen($a) - strlen($b);
});
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,$target) cache now ".array2string(self::$symlink_cache));
}
/**
* Remove a resolved symlink from cache
*
* @param string $_path vfs path
*/
static public function symlinkCache_remove($_path)
{
$path = self::get_path($_path);
unset(self::$symlink_cache[$path]);
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path) cache now ".array2string(self::$symlink_cache));
}
/**
* Resolve a path from our symlink cache
*
* The cache is sorted from longer to shorter pathes.
*
* @param string $_path
* @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!)
* @return string target or path, if path not found
*/
public static function symlinkCache_resolve($_path, $do_symlink=true)
{
// remove vfs scheme, but no other schemes (eg. filesystem!)
$path = self::get_path($_path);
$strlen_path = strlen($path);
foreach(self::$symlink_cache as $p => $t)
{
if (($strlen_p = strlen($p)) > $strlen_path) continue; // $path can NOT start with $p
if ($path == $p)
{
if ($do_symlink) $target = $t;
break;
}
elseif (substr($path,0,$strlen_p+1) == $p.'/')
{
$target = $t . substr($path,$strlen_p);
break;
}
}
if (self::LOG_LEVEL > 1 && isset($target)) error_log(__METHOD__."($path) = $target");
return isset($target) ? $target : $path;
}
/**
* Clears our internal stat and symlink cache
*
* Normaly not necessary, as it is automatically cleared/updated, UNLESS Vfs::$user changes!
*/
static function clearstatcache()
{
self::$symlink_cache = self::$resolve_url_cache = array();
}
/**
* Load stream wrapper for a given schema
*
* @param string $scheme
* @return boolean
*/
static function load_wrapper($scheme)
{
if (!in_array($scheme,self::get_wrappers()))
{
switch($scheme)
{
case 'webdav':
case 'webdavs':
require_once('HTTP/WebDAV/Client.php');
self::$wrappers[] = $scheme;
break;
case '':
break; // default file, always loaded
default:
// check if scheme is buildin in php or one of our own stream wrappers
if (in_array($scheme,stream_get_wrappers()) || class_exists(self::scheme2class($scheme)))
{
self::$wrappers[] = $scheme;
}
else
{
trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING);
return false;
}
}
}
return true;
}
/**
* Return already loaded stream wrappers
*
* @return array
*/
static function get_wrappers()
{
if (is_null(self::$wrappers))
{
self::$wrappers = stream_get_wrappers();
}
return self::$wrappers;
}
/**
* Get the class-name for a scheme
*
* A scheme is not allowed to contain an underscore, but allows a dot and a class names only allow or need underscores, but no dots
* --> we replace dots in scheme with underscored to get the class-name
*
* @param string $scheme eg. vfs
* @return string
*/
static function scheme2class($scheme)
{
if ($scheme === self::SCHEME)
{
return __CLASS__;
}
list($app, $app_scheme) = explode('.', $scheme);
foreach(array(
empty($app_scheme) ? 'EGroupware\\Api\\Vfs\\'.ucfirst($scheme).'\\StreamWrapper' : // streamwrapper in Api\Vfs
'EGroupware\\'.ucfirst($app).'\\Vfs\\'.ucfirst($app_scheme).'\\StreamWrapper', // streamwrapper in $app\Vfs
str_replace('.','_',$scheme).'_stream_wrapper', // old (flat) name
) as $class)
{
//error_log(__METHOD__."('$scheme') class_exists('$class')=".array2string(class_exists($class)));
if (class_exists($class)) return $class;
}
}
/**
* Getting the path from an url (or path) AND removing trailing slashes
*
* @param string $path url or path (might contain trailing slash from WebDAV!)
* @param string $only_remove_scheme =self::SCHEME if given only that scheme get's removed
* @return string path without training slash
*/
static protected function get_path($path,$only_remove_scheme=self::SCHEME)
{
if ($path[0] != '/' && (!$only_remove_scheme || Vfs::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme))
{
$path = Vfs::parse_url($path, PHP_URL_PATH);
}
// remove trailing slashes eg. added by WebDAV, but do NOT remove / from "sqlfs://default/"!
if ($path != '/')
{
while (mb_substr($path, -1) == '/' && $path != '/' && ($path[0] == '/' || Vfs::parse_url($path, PHP_URL_PATH) != '/'))
{
$path = mb_substr($path,0,-1);
}
}
return $path;
}
/**
* Check if url contains ro=1 parameter to mark mount readonly
*
* @param string $url
* @return boolean
*/
static function url_is_readonly($url)
{
static $cache = array();
$ret =& $cache[$url];
if (!isset($ret))
{
$matches = null;
$ret = preg_match('/\?(.*&)?ro=([^&]+)/', $url, $matches) && $matches[2];
}
return $ret;
}
/**
* Allow to call methods of the underlying stream wrapper: touch, chmod, chgrp, chown, ...
*

View File

@ -193,8 +193,6 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
$this->operation = self::url2operation($url);
$dir = Vfs::dirname($url);
error_log(__METHOD__."('$url', $mode, $options) path=$path, context=".json_encode(stream_context_get_options($this->context)));
$this->opened_path = $opened_path = $path;
$this->opened_mode = $mode = str_replace('b','',$mode); // we are always binary, like every Linux system
$this->opened_stream = null;
@ -1250,10 +1248,6 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) $GLOBALS['egw']->invalidate_session_cache();
return $this->url_stat($url, $flags);
}
if (!isset($info['effectiv-uid']) && $this->context)
{
$info['effectiv-uid'] = stream_context_get_options($this->context)[Vfs::SCHEME]['user'];
}
self::$stat_cache[$path] = $info;
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$flags)=".array2string($info));
@ -1279,7 +1273,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
// 256 = 0400, 32 = 040
$sql_read_acl[$user] = '((fs_mode & 4)=4 OR (fs_mode & 256)=256 AND fs_uid='.$user.
($memberships ? ' OR (fs_mode & 32)=32 AND fs_gid IN('.implode(',',$memberships).')' : '').')';
error_log(__METHOD__."() user=".array2string($user).' --> memberships='.array2string($memberships).' --> '.$sql_read_acl.($memberships?'':': '.function_backtrace()));
//error_log(__METHOD__."() user=".array2string($user).' --> memberships='.array2string($memberships).' --> '.$sql_read_acl.($memberships?'':': '.function_backtrace()));
}
return $sql_read_acl[$user];
}
@ -1736,7 +1730,6 @@ GROUP BY A.fs_id';
// eGW addition to return some extra values
'mime' => $info['fs_mime'],
'readlink' => $info['fs_link'],
'effectiv-uid' => $info['effectiv-uid'],
);
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($info[name]) = ".array2string($stat));
return $stat;

View File

@ -28,25 +28,12 @@ class StreamWrapper extends Base implements StreamWrapperIface
use UserContextTrait;
const PREFIX = 'vfs://default';
/**
* Scheme / protocol used for this stream-wrapper
*/
const SCHEME = 'vfs';
/**
* Should unreadable entries in a not writable directory be hidden, default yes
*/
const HIDE_UNREADABLES = true;
/**
* How much should be logged to the apache error-log
*
* 0 = Nothing
* 1 = only errors
* 2 = all function calls and errors (contains passwords too!)
*/
const LOG_LEVEL = 1;
/**
* Maximum depth of symlinks, if exceeded url_stat will return false
*
@ -54,18 +41,6 @@ class StreamWrapper extends Base implements StreamWrapperIface
*/
const MAX_SYMLINK_DEPTH = 10;
/**
* Our fstab in the form mount-point => url
*
* The entry for root has to be the first, or more general if you mount into subdirs the parent has to be before!
*
* @var array
*/
protected static $fstab = array(
'/' => 'sqlfs://$host/',
'/apps' => 'links://$host/apps',
);
/**
* stream / ressouce this class is opened for by stream_open
*
@ -134,8 +109,6 @@ class StreamWrapper extends Base implements StreamWrapperIface
*/
private $extra_dir_ptr;
private static $wrappers;
/**
* Resolve the given path according to our fstab AND symlinks
*
@ -173,113 +146,6 @@ class StreamWrapper extends Base implements StreamWrapperIface
return $url;
}
/**
* Cache of already resolved urls
*
* @var array with path => target
*/
private static $resolve_url_cache = array();
/**
* Resolve the given path according to our fstab
*
* @param string $_path
* @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!)
* @param boolean $use_symlinkcache =true
* @param boolean $replace_user_pass_host =true replace $user,$pass,$host in url, default true, if false result is not cached
* @param boolean $fix_url_query =false true append relativ path to url query parameter, default not
* @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry
*/
static function resolve_url($_path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true,$fix_url_query=false)
{
$path = self::get_path($_path);
// we do some caching here
if (isset(self::$resolve_url_cache[$path]) && $replace_user_pass_host)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '".self::$resolve_url_cache[$path]."' (from cache)");
return self::$resolve_url_cache[$path];
}
// check if we can already resolve path (or a part of it) with a known symlinks
if ($use_symlinkcache)
{
$path = self::symlinkCache_resolve($path,$do_symlink);
}
// setting default user, passwd and domain, if it's not contained int the url
$defaults = array(
'user' => $GLOBALS['egw_info']['user']['account_lid'],
'pass' => urlencode($GLOBALS['egw_info']['user']['passwd']),
'host' => $GLOBALS['egw_info']['user']['domain'],
'home' => str_replace(array('\\\\','\\'),array('','/'),$GLOBALS['egw_info']['user']['homedirectory']),
);
$parts = array_merge(Vfs::parse_url($path),$defaults);
if (!$parts['host']) $parts['host'] = 'default'; // otherwise we get an invalid url (scheme:///path/to/something)!
if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$path' (path is already an url)");
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 == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1))
{
$scheme = Vfs::parse_url($url,PHP_URL_SCHEME);
if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers))
{
self::load_wrapper($scheme);
}
if (($relative = substr($parts['path'],strlen($mounted))))
{
$url = Vfs::concat($url,$relative);
}
// if url contains url parameter, eg. from filesystem streamwrapper, we need to append relative path here too
$matches = null;
if ($fix_url_query && preg_match('|([?&]url=)([^&]+)|', $url, $matches))
{
$url = str_replace($matches[0], $matches[1].Vfs::concat($matches[2], substr($parts['path'],strlen($mounted))), $url);
}
if ($replace_user_pass_host)
{
$url = str_replace(array('$user','$pass','$host','$home'),array($parts['user'],$parts['pass'],$parts['host'],$parts['home']),$url);
}
if ($parts['query']) $url .= '?'.$parts['query'];
if ($parts['fragment']) $url .= '#'.$parts['fragment'];
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$url'");
if ($replace_user_pass_host) self::$resolve_url_cache[$path] = $url;
return $url;
}
}
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path') can't resolve path!\n");
trigger_error(__METHOD__."($path) can't resolve path!\n",E_USER_WARNING);
return false;
}
/**
* Returns mount url of a full url returned by resolve_url
*
* @param string $fullurl full url returned by resolve_url
* @return string|NULL mount url or null if not found
*/
static function mount_url($fullurl)
{
foreach(array_reverse(self::$fstab) as $url)
{
list($url_no_query) = explode('?',$url);
if (substr($fullurl,0,1+strlen($url_no_query)) === $url_no_query.'/')
{
return $url;
}
}
return null;
}
/**
* This method is called immediately after your stream object is created.
*
@ -297,8 +163,6 @@ class StreamWrapper extends Base implements StreamWrapperIface
unset($options,$opened_path); // not used but required by function signature
$this->opened_stream = null;
error_log(__METHOD__."('$path', $mode, $options) context=".json_encode(stream_context_get_options($this->context)));
$stat = null;
if (!($url = $this->resolve_url_symlinks($path,$mode[0]=='r',true,$stat)))
{
@ -820,7 +684,6 @@ class StreamWrapper extends Base implements StreamWrapperIface
}
try {
clearstatcache(); // testwise, NOT good for performance
if ($flags & STREAM_URL_STAT_LINK)
{
$stat = @lstat($url); // suppressed the stat failed warnings
@ -877,7 +740,6 @@ clearstatcache(); // testwise, NOT good for performance
// if numer of tries is exceeded, re-throw exception
throw $e;
}
clearstatcache(); // testwise, NOT good for performance
// check if a failed url_stat was for a home dir, in that case silently create it
if (!$stat && $try_create_home && Vfs::dirname(Vfs::parse_url($path,PHP_URL_PATH)) == '/home' &&
($id = $GLOBALS['egw']->accounts->name2id(Vfs::basename($path))) &&
@ -947,6 +809,26 @@ clearstatcache(); // testwise, NOT good for performance
return self::_call_on_backend('check_extended_acl', [$url, $check], true, 0, true); // true = fail silent if backend does not support
}
/**
* 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
*/
function has_owner_rights($path,array $stat=null)
{
if (!$stat)
{
$stat = $this->url_stat($path,0);
}
return $stat['uid'] == $this->user && // (current) user is the owner
Vfs::$is_root || // class runs with root rights
!$stat['uid'] && $stat['gid'] && Vfs::$is_admin; // group directory and user is an eGW admin
}
/**
* Check if path (which fails the stat call) contains symlinks in path-components other then the last one
*
@ -991,95 +873,6 @@ clearstatcache(); // testwise, NOT good for performance
return false; // $path does not exist
}
/**
* Cache of already resolved symlinks
*
* @var array with path => target
*/
private static $symlink_cache = array();
/**
* Add a resolved symlink to cache
*
* @param string $_path vfs path
* @param string $target target path
*/
static protected function symlinkCache_add($_path,$target)
{
$path = self::get_path($_path);
if (isset(self::$symlink_cache[$path])) return; // nothing to do
if ($target[0] != '/') $target = Vfs::parse_url($target,PHP_URL_PATH);
self::$symlink_cache[$path] = $target;
// sort longest path first
uksort(self::$symlink_cache, function($b, $a)
{
return strlen($a) - strlen($b);
});
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,$target) cache now ".array2string(self::$symlink_cache));
}
/**
* Remove a resolved symlink from cache
*
* @param string $_path vfs path
*/
static public function symlinkCache_remove($_path)
{
$path = self::get_path($_path);
unset(self::$symlink_cache[$path]);
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path) cache now ".array2string(self::$symlink_cache));
}
/**
* Resolve a path from our symlink cache
*
* The cache is sorted from longer to shorter pathes.
*
* @param string $_path
* @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!)
* @return string target or path, if path not found
*/
static public function symlinkCache_resolve($_path,$do_symlink=true)
{
// remove vfs scheme, but no other schemes (eg. filesystem!)
$path = self::get_path($_path);
$strlen_path = strlen($path);
foreach(self::$symlink_cache as $p => $t)
{
if (($strlen_p = strlen($p)) > $strlen_path) continue; // $path can NOT start with $p
if ($path == $p)
{
if ($do_symlink) $target = $t;
break;
}
elseif (substr($path,0,$strlen_p+1) == $p.'/')
{
$target = $t . substr($path,$strlen_p);
break;
}
}
if (self::LOG_LEVEL > 1 && isset($target)) error_log(__METHOD__."($path) = $target");
return isset($target) ? $target : $path;
}
/**
* Clears our internal stat and symlink cache
*
* Normaly not necessary, as it is automatically cleared/updated, UNLESS Vfs::$user changes!
*/
static function clearstatcache()
{
self::$symlink_cache = self::$resolve_url_cache = array();
}
/**
* This method is called in response to readdir().
*
@ -1142,234 +935,6 @@ clearstatcache(); // testwise, NOT good for performance
return $ret;
}
/**
* Load stream wrapper for a given schema
*
* @param string $scheme
* @return boolean
*/
static function load_wrapper($scheme)
{
if (!in_array($scheme,self::get_wrappers()))
{
switch($scheme)
{
case 'webdav':
case 'webdavs':
require_once('HTTP/WebDAV/Client.php');
self::$wrappers[] = $scheme;
break;
case '':
break; // default file, always loaded
default:
// check if scheme is buildin in php or one of our own stream wrappers
if (in_array($scheme,stream_get_wrappers()) || class_exists(self::scheme2class($scheme)))
{
self::$wrappers[] = $scheme;
}
else
{
trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING);
return false;
}
}
}
return true;
}
/**
* Return already loaded stream wrappers
*
* @return array
*/
static function get_wrappers()
{
if (is_null(self::$wrappers))
{
self::$wrappers = stream_get_wrappers();
}
return self::$wrappers;
}
/**
* Get the class-name for a scheme
*
* A scheme is not allowed to contain an underscore, but allows a dot and a class names only allow or need underscores, but no dots
* --> we replace dots in scheme with underscored to get the class-name
*
* @param string $scheme eg. vfs
* @return string
*/
static function scheme2class($scheme)
{
if ($scheme === self::SCHEME)
{
return __CLASS__;
}
list($app, $app_scheme) = explode('.', $scheme);
foreach(array(
empty($app_scheme) ? 'EGroupware\\Api\\Vfs\\'.ucfirst($scheme).'\\StreamWrapper' : // streamwrapper in Api\Vfs
'EGroupware\\'.ucfirst($app).'\\Vfs\\'.ucfirst($app_scheme).'\\StreamWrapper', // streamwrapper in $app\Vfs
str_replace('.','_',$scheme).'_stream_wrapper', // old (flat) name
) as $class)
{
//error_log(__METHOD__."('$scheme') class_exists('$class')=".array2string(class_exists($class)));
if (class_exists($class)) return $class;
}
}
/**
* Getting the path from an url (or path) AND removing trailing slashes
*
* @param string $path url or path (might contain trailing slash from WebDAV!)
* @param string $only_remove_scheme =self::SCHEME if given only that scheme get's removed
* @return string path without training slash
*/
static protected function get_path($path,$only_remove_scheme=self::SCHEME)
{
if ($path[0] != '/' && (!$only_remove_scheme || Vfs::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme))
{
$path = Vfs::parse_url($path, PHP_URL_PATH);
}
// remove trailing slashes eg. added by WebDAV, but do NOT remove / from "sqlfs://default/"!
if ($path != '/')
{
while (mb_substr($path, -1) == '/' && $path != '/' && ($path[0] == '/' || Vfs::parse_url($path, PHP_URL_PATH) != '/'))
{
$path = mb_substr($path,0,-1);
}
}
return $path;
}
/**
* Check if url contains ro=1 parameter to mark mount readonly
*
* @param string $url
* @return boolean
*/
static function url_is_readonly($url)
{
static $cache = array();
$ret =& $cache[$url];
if (!isset($ret))
{
$matches = null;
$ret = preg_match('/\?(.*&)?ro=([^&]+)/', $url, $matches) && $matches[2];
}
return $ret;
}
/**
* Mounts $url under $path in the vfs, called without parameter it returns the fstab
*
* The fstab is stored in the eGW configuration and used for all eGW users.
*
* @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. /
* @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
* @param boolean $persitent_mount =true create a persitent mount, or only a temprary for current request
* @param boolean $clear_fstab =false true clear current fstab, false (default) only add given mount
* @return array|boolean array with fstab, if called without parameter or true on successful mount
*/
static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true,$clear_fstab=false)
{
if (is_null($check_url)) $check_url = strpos($url,'$') === false;
if (!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup
{
$api_config = Api\Config::read('phpgwapi');
if (isset($api_config['vfs_fstab']) && is_array($api_config['vfs_fstab']))
{
self::$fstab = $api_config['vfs_fstab'];
}
else
{
self::$fstab = array(
'/' => 'sqlfs://$host/',
'/apps' => 'links://$host/apps',
);
}
unset($api_config);
}
if (is_null($url) || is_null($path))
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns '.array2string(self::$fstab));
return self::$fstab;
}
if (!Vfs::$is_root)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') permission denied, you are NOT root!');
return false; // only root can mount
}
if ($clear_fstab)
{
self::$fstab = array();
}
if (isset(self::$fstab[$path]) && self::$fstab[$path] === $url)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.');
return true; // already mounted
}
self::load_wrapper(Vfs::parse_url($url,PHP_URL_SCHEME));
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
}
self::$fstab[$path] = $url;
uksort(self::$fstab, function($a, $b)
{
return strlen($a) - strlen($b);
});
if ($persitent_mount)
{
Api\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;
}
/**
* Unmounts a filesystem part of the vfs
*
* @param string $path url or path of the filesystem to unmount
*/
static function umount($path)
{
if (!Vfs::$is_root)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).','.array2string($path).') permission denied, you are NOT root!');
return false; // only root can mount
}
if (!isset(self::$fstab[$path]) && ($path = array_search($path,self::$fstab)) === false)
{
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).') NOT mounted!');
return false; // $path not mounted
}
unset(self::$fstab[$path]);
Api\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($path).') returns true (successful unmount).');
return true;
}
/**
* Init our static properties and register this wrapper
*