mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 14:41:29 +01:00
change sharing to allow mounting shared into existing user sessions
without the need to destroy the session, if the sharee is a different user This is accomplished by keeping the sharee in the stream context / attribute of the vfs class(es) instead of static Vfs::$user. Later is still used for the current user - identical to egw_info[user][account_id]. This commit / merge of the vfs-context feature branch also added the abilty to mount WebDAV sources, eg. a sharing link from an other EGroupware instance or any WebDAV server
This commit is contained in:
parent
7ae230356f
commit
4886583cc1
@ -57,9 +57,9 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
* The anonymous user probably doesn't have the needed permissions to access
|
||||
* the record, so we should set that up to avoid permission errors
|
||||
*/
|
||||
protected function after_login()
|
||||
protected static function after_login(array $share)
|
||||
{
|
||||
list($app) = explode('::', $this->share['share_path']);
|
||||
list($app) = explode('::', $share['share_path']);
|
||||
|
||||
// allow app (gets overwritten by session::create)
|
||||
$GLOBALS['egw_info']['flags']['currentapp'] = $app;
|
||||
|
@ -2019,10 +2019,11 @@ class Session
|
||||
/**
|
||||
* Initialise the used session handler
|
||||
*
|
||||
* @param string? $sessionid =null default use self::get_sessionid()
|
||||
* @return boolean true if we have a session, false otherwise
|
||||
* @throws \ErrorException if there is no PHP session support
|
||||
*/
|
||||
public static function init_handler()
|
||||
public static function init_handler($sessionid=null)
|
||||
{
|
||||
switch(session_status())
|
||||
{
|
||||
@ -2032,7 +2033,7 @@ class Session
|
||||
if (headers_sent()) return false; // only gives warnings
|
||||
ini_set('session.use_cookies',0); // disable the automatic use of cookies, as it uses the path / by default
|
||||
session_name(self::EGW_SESSION_NAME);
|
||||
if (($sessionid = self::get_sessionid()))
|
||||
if (isset($sessionid) || ($sessionid = self::get_sessionid()))
|
||||
{
|
||||
session_id($sessionid);
|
||||
self::cache_control();
|
||||
|
@ -95,16 +95,16 @@ class Sharing
|
||||
*/
|
||||
public static function get_token()
|
||||
{
|
||||
// WebDAV has no concept of a query string and clients (including cadaver)
|
||||
// seem to pass '?' unencoded, so we need to extract the path info out
|
||||
// of the request URI ourselves
|
||||
// if request URI contains a full url, remove schema and domain
|
||||
// WebDAV has no concept of a query string and clients (including cadaver)
|
||||
// seem to pass '?' unencoded, so we need to extract the path info out
|
||||
// of the request URI ourselves
|
||||
// if request URI contains a full url, remove schema and domain
|
||||
$matches = null;
|
||||
if (preg_match('|^https?://[^/]+(/.*)$|', $path_info=$_SERVER['REQUEST_URI'], $matches))
|
||||
{
|
||||
$path_info = $matches[1];
|
||||
}
|
||||
$path_info = substr($path_info, strlen($_SERVER['SCRIPT_NAME']));
|
||||
if (preg_match('|^https?://[^/]+(/.*)$|', $path_info=$_SERVER['REQUEST_URI'], $matches))
|
||||
{
|
||||
$path_info = $matches[1];
|
||||
}
|
||||
$path_info = substr($path_info, strlen($_SERVER['SCRIPT_NAME']));
|
||||
list(, $token/*, $path*/) = preg_split('|[/?]|', $path_info, 3);
|
||||
|
||||
list($token) = explode(':', $token);
|
||||
@ -140,16 +140,20 @@ class Sharing
|
||||
/**
|
||||
* Create sharing session
|
||||
*
|
||||
* Certain cases:
|
||||
* a) there is not session $keep_session === null
|
||||
* --> create new anon session with just filemanager rights and share as fstab
|
||||
* b) there is a session $keep_session === true
|
||||
* b1) current user is share owner (eg. checking the link)
|
||||
* --> mount share under token additionally
|
||||
* b2) current user not share owner
|
||||
* b2a) need/use filemanager UI (eg. directory)
|
||||
* --> destroy current session and continue with a)
|
||||
* b2b) single file or WebDAV
|
||||
* There are two cases:
|
||||
*
|
||||
* 1) there is no session $keep_session === null
|
||||
* --> create new anon session with just filemanager rights and resolved share incl. sharee as only fstab entry
|
||||
*
|
||||
* 2) there is a (non-anonymous) session $keep_session === true
|
||||
* --> mount share with sharing stream-wrapper into users "shares" subdirectory of home directory
|
||||
* and ask user if he wants the share permanently mounted there
|
||||
*
|
||||
* Even with sharing stream-wrapper a) and b) need to be different, as sharing SW needs an intact fstab!
|
||||
*
|
||||
* Not yet sure if this still needs extra handling:
|
||||
*
|
||||
* 2a) single file or WebDAV
|
||||
* --> modify EGroupware enviroment for that request only, no change in session
|
||||
*
|
||||
* @param boolean $keep_session =null null: create a new session, true: try mounting it into existing (already verified) session
|
||||
@ -168,11 +172,22 @@ class Sharing
|
||||
return '';
|
||||
}
|
||||
|
||||
protected static function check_token($keep_session, &$share)
|
||||
/**
|
||||
* Check sharing token
|
||||
*
|
||||
* @param boolean $keep_session false: does NOT check/fidle with session, true: return if session belongs to token
|
||||
* @param array& $share on return information about the share
|
||||
* @param ?string $token default call self::get_token() to get it from the URL
|
||||
* @param ?string $password default $_SERVER['PHP_AUTH_PW']
|
||||
* @throws Exception
|
||||
* @throws Exception\NoPermission
|
||||
* @throws Exception\NotFound
|
||||
*/
|
||||
public static function check_token($keep_session, &$share, $token=null, $password=null)
|
||||
{
|
||||
self::$db = $GLOBALS['egw']->db;
|
||||
|
||||
$token = static::get_token();
|
||||
if (!isset($token)) $token = static::get_token();
|
||||
|
||||
// are we called from header include, because session did not verify
|
||||
// --> check if it verifys for our token
|
||||
@ -203,7 +218,7 @@ class Sharing
|
||||
);
|
||||
}
|
||||
// check password, if required
|
||||
if(!static::check_password($share))
|
||||
if(!static::check_password($share, $password))
|
||||
{
|
||||
$realm = 'EGroupware share '.$share['share_token'];
|
||||
header('WWW-Authenticate: Basic realm="'.$realm.'"');
|
||||
@ -220,14 +235,17 @@ class Sharing
|
||||
* provided matches.
|
||||
*
|
||||
* @param Array $share
|
||||
* @param ?string $password default $_SERVER['PHP_AUTH_PW']
|
||||
* @return boolean Password OK (or not needed)
|
||||
*/
|
||||
protected static function check_password(Array $share)
|
||||
protected static function check_password(Array $share, $password=null)
|
||||
{
|
||||
if ($share['share_passwd'] && (empty($_SERVER['PHP_AUTH_PW']) ||
|
||||
!(Auth::compare_password($_SERVER['PHP_AUTH_PW'], $share['share_passwd'], 'crypt') ||
|
||||
Header\Authenticate::decode_password($_SERVER['PHP_AUTH_PW']) &&
|
||||
Auth::compare_password($_SERVER['PHP_AUTH_PW'], $share['share_passwd'], 'crypt'))))
|
||||
if (!isset($password)) $password = $_SERVER['PHP_AUTH_PW'];
|
||||
|
||||
if ($share['share_passwd'] && (empty($password) ||
|
||||
!(Auth::compare_password($password, $share['share_passwd'], 'crypt') ||
|
||||
Header\Authenticate::decode_password($password) &&
|
||||
Auth::compare_password($password, $share['share_passwd'], 'crypt'))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -246,7 +264,7 @@ class Sharing
|
||||
* Sub-class specific things needed to be done to the share (or session)
|
||||
* after we login but before we start actually doing anything
|
||||
*/
|
||||
protected function after_login() {}
|
||||
protected static function after_login(array $share) {}
|
||||
|
||||
|
||||
protected static function login($keep_session, &$share)
|
||||
@ -267,7 +285,7 @@ class Sharing
|
||||
{
|
||||
$sessionid = static::create_new_session();
|
||||
|
||||
$GLOBALS['egw']->sharing->after_login();
|
||||
static::after_login($share);
|
||||
}
|
||||
// we have a session we want to keep, but share owner is different from current user and we dont need filemanager UI
|
||||
// --> we dont need session and close it, to not modifiy it
|
||||
@ -275,8 +293,7 @@ class Sharing
|
||||
{
|
||||
$GLOBALS['egw']->session->commit_session();
|
||||
}
|
||||
// need to store new fstab and vfs_user in session to allow GET requests / downloads via WebDAV
|
||||
$GLOBALS['egw_info']['user']['vfs_user'] = Vfs::$user;
|
||||
// need to store new fstab in session to allow GET requests / downloads via WebDAV
|
||||
$GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount();
|
||||
|
||||
// update modified egw and egw_info again in session, if neccessary
|
||||
@ -369,7 +386,7 @@ class Sharing
|
||||
$class = strpos($status, '404') === 0 ? 'EGroupware\Api\Exception\NotFound' :
|
||||
strpos($status, '401') === 0 ? 'EGroupware\Api\Exception\NoPermission' :
|
||||
'EGroupware\Api\Exception';
|
||||
throw new $class($message);
|
||||
throw new $class($message, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -490,7 +507,7 @@ class Sharing
|
||||
public function ServeRequest()
|
||||
{
|
||||
// sharing is for a different share, change to current share
|
||||
if ($this->share['share_token'] !== self::get_token())
|
||||
if (empty($this->share['skip_validate_token']) && $this->share['share_token'] !== self::get_token())
|
||||
{
|
||||
// to keep the session we require the regular user flag "N" AND a user-name not equal to "anonymous"
|
||||
self::create_session($GLOBALS['egw']->session->session_flags === 'N' &&
|
||||
@ -499,14 +516,14 @@ class Sharing
|
||||
return $GLOBALS['egw']->sharing->ServeRequest();
|
||||
}
|
||||
|
||||
// No extended ACL for readonly shares, disable eacl by setting session cache
|
||||
/* No extended ACL for readonly shares, disable eacl by setting session cache
|
||||
if(!($this->share['share_writable'] & 1))
|
||||
{
|
||||
Cache::setSession(Vfs\Sqlfs\StreamWrapper::EACL_APPNAME, 'extended_acl', array(
|
||||
'/' => 1,
|
||||
$this->share['share_path'] => 1
|
||||
));
|
||||
}
|
||||
}*/
|
||||
if($this->use_collabora())
|
||||
{
|
||||
$ui = new \EGroupware\Collabora\Ui();
|
||||
|
349
api/src/Vfs.php
349
api/src/Vfs.php
@ -7,7 +7,7 @@
|
||||
* @package api
|
||||
* @subpackage vfs
|
||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @copyright (c) 2008-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @copyright (c) 2008-20 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api;
|
||||
@ -66,45 +66,15 @@ use HTTP_WebDAV_Server;
|
||||
* Vfs::parse_url($url, $component=-1), Vfs::dirname($url) and Vfs::basename($url) work
|
||||
* on urls containing utf-8 characters, which get NOT urlencoded in our VFS!
|
||||
*/
|
||||
class Vfs
|
||||
class Vfs extends Vfs\Base
|
||||
{
|
||||
const PREFIX = 'vfs://default';
|
||||
/**
|
||||
* Scheme / protocol used for this stream-wrapper
|
||||
*/
|
||||
const SCHEME = Vfs\StreamWrapper::SCHEME;
|
||||
/**
|
||||
* Mime type of directories, the old vfs used 'Directory', while eg. WebDAV uses 'httpd/unix-directory'
|
||||
*/
|
||||
const DIR_MIME_TYPE = Vfs\StreamWrapper::DIR_MIME_TYPE;
|
||||
/**
|
||||
* Readable bit, for dirs traversable
|
||||
*/
|
||||
const READABLE = 4;
|
||||
/**
|
||||
* Writable bit, for dirs delete or create files in that dir
|
||||
*/
|
||||
const WRITABLE = 2;
|
||||
/**
|
||||
* Excecutable bit, here only use to check if user is allowed to search dirs
|
||||
*/
|
||||
const EXECUTABLE = 1;
|
||||
/**
|
||||
* mode-bits, which have to be set for links
|
||||
*/
|
||||
const MODE_LINK = Vfs\StreamWrapper::MODE_LINK;
|
||||
const PREFIX = Vfs\StreamWrapper::PREFIX;
|
||||
|
||||
/**
|
||||
* 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!
|
||||
*
|
||||
@ -325,45 +295,6 @@ class Vfs
|
||||
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_
|
||||
*
|
||||
@ -384,6 +315,7 @@ class Vfs
|
||||
*
|
||||
* @param string|array $base base of the search
|
||||
* @param array $options =null the following keys are allowed:
|
||||
* <code>
|
||||
* - type => {d|f|F|!l} d=dirs, f=files (incl. symlinks), F=files (incl. symlinks to files), !l=no symlinks, default all
|
||||
* - depth => {true|false(default)} put the contents of a dir before the dir itself
|
||||
* - dirsontop => {true(default)|false} allways return dirs before the files (two distinct blocks)
|
||||
@ -403,7 +335,8 @@ class Vfs
|
||||
* - follow => {true|false(default)} follow symlinks
|
||||
* - hidden => {true|false(default)} include hidden files (name starts with a '.' or is Thumbs.db)
|
||||
* - show-deleted => {true|false(default)} get also set by hidden, if not explicitly set otherwise (requires versioning!)
|
||||
* @param string|array/true $exec =null function to call with each found file/dir as first param and stat array as last param or
|
||||
* </code>
|
||||
* @param string|array|true $exec =null function to call with each found file/dir as first param and stat array as last param or
|
||||
* true to return file => stat pairs
|
||||
* @param array $exec_params =null further params for exec as array, path is always the first param and stat the last!
|
||||
* @return array of pathes if no $exec, otherwise path => stat pairs
|
||||
@ -421,11 +354,11 @@ class Vfs
|
||||
// process some of the options (need to be done only once)
|
||||
if (isset($options['name']) && !isset($options['name_preg'])) // change from simple *,? wildcards to preg regular expression once
|
||||
{
|
||||
$options['name_preg'] = '/^'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($options['name'])).'$/i';
|
||||
$options['name_preg'] = '/^'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($options['name'], '/')).'$/i';
|
||||
}
|
||||
if (isset($options['path']) && !isset($options['preg_path'])) // change from simple *,? wildcards to preg regular expression once
|
||||
{
|
||||
$options['path_preg'] = '/^'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($options['path'])).'$/i';
|
||||
$options['path_preg'] = '/^'.str_replace(array('\\?','\\*'),array('.{1}','.*'),preg_quote($options['path'], '/')).'$/i';
|
||||
}
|
||||
if (!isset($options['uid']))
|
||||
{
|
||||
@ -460,7 +393,11 @@ class Vfs
|
||||
}
|
||||
|
||||
// make all find options available as stream context option "find", to allow plugins to use them
|
||||
$context = stream_context_create(array(self::SCHEME => array('find' => $options)));
|
||||
$context = stream_context_create([
|
||||
self::SCHEME => [
|
||||
'find' => $options,
|
||||
],
|
||||
]);
|
||||
|
||||
$url = $options['url'];
|
||||
|
||||
@ -789,7 +726,7 @@ class Vfs
|
||||
* 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 string $path
|
||||
* @param string $path or url
|
||||
* @param int $check mode to check: one or more or'ed together of: 4 = self::READABLE,
|
||||
* 2 = self::WRITABLE, 1 = self::EXECUTABLE
|
||||
* @return boolean
|
||||
@ -803,12 +740,13 @@ class Vfs
|
||||
* 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 string $path path
|
||||
* @param string $path path or url
|
||||
* @param int $check mode to check: one or more or'ed together of: 4 = self::READABLE,
|
||||
* 2 = self::WRITABLE, 1 = self::EXECUTABLE
|
||||
* @param array|boolean $stat =null stat array or false, to not query it again
|
||||
* @param int $user =null user used for check, if not current user (self::$user)
|
||||
* @return boolean
|
||||
* @todo deprecated or even remove $user parameter and code
|
||||
*/
|
||||
static function check_access($path, $check, $stat=null, $user=null)
|
||||
{
|
||||
@ -855,82 +793,15 @@ class Vfs
|
||||
return $ret;
|
||||
}
|
||||
|
||||
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 Exception\WrongParameter('path has to be string, use check_access($path,$check,$stat=null)!');
|
||||
}
|
||||
// query stat array, if not given
|
||||
if (is_null($stat))
|
||||
{
|
||||
if (!isset($vfs)) $vfs = new Vfs\StreamWrapper();
|
||||
$stat = $vfs->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__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!");
|
||||
return false; // file not found
|
||||
}
|
||||
// check if we use an EGroupwre stream wrapper, or a stock php one
|
||||
// if it's not an EGroupware one, we can NOT use uid, gid and mode!
|
||||
if (($scheme = self::parse_url($stat['url'],PHP_URL_SCHEME)) && !(class_exists(self::scheme2class($scheme))))
|
||||
{
|
||||
switch($check)
|
||||
{
|
||||
case self::READABLE:
|
||||
return is_readable($stat['url']);
|
||||
case self::WRITABLE:
|
||||
return is_writable($stat['url']);
|
||||
case self::EXECUTABLE:
|
||||
return is_executable($stat['url']);
|
||||
}
|
||||
}
|
||||
// check if other rights grant access
|
||||
if (($stat['mode'] & $check) == $check)
|
||||
{
|
||||
//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__."(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
|
||||
if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid'])
|
||||
{
|
||||
if (($memberships = $GLOBALS['egw']->accounts->memberships(self::$user, true)) && in_array(-abs($stat['gid']), $memberships))
|
||||
{
|
||||
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// if we check writable and have a readonly mount --> return false, as backends dont know about r/o url parameter
|
||||
if ($check == self::WRITABLE && Vfs\StreamWrapper::url_is_readonly($stat['url']))
|
||||
{
|
||||
//error_log(__METHOD__."(path=$path, check=writable, ...) failed because mount is readonly");
|
||||
return false;
|
||||
}
|
||||
// check backend for extended acls (only if path given)
|
||||
$ret = $path && self::_call_on_backend('check_extended_acl',array(isset($stat['url'])?$stat['url']:$path,$check),true); // true = fail silent if backend does not support
|
||||
|
||||
//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;
|
||||
if (!isset($vfs)) $vfs = new Vfs\StreamWrapper($path);
|
||||
return $vfs->check_access($path, $check, $stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string $path
|
||||
* @param string $path or url
|
||||
* @return boolean
|
||||
*/
|
||||
static function is_writable($path)
|
||||
@ -942,7 +813,7 @@ class Vfs
|
||||
* 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 string $path
|
||||
* @param string $path or url
|
||||
* @return boolean
|
||||
*/
|
||||
static function is_executable($path)
|
||||
@ -953,7 +824,7 @@ class Vfs
|
||||
/**
|
||||
* Check if path is a script and write access would be denied by backend
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path or url
|
||||
* @return boolean true if $path is a script AND exec mount-option is NOT set, false otherwise
|
||||
*/
|
||||
static function deny_script($path)
|
||||
@ -1038,7 +909,7 @@ class Vfs
|
||||
*/
|
||||
static function proppatch($path,array $props)
|
||||
{
|
||||
return self::_call_on_backend('proppatch',array($path,$props));
|
||||
return self::_call_on_backend('proppatch', [$path,$props], false, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1057,7 +928,7 @@ class Vfs
|
||||
*/
|
||||
static function propfind($path,$ns=self::DEFAULT_PROP_NAMESPACE)
|
||||
{
|
||||
return self::_call_on_backend('propfind',array($path,$ns),true); // true = fail silent (no PHP Warning)
|
||||
return self::_call_on_backend('propfind', [$path, $ns],true, 0, true); // true = fail silent (no PHP Warning)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1380,21 +1251,12 @@ class Vfs
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1665,24 +1527,25 @@ class Vfs
|
||||
/**
|
||||
* lock a ressource/path
|
||||
*
|
||||
* @param string $path path or url
|
||||
* @param string $url url or path, lock is granted for the path only, but url is used for access checks
|
||||
* @param string &$token
|
||||
* @param int &$timeout
|
||||
* @param string &$owner
|
||||
* @param int|string &$owner account_id, account_lid or mailto-url
|
||||
* @param string &$scope
|
||||
* @param string &$type
|
||||
* @param boolean $update =false
|
||||
* @param boolean $check_writable =true should we check if the ressource is writable, before granting locks, default yes
|
||||
* @return boolean true on success
|
||||
*/
|
||||
static function lock($path,&$token,&$timeout,&$owner,&$scope,&$type,$update=false,$check_writable=true)
|
||||
static function lock($url, &$token, &$timeout, &$owner, &$scope, &$type, $update=false, $check_writable=true)
|
||||
{
|
||||
// we require write rights to lock/unlock a resource
|
||||
if (!$path || $update && !$token || $check_writable &&
|
||||
!(self::is_writable($path) || !self::file_exists($path) && ($dir=self::dirname($path)) && self::is_writable($dir)))
|
||||
if (!$url || $update && !$token || $check_writable &&
|
||||
!(self::is_writable($url) || !self::file_exists($url) && ($dir=self::dirname($url)) && self::is_writable($dir)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$path = self::parse_url($url, PHP_URL_PATH);
|
||||
// remove the lock info evtl. set in the cache
|
||||
unset(self::$lock_cache[$path]);
|
||||
|
||||
@ -1712,17 +1575,21 @@ class Vfs
|
||||
}
|
||||
}
|
||||
// HTTP_WebDAV_Server does this check before calling LOCK, but we want to be complete and usable outside WebDAV
|
||||
elseif(($lock = self::checkLock($path)) && ($lock['scope'] == 'exclusive' || $scope == 'exclusive'))
|
||||
elseif(($lock = self::checkLock($url)) && ($lock['scope'] == 'exclusive' || $scope == 'exclusive'))
|
||||
{
|
||||
$ret = false; // there's alread a lock
|
||||
}
|
||||
else
|
||||
{
|
||||
// HTTP_WebDAV_Server sets owner and token, but we want to be complete and usable outside WebDAV
|
||||
if (!$owner || $owner == 'unknown')
|
||||
if (!$owner || $owner === 'unknown')
|
||||
{
|
||||
$owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
|
||||
}
|
||||
elseif (($email = Accounts::id2name($owner, 'account_email')))
|
||||
{
|
||||
$owner = 'mailto:'.$email;
|
||||
}
|
||||
if (!$token)
|
||||
{
|
||||
require_once(__DIR__.'/WebDAV/Server.php');
|
||||
@ -1746,48 +1613,50 @@ class Vfs
|
||||
$ret = false; // there's already a lock
|
||||
}
|
||||
}
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($path,$token,$timeout,$owner,$scope,$type,update=$update,check_writable=$check_writable) returns ".($ret ? 'true' : 'false'));
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($url,$token,$timeout,$owner,$scope,$type,update=$update,check_writable=$check_writable) returns ".($ret ? 'true' : 'false'));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* unlock a ressource/path
|
||||
*
|
||||
* @param string $path path to unlock
|
||||
* @param string $url url or path, lock is granted for the path only, but url is used for access checks
|
||||
* @param string $token locktoken
|
||||
* @param boolean $check_writable =true should we check if the ressource is writable, before granting locks, default yes
|
||||
* @return boolean true on success
|
||||
*/
|
||||
static function unlock($path,$token,$check_writable=true)
|
||||
static function unlock($url,$token,$check_writable=true)
|
||||
{
|
||||
// we require write rights to lock/unlock a resource
|
||||
if ($check_writable && !self::is_writable($path))
|
||||
if ($check_writable && !self::is_writable($url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (($ret = self::$db->delete(self::LOCK_TABLE,array(
|
||||
'lock_path' => $path,
|
||||
'lock_token' => $token,
|
||||
),__LINE__,__FILE__) && self::$db->affected_rows()))
|
||||
{
|
||||
// remove the lock from the cache too
|
||||
unset(self::$lock_cache[$path]);
|
||||
}
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($path,$token,$check_writable) returns ".($ret ? 'true' : 'false'));
|
||||
$path = self::parse_url($url, PHP_URL_PATH);
|
||||
if (($ret = self::$db->delete(self::LOCK_TABLE,array(
|
||||
'lock_path' => $path,
|
||||
'lock_token' => $token,
|
||||
),__LINE__,__FILE__) && self::$db->affected_rows()))
|
||||
{
|
||||
// remove the lock from the cache too
|
||||
unset(self::$lock_cache[$path]);
|
||||
}
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($url,$token,$check_writable) returns ".($ret ? 'true' : 'false'));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* checkLock() helper
|
||||
*
|
||||
* @param string resource path to check for locks
|
||||
* @param string $url url or path, lock is granted for the path only, but url is used for access checks
|
||||
* @return array|boolean false if there's no lock, else array with lock info
|
||||
*/
|
||||
static function checkLock($path)
|
||||
static function checkLock($url)
|
||||
{
|
||||
$path = self::parse_url($url, PHP_URL_PATH);
|
||||
if (isset(self::$lock_cache[$path]))
|
||||
{
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($path) returns from CACHE ".str_replace(array("\n",' '),'',print_r(self::$lock_cache[$path],true)));
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($url) returns from CACHE ".str_replace(array("\n",' '),'',print_r(self::$lock_cache[$url],true)));
|
||||
return self::$lock_cache[$path];
|
||||
}
|
||||
$where = 'lock_path='.self::$db->quote($path);
|
||||
@ -1808,10 +1677,10 @@ class Vfs
|
||||
'lock_token' => $result['token'],
|
||||
),__LINE__,__FILE__);
|
||||
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($path) lock is expired at ".date('Y-m-d H:i:s',$result['expires'])." --> removed");
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($url) lock is expired at ".date('Y-m-d H:i:s',$result['expires'])." --> removed");
|
||||
$result = false;
|
||||
}
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($path) returns ".($result?array2string($result):'false'));
|
||||
if (self::LOCK_DEBUG) error_log(__METHOD__."($url) returns ".($result?array2string($result):'false'));
|
||||
return self::$lock_cache[$path] = $result;
|
||||
}
|
||||
|
||||
@ -1914,9 +1783,7 @@ class Vfs
|
||||
*/
|
||||
static function init_static()
|
||||
{
|
||||
// if special user/vfs_user given (eg. from sharing) use it instead default user/account_id
|
||||
self::$user = (int)(isset($GLOBALS['egw_info']['user']['vfs_user']) ?
|
||||
$GLOBALS['egw_info']['user']['vfs_user'] : $GLOBALS['egw_info']['user']['account_id']);
|
||||
self::$user = (int)$GLOBALS['egw_info']['user']['account_id'];
|
||||
self::$is_admin = isset($GLOBALS['egw_info']['user']['apps']['admin']);
|
||||
self::$db = isset($GLOBALS['egw_setup']->db) ? $GLOBALS['egw_setup']->db : $GLOBALS['egw']->db;
|
||||
self::$lock_cache = array();
|
||||
@ -2212,25 +2079,10 @@ class Vfs
|
||||
*/
|
||||
static function resolve_url_symlinks($_path,$file_exists=true,$resolve_last_symlink=true,&$stat=null)
|
||||
{
|
||||
$vfs = new Vfs\StreamWrapper();
|
||||
$vfs = new Vfs\StreamWrapper($_path);
|
||||
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.
|
||||
*
|
||||
@ -2276,77 +2128,6 @@ class Vfs
|
||||
return $path[0] == '/' && unlink(self::PREFIX.$path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to call methods of the underlying stream wrapper: touch, chmod, chgrp, chown, ...
|
||||
*
|
||||
* We cant use a magic __call() method, as it does not work for static methods!
|
||||
*
|
||||
* @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)
|
||||
* @param int $path_param_key =0 key in params containing the path, default 0
|
||||
* @return mixed return value of backend or false if function does not exist on backend
|
||||
*/
|
||||
static protected function _call_on_backend($name,$params,$fail_silent=false,$path_param_key=0)
|
||||
{
|
||||
$pathes = $params[$path_param_key];
|
||||
|
||||
$scheme2urls = array();
|
||||
foreach(is_array($pathes) ? $pathes : array($pathes) as $path)
|
||||
{
|
||||
if (!($url = self::resolve_url_symlinks($path,false,false)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$k=(string)self::parse_url($url,PHP_URL_SCHEME);
|
||||
if (!(is_array($scheme2urls[$k]))) $scheme2urls[$k] = array();
|
||||
$scheme2urls[$k][$path] = $url;
|
||||
}
|
||||
$ret = array();
|
||||
foreach($scheme2urls as $scheme => $urls)
|
||||
{
|
||||
if ($scheme)
|
||||
{
|
||||
if (!class_exists($class = self::scheme2class($scheme)) || !method_exists($class,$name))
|
||||
{
|
||||
if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
if (!is_array($pathes))
|
||||
{
|
||||
$params[$path_param_key] = $url;
|
||||
|
||||
return call_user_func_array(array($class,$name),$params);
|
||||
}
|
||||
$params[$path_param_key] = $urls;
|
||||
if (!is_array($r = call_user_func_array(array($class,$name),$params)))
|
||||
{
|
||||
return $r;
|
||||
}
|
||||
// we need to re-translate the urls to pathes, as they can eg. contain symlinks
|
||||
foreach($urls as $path => $url)
|
||||
{
|
||||
if (isset($r[$url]) || isset($r[$url=self::parse_url($url,PHP_URL_PATH)]))
|
||||
{
|
||||
$ret[$path] = $r[$url];
|
||||
}
|
||||
}
|
||||
}
|
||||
// call the filesystem specific function (dont allow to use arrays!)
|
||||
elseif(!function_exists($name) || is_array($pathes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$time = null;
|
||||
return $name($url,$time);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* touch just running on VFS path
|
||||
*
|
||||
@ -2412,7 +2193,7 @@ class Vfs
|
||||
*/
|
||||
static function readlink($path)
|
||||
{
|
||||
$ret = self::_call_on_backend('readlink',array($path),true); // true = fail silent, if backend does not support readlink
|
||||
$ret = self::_call_on_backend('readlink', [$path],true, 0, true); // true = fail silent, if backend does not support readlink
|
||||
//error_log(__METHOD__."('$path') returning ".array2string($ret).' '.function_backtrace());
|
||||
return $ret;
|
||||
}
|
||||
@ -2428,7 +2209,7 @@ class Vfs
|
||||
*/
|
||||
static function symlink($target,$link)
|
||||
{
|
||||
if (($ret = self::_call_on_backend('symlink',array($target,$link),false,1))) // 1=path is in $link!
|
||||
if (($ret = self::_call_on_backend('symlink', [$target, $link],false,1, true))) // 1=path is in $link!
|
||||
{
|
||||
Vfs\StreamWrapper::symlinkCache_remove($link);
|
||||
}
|
||||
@ -2515,9 +2296,9 @@ class Vfs
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
578
api/src/Vfs/Base.php
Normal file
578
api/src/Vfs/Base.php
Normal file
@ -0,0 +1,578 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware API: VFS - shared base of Vfs class and Vfs-stream-wrapper
|
||||
*
|
||||
* @link https://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-20 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs;
|
||||
|
||||
use EGroupware\Api\Config;
|
||||
use EGroupware\Api\Vfs;
|
||||
|
||||
/**
|
||||
* Shared base of Vfs class and Vfs-stream-wrapper
|
||||
*/
|
||||
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'
|
||||
*/
|
||||
const DIR_MIME_TYPE = 'httpd/unix-directory';
|
||||
/**
|
||||
* Readable bit, for dirs traversable
|
||||
*/
|
||||
const READABLE = 4;
|
||||
/**
|
||||
* Writable bit, for dirs delete or create files in that dir
|
||||
*/
|
||||
const WRITABLE = 2;
|
||||
/**
|
||||
* Excecutable bit, here only use to check if user is allowed to search dirs
|
||||
*/
|
||||
const EXECUTABLE = 1;
|
||||
/**
|
||||
* mode-bits, which have to be set for links
|
||||
*/
|
||||
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 = 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)
|
||||
{
|
||||
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]);
|
||||
|
||||
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, &$mounted=null)
|
||||
{
|
||||
foreach(array_reverse(self::$fstab) as $mounted => $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
|
||||
* @param ?string &$mounted =null on return mount-point of resolved url, IF $_path is a path or vfs-url, other urls return NULL!
|
||||
* @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, &$mounted=null)
|
||||
{
|
||||
$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)");
|
||||
$mounted = self::$resolve_url_cache[$path]['mounted'];
|
||||
return self::$resolve_url_cache[$path]['url'];
|
||||
}
|
||||
// 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 (($class = self::scheme2class($scheme)) && is_callable([$class, 'replace']))
|
||||
{
|
||||
if (!($replace = call_user_func([$class, 'replace'], $url)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$url = $replace;
|
||||
}
|
||||
if ($replace_user_pass_host) self::$resolve_url_cache[$path] = ['url' => $url, 'mounted' => $mounted];
|
||||
|
||||
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':
|
||||
\Grale\WebDav\StreamWrapper::register();
|
||||
self::$wrappers[] = 'webdav';
|
||||
self::$wrappers[] = 'webdavs';
|
||||
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, ...
|
||||
*
|
||||
* We cant use a magic __call() method, as it does not work for static methods!
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $params first param has to be the path, otherwise we can not determine the correct wrapper
|
||||
* @param boolean|"null" $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), or "null": return NULL
|
||||
* @param int $path_param_key =0 key in params containing the path, default 0
|
||||
* @param boolean $instanciate =false true: instanciate the class to call method $name, false: static call
|
||||
* @return mixed return value of backend or false if function does not exist on backend
|
||||
*/
|
||||
protected static function _call_on_backend($name, array $params, $fail_silent=false, $path_param_key=0, $instanciate=false)
|
||||
{
|
||||
$pathes = $params[$path_param_key];
|
||||
|
||||
$scheme2urls = array();
|
||||
foreach(is_array($pathes) ? $pathes : array($pathes) as $path)
|
||||
{
|
||||
if (!($url = Vfs::resolve_url_symlinks($path,false,false)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$k=(string)Vfs::parse_url($url,PHP_URL_SCHEME);
|
||||
if (!(is_array($scheme2urls[$k]))) $scheme2urls[$k] = array();
|
||||
$scheme2urls[$k][$path] = $url;
|
||||
}
|
||||
$ret = array();
|
||||
foreach($scheme2urls as $scheme => $urls)
|
||||
{
|
||||
if ($scheme)
|
||||
{
|
||||
if (!class_exists($class = Vfs\StreamWrapper::scheme2class($scheme)) || !method_exists($class,$name))
|
||||
{
|
||||
if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING);
|
||||
return $fail_silent === 'null' ? null : false;
|
||||
}
|
||||
$callback = [$instanciate ? new $class($url) : $class, $name];
|
||||
if (!is_array($pathes))
|
||||
{
|
||||
$params[$path_param_key] = $url;
|
||||
|
||||
return call_user_func_array($callback, $params);
|
||||
}
|
||||
$params[$path_param_key] = $urls;
|
||||
if (!is_array($r = call_user_func_array($callback, $params)))
|
||||
{
|
||||
return $r;
|
||||
}
|
||||
// we need to re-translate the urls to pathes, as they can eg. contain symlinks
|
||||
foreach($urls as $path => $url)
|
||||
{
|
||||
if (isset($r[$url]) || isset($r[$url=Vfs::parse_url($url,PHP_URL_PATH)]))
|
||||
{
|
||||
$ret[$path] = $r[$url];
|
||||
}
|
||||
}
|
||||
}
|
||||
// call the filesystem specific function (dont allow to use arrays!)
|
||||
elseif(!function_exists($name) || is_array($pathes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$time = null;
|
||||
return $name($url,$time);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* eGroupWare API: VFS - stream wrapper for linked files
|
||||
* 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-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @version $Id: class.sqlfs_stream_wrapper.inc.php 24997 2008-03-02 21:44:15Z ralfbecker $
|
||||
* @copyright (c) 2008-20 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs\Links;
|
||||
@ -76,7 +75,7 @@ class StreamWrapper extends LinksParent
|
||||
* @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)
|
||||
function check_extended_acl($url,$check)
|
||||
{
|
||||
if (Vfs::$is_root)
|
||||
{
|
||||
@ -96,7 +95,7 @@ class StreamWrapper extends LinksParent
|
||||
$access = !($check & Vfs::WRITABLE); // always grant read access to /apps
|
||||
$what = '!$app';
|
||||
}
|
||||
elseif (!self::check_app_rights($app))
|
||||
elseif (!$this->check_app_rights($app))
|
||||
{
|
||||
$access = false; // user has no access to the $app application
|
||||
$what = 'no app-rights';
|
||||
@ -113,8 +112,8 @@ class StreamWrapper extends LinksParent
|
||||
{
|
||||
// vfs & stream-wrapper use posix rights, Api\Link::file_access uses Api\Acl::{EDIT|READ}!
|
||||
$required = $check & Vfs::WRITABLE ? Api\Acl::EDIT : Api\Acl::READ;
|
||||
$access = Api\Link::file_access($app,$id,$required,$rel_path,Vfs::$user);
|
||||
$what = "from Api\Link::file_access('$app',$id,$required,'$rel_path,".Vfs::$user.")";
|
||||
$access = Api\Link::file_access($app, $id, $required, $rel_path, $this->user);
|
||||
$what = "from Api\Link::file_access('$app', $id, $required, '$rel_path,".$this->user.")";
|
||||
}
|
||||
if (self::DEBUG) error_log(__METHOD__."($url,$check) user=".Vfs::$user." ($what) ".($access?"access granted ($app:$id:$rel_path)":'no access!!!'));
|
||||
return $access;
|
||||
@ -126,18 +125,18 @@ class StreamWrapper extends LinksParent
|
||||
* @param string $app
|
||||
* @return boolean
|
||||
*/
|
||||
public static function check_app_rights($app)
|
||||
public function check_app_rights($app)
|
||||
{
|
||||
if ($GLOBALS['egw_info']['user']['account_id'] == Vfs::$user)
|
||||
if ($GLOBALS['egw_info']['user']['account_id'] == $this->user && isset($GLOBALS['egw_info']['user']['apps']))
|
||||
{
|
||||
return isset($GLOBALS['egw_info']['user']['apps'][$app]);
|
||||
}
|
||||
static $user_apps = array();
|
||||
if (!isset($user_apps[Vfs::$user]))
|
||||
if (!isset($user_apps[$this->user]))
|
||||
{
|
||||
$user_apps[Vfs::$user] = $GLOBALS['egw']->acl->get_user_applications(Vfs::$user);
|
||||
$user_apps[$this->user] = $GLOBALS['egw']->acl->get_user_applications($this->user);
|
||||
}
|
||||
return !empty($user_apps[Vfs::$user][$app]);
|
||||
return !empty($user_apps[$this->user][$app]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,48 +158,52 @@ class StreamWrapper extends LinksParent
|
||||
*/
|
||||
function url_stat ( $url, $flags )
|
||||
{
|
||||
$eacl_check=self::check_extended_acl($url,Vfs::READABLE);
|
||||
$this->check_set_context($url);
|
||||
|
||||
// return vCard as /.entry
|
||||
if ( $eacl_check && substr($url,-7) == '/.entry' &&
|
||||
(list($app) = array_slice(explode('/',$url),-3,1)) && $app === 'addressbook')
|
||||
$ret = false;
|
||||
if (($eacl_check = $this->check_extended_acl($url,Vfs::READABLE)))
|
||||
{
|
||||
$ret = array(
|
||||
'ino' => '#'.md5($url),
|
||||
'name' => '.entry',
|
||||
'mode' => self::MODE_FILE|Vfs::READABLE, // required by the stream wrapper
|
||||
'size' => 1024, // fmail does NOT attach files with size 0!
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'mtime' => time(),
|
||||
'ctime' => time(),
|
||||
'nlink' => 1,
|
||||
// eGW addition to return some extra values
|
||||
'mime' => $app == 'addressbook' ? 'text/vcard' : 'text/calendar',
|
||||
);
|
||||
}
|
||||
// if entry directory does not exist --> return fake directory
|
||||
elseif (!($ret = parent::url_stat($url,$flags)) && $eacl_check)
|
||||
{
|
||||
list(,/*$apps*/,/*$app*/,$id,$rel_path) = array_pad(explode('/', Vfs::parse_url($url, PHP_URL_PATH), 5),5,null);
|
||||
if ($id && !isset($rel_path))
|
||||
// return vCard as /.entry
|
||||
if (substr($url, -7) == '/.entry' &&
|
||||
(list($app) = array_slice(explode('/', $url), -3, 1)) && $app === 'addressbook')
|
||||
{
|
||||
$ret = array(
|
||||
'ino' => '#'.md5($url),
|
||||
'name' => $id,
|
||||
'mode' => self::MODE_DIR, // required by the stream wrapper
|
||||
'size' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'ino' => '#' . md5($url),
|
||||
'name' => '.entry',
|
||||
'mode' => self::MODE_FILE | Vfs::READABLE, // required by the stream wrapper
|
||||
'size' => 1024, // email does NOT attach files with size 0!
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'mtime' => time(),
|
||||
'ctime' => time(),
|
||||
'nlink' => 2,
|
||||
'nlink' => 1,
|
||||
// eGW addition to return some extra values
|
||||
'mime' => Vfs::DIR_MIME_TYPE,
|
||||
'mime' => $app == 'addressbook' ? 'text/vcard' : 'text/calendar',
|
||||
);
|
||||
}
|
||||
// if entry directory does not exist --> return fake directory
|
||||
elseif (!($ret = parent::url_stat($url, $flags)))
|
||||
{
|
||||
list(,/*$apps*/,/*$app*/, $id, $rel_path) = array_pad(explode('/', Vfs::parse_url($url, PHP_URL_PATH), 5), 5, null);
|
||||
if ($id && !isset($rel_path))
|
||||
{
|
||||
$ret = array(
|
||||
'ino' => '#' . md5($url),
|
||||
'name' => $id,
|
||||
'mode' => self::MODE_DIR, // required by the stream wrapper
|
||||
'size' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'mtime' => time(),
|
||||
'ctime' => time(),
|
||||
'nlink' => 2,
|
||||
// eGW addition to return some extra values
|
||||
'mime' => Vfs::DIR_MIME_TYPE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self::DEBUG) error_log(__METHOD__."('$url', $flags) calling parent::url_stat(,,".array2string($eacl_check).') returning '.array2string($ret));
|
||||
if (self::DEBUG) error_log(__METHOD__."('$url', $flags) eacl_check=".array2string($eacl_check).' returning '.array2string($ret));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@ -264,7 +267,7 @@ class StreamWrapper extends LinksParent
|
||||
list(,$apps,$app,$id) = explode('/',$path);
|
||||
|
||||
$ret = false;
|
||||
if ($apps == 'apps' && $app && !$id || self::check_extended_acl($path,Vfs::WRITABLE)) // app directory itself is allways ok
|
||||
if ($apps == 'apps' && $app && !$id || $this->check_extended_acl($path,Vfs::WRITABLE)) // app directory itself is allways ok
|
||||
{
|
||||
$current_is_root = Vfs::$is_root; Vfs::$is_root = true;
|
||||
$current_user = Vfs::$user; Vfs::$user = 0;
|
||||
@ -335,7 +338,7 @@ class StreamWrapper extends LinksParent
|
||||
{
|
||||
$charset = 'utf-8';
|
||||
}
|
||||
if (!($vcard =& $ab_vcard->getVCard($id, $charset)))
|
||||
if (!($vcard = $ab_vcard->getVCard($id, $charset)))
|
||||
{
|
||||
error_log(__METHOD__."('$url', '$mode', $options) addressbook_vcal::getVCard($id) returned false!");
|
||||
return false;
|
||||
@ -348,7 +351,7 @@ class StreamWrapper extends LinksParent
|
||||
}
|
||||
// create not existing entry directories on the fly
|
||||
if ($mode[0] != 'r' && ($dir = Vfs::dirname($url)) &&
|
||||
!parent::url_stat($dir, 0) && self::check_extended_acl($dir, Vfs::WRITABLE))
|
||||
!parent::url_stat($dir, 0) && $this->check_extended_acl($dir, Vfs::WRITABLE))
|
||||
{
|
||||
$this->mkdir($dir,0,STREAM_MKDIR_RECURSIVE);
|
||||
}
|
||||
@ -431,14 +434,14 @@ class StreamWrapper extends LinksParent
|
||||
* @param string $link
|
||||
* @return boolean true on success false on error
|
||||
*/
|
||||
static function symlink($target,$link)
|
||||
function symlink($target,$link)
|
||||
{
|
||||
$parent = new \EGroupware\Api\Vfs\Links\LinksParent();
|
||||
if (!$parent->url_stat($dir = Vfs::dirname($link),0) && self::check_extended_acl($dir,Vfs::WRITABLE))
|
||||
$parent = new \EGroupware\Api\Vfs\Links\LinksParent($target);
|
||||
if (!$parent->url_stat($dir = Vfs::dirname($link),0) && $this->check_extended_acl($dir,Vfs::WRITABLE))
|
||||
{
|
||||
$parent->mkdir($dir,0,STREAM_MKDIR_RECURSIVE);
|
||||
}
|
||||
return parent::symlink($target,$link);
|
||||
return $parent->symlink($target,$link);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -446,7 +449,7 @@ class StreamWrapper extends LinksParent
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
stream_register_wrapper(self::SCHEME, __CLASS__);
|
||||
stream_wrapper_register(self::SCHEME, __CLASS__);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ namespace EGroupware\Api\Vfs;
|
||||
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
use EGroupware\Collabora\Wopi;
|
||||
use filemanager_ui;
|
||||
|
||||
/**
|
||||
@ -71,19 +72,28 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Subdirectory of user's home directory to mount shares into
|
||||
*/
|
||||
const SHARES_DIRECTORY = 'shares';
|
||||
|
||||
/**
|
||||
* Create sharing session
|
||||
*
|
||||
* Certain cases:
|
||||
* a) there is not session $keep_session === null
|
||||
* --> create new anon session with just filemanager rights and share as fstab
|
||||
* b) there is a session $keep_session === true
|
||||
* b1) current user is share owner (eg. checking the link)
|
||||
* --> mount share under token additionally
|
||||
* b2) current user not share owner
|
||||
* b2a) need/use filemanager UI (eg. directory)
|
||||
* --> destroy current session and continue with a)
|
||||
* b2b) single file or WebDAV
|
||||
* There are two cases:
|
||||
*
|
||||
* 1) there is no session $keep_session === null
|
||||
* --> create new anon session with just filemanager rights and resolved share incl. sharee as only fstab entry
|
||||
*
|
||||
* 2) there is a (non-anonymous) session $keep_session === true
|
||||
* --> mount share with sharing stream-wrapper into users "shares" subdirectory of home directory
|
||||
* and ask user if he wants the share permanently mounted there
|
||||
*
|
||||
* Even with sharing stream-wrapper a) and b) need to be different, as sharing SW needs an intact fstab!
|
||||
*
|
||||
* Not yet sure if this still needs extra handling:
|
||||
*
|
||||
* 2a) single file or WebDAV
|
||||
* --> modify EGroupware enviroment for that request only, no change in session
|
||||
*
|
||||
* @param boolean $keep_session =null null: create a new session, true: try mounting it into existing (already verified) session
|
||||
@ -98,54 +108,64 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
$GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount();
|
||||
Vfs::clearstatcache();
|
||||
}
|
||||
$share['resolve_url'] = Vfs::resolve_url($share['share_path'], true, true, true, true); // true = fix evtl. contained url parameter
|
||||
|
||||
// for a regular user session, mount the share into "shares" subdirectory of his home-directory
|
||||
if ($keep_session && $GLOBALS['egw_info']['user']['account_lid'] && $GLOBALS['egw_info']['user']['account_lid'] !== 'anonymous')
|
||||
{
|
||||
$shares_dir = '/home/'.Vfs::encodePathComponent($GLOBALS['egw_info']['user']['account_lid']).'/'.self::SHARES_DIRECTORY;
|
||||
if (!Vfs::file_exists($shares_dir)) Vfs::mkdir($shares_dir, 0750, true);
|
||||
$share['share_root'] = Vfs::concat($shares_dir, Vfs::basename($share['share_path']));
|
||||
|
||||
// ToDo: handle there's already something there with that name (incl. maybe the same share!)
|
||||
|
||||
Vfs::$is_root = true;
|
||||
if (!Vfs::mount(Vfs\Sharing\StreamWrapper::share2url($share), $share['share_root'], false, false, $clear_fstab))
|
||||
{
|
||||
sleep(1);
|
||||
return static::share_fail(
|
||||
'404 Not Found',
|
||||
"Requested resource '/".htmlspecialchars($share['share_token'])."' does NOT exist!\n"
|
||||
);
|
||||
}
|
||||
Vfs::$is_root = false;
|
||||
|
||||
Api\Framework::message(lang('Share has been mounted into you shares directory').': '.$share['share_root'], 'success');
|
||||
// ToDo: ask user if he want's the share permanently mounted
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* From here on pure sharing url without regular EGroupware user (session)
|
||||
*/
|
||||
$share['resolve_url'] = Vfs::build_url([
|
||||
'user' => Api\Accounts::id2name($share['share_owner']),
|
||||
]+Vfs::parse_url(Vfs::resolve_url($share['share_path'], true, true, true, true))); // true = fix evtl. contained url parameter
|
||||
// if share not writable append ro=1 to mount url to make it readonly
|
||||
if (!($share['share_writable'] & 1))
|
||||
{
|
||||
$share['resolve_url'] .= (strpos($share['resolve_url'], '?') ? '&' : '?').'ro=1';
|
||||
}
|
||||
//_debug_array($share);
|
||||
$share['share_root'] = '/';
|
||||
|
||||
$share['share_root'] = '/'.Vfs::basename($share['share_path']);
|
||||
if ($keep_session) // add share to existing session
|
||||
// only allow filemanager app & collabora
|
||||
// In some cases, $GLOBALS['egw_info']['apps'] is not yet set at all. Set it to app => true, it will be used
|
||||
// in Session->read_repositories() to make sure we get access to these apps when the session loads the apps.
|
||||
$apps = $GLOBALS['egw']->acl->get_user_applications($share['share_owner']);
|
||||
$GLOBALS['egw_info']['user']['apps'] = array(
|
||||
'filemanager' => $GLOBALS['egw_info']['apps']['filemanager'] || true,
|
||||
'collabora' => $GLOBALS['egw_info']['apps']['collabora'] || $apps['collabora']
|
||||
);
|
||||
|
||||
// Need to re-init stream wrapper, as some of them look at preferences or permissions
|
||||
$class = Vfs\StreamWrapper::scheme2class(Vfs::parse_url($share['resolve_url'],PHP_URL_SCHEME));
|
||||
if($class && method_exists($class, 'init_static'))
|
||||
{
|
||||
// if current user is not the share owner, we cant just mount share
|
||||
if (Vfs::$user != $share['share_owner'])
|
||||
{
|
||||
$keep_session = false;
|
||||
}
|
||||
}
|
||||
if (!$keep_session) // do NOT change to else, as we might have set $keep_session=false!
|
||||
{
|
||||
// only allow filemanager app & collabora
|
||||
// In some cases, $GLOBALS['egw_info']['apps'] is not yet set at all. Set it to app => true, it will be used
|
||||
// in Session->read_repositories() to make sure we get access to these apps when the session loads the apps.
|
||||
$apps = $GLOBALS['egw']->acl->get_user_applications($share['share_owner']);
|
||||
$GLOBALS['egw_info']['user']['apps'] = array(
|
||||
'filemanager' => $GLOBALS['egw_info']['apps']['filemanager'] || true,
|
||||
'collabora' => $GLOBALS['egw_info']['apps']['collabora'] || $apps['collabora']
|
||||
);
|
||||
|
||||
Vfs::$user = $share['share_owner'];
|
||||
|
||||
// Need to re-init stream wrapper, as some of them look at
|
||||
// preferences or permissions
|
||||
$scheme = Vfs\StreamWrapper::scheme2class(Vfs::parse_url($share['resolve_url'],PHP_URL_SCHEME));
|
||||
if($scheme && method_exists($scheme, 'init_static'))
|
||||
{
|
||||
$scheme::init_static();
|
||||
}
|
||||
$class::init_static();
|
||||
}
|
||||
|
||||
// mounting share
|
||||
Vfs::$is_root = true;
|
||||
$clear_fstab = !$keep_session && (!$GLOBALS['egw_info']['user']['account_lid'] || $GLOBALS['egw_info']['user']['account_lid'] == 'anonymous');
|
||||
// if current user is not the share owner, we cant just mount share into existing VFS
|
||||
if ($GLOBALS['egw_info']['user']['account_id'] != $share['share_owner'])
|
||||
{
|
||||
$clear_fstab = true;
|
||||
}
|
||||
if (!Vfs::mount($share['resolve_url'], $share['share_root'], false, false, $clear_fstab))
|
||||
if (!Vfs::mount($share['resolve_url'], $share['share_root'], false, false, true))
|
||||
{
|
||||
sleep(1);
|
||||
return static::share_fail(
|
||||
@ -154,6 +174,7 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
);
|
||||
}
|
||||
|
||||
/* ToDo: is this still needed and for what reason, as Vfs::mount() already supports session / non-persistent mounts
|
||||
$session_fstab =& Api\Cache::getSession('api', 'fstab');
|
||||
if(!$session_fstab)
|
||||
{
|
||||
@ -163,8 +184,7 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
{
|
||||
Vfs::mount($info['mount'], $mount, false, false);
|
||||
}
|
||||
static::session_mount($share['share_root'], $share['resolve_url']);
|
||||
|
||||
static::session_mount($share['share_root'], $share['resolve_url']);*/
|
||||
|
||||
Vfs::$is_root = false;
|
||||
Vfs::clearstatcache();
|
||||
@ -190,18 +210,22 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
);
|
||||
}
|
||||
|
||||
protected function after_login()
|
||||
protected static function after_login($share)
|
||||
{
|
||||
// only allow filemanager app (gets overwritten by session::create)
|
||||
$GLOBALS['egw_info']['user']['apps'] = array(
|
||||
'filemanager' => $GLOBALS['egw_info']['apps']['filemanager']
|
||||
);
|
||||
// check if sharee has Collabora run rights --> give is to share too
|
||||
$apps = $GLOBALS['egw']->acl->get_user_applications($this->share['share_owner']);
|
||||
$apps = $GLOBALS['egw']->acl->get_user_applications($share['share_owner']);
|
||||
if (!empty($apps['collabora']))
|
||||
{
|
||||
$GLOBALS['egw_info']['user']['apps']['collabora'] = $GLOBALS['egw_info']['apps']['collabora'];
|
||||
}
|
||||
// session::create also overwrites link-registry
|
||||
Vfs::clearstatcache();
|
||||
// clear link-cache and load link registry without permission check to access /apps
|
||||
Api\Link::init_static(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -318,7 +342,7 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
{
|
||||
if(parse_url($path, PHP_URL_SCHEME) !== 'vfs')
|
||||
{
|
||||
$path = 'vfs://default'.($path[0] == '/' ? '' : '/').$path;
|
||||
$path = Vfs::PREFIX.Vfs::parse_url($path, PHP_URL_PATH);
|
||||
}
|
||||
|
||||
// We don't allow sharing paths that contain links, resolve to target instead
|
||||
@ -335,7 +359,7 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
$path = str_replace($check, $delinked, $path);
|
||||
if(parse_url($path, PHP_URL_SCHEME) !== 'vfs')
|
||||
{
|
||||
$path = 'vfs://default'.($path[0] == '/' ? '' : '/').$path;
|
||||
$path = Vfs::PREFIX.Vfs::parse_url($path, PHP_URL_PATH);
|
||||
}
|
||||
$check = $path;
|
||||
}
|
||||
@ -350,8 +374,10 @@ class Sharing extends \EGroupware\Api\Sharing
|
||||
// Make sure we get the correct path if sharing from a share
|
||||
if(isset($GLOBALS['egw']->sharing) && $exists)
|
||||
{
|
||||
/* Why not use $stat['url']
|
||||
$resolved_stat = Vfs::parse_url($stat['url']);
|
||||
$path = 'vfs://default'. $resolved_stat['path'];
|
||||
$path = 'vfs://default'. $resolved_stat['path'];*/
|
||||
$path = $stat['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
api/src/Vfs/Sharing/StreamWrapper.php
Normal file
124
api/src/Vfs/Sharing/StreamWrapper.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware API: VFS - sharing stream wrapper
|
||||
*
|
||||
* @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 <rb@egroupware.org>
|
||||
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs\Sharing;
|
||||
|
||||
use EGroupware\Api\Vfs;
|
||||
use EGroupware\Api;
|
||||
|
||||
/**
|
||||
* VFS - sharing stream wrapper
|
||||
*
|
||||
* Sharing stream wrapper allows to mount a share represented by it's hash and optional password to be mounted
|
||||
* into EGroupware's VFS: sharing://<hash>[:<password>]@default/ --> vfs://<sharee>@default/<shared-path>
|
||||
*/
|
||||
class StreamWrapper extends Vfs\StreamWrapper
|
||||
{
|
||||
const SCHEME = 'sharing';
|
||||
const PREFIX = 'sharing://default';
|
||||
|
||||
/**
|
||||
* Method to replace sharing url with sharee and shared path, and to shortcut Vfs\StreamWrapper::resolve_url()
|
||||
*
|
||||
* @param $url
|
||||
* @return bool|string
|
||||
*/
|
||||
static function replace($url)
|
||||
{
|
||||
$parts = Vfs::parse_url($url);
|
||||
|
||||
$hash = $parts['user'] ?: explode('/', $parts['path'])[1];
|
||||
$rel_path = empty($parts['user']) ? preg_replace('|^/[^/]+|', '', $parts['path']) : $parts['path'];
|
||||
|
||||
try
|
||||
{
|
||||
if (empty($hash)) throw new Api\Exception\NotFound('Hash must not be empty', 404);
|
||||
|
||||
Api\Sharing::check_token(false, $share, $hash, $parts['pass'] ?? '');
|
||||
|
||||
return self::share2url($share);
|
||||
}
|
||||
catch (Api\Exception $e) {
|
||||
_egw_log_exception($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate sharing URL from share
|
||||
*
|
||||
* @param array $share as returned eg. by Api\Sharing::check_token()
|
||||
* @return string
|
||||
* @throws Api\Exception\NotFound if sharee was not found
|
||||
*/
|
||||
static function share2url(array $share)
|
||||
{
|
||||
if (empty($share['share_owner']) || !($account_lid = Api\Accounts::id2name($share['share_owner'])))
|
||||
{
|
||||
throw new Api\Exception\NotFound('Share owner not found', 404);
|
||||
}
|
||||
return Vfs::concat('vfs://'.$account_lid.'@default'.Vfs::parse_url($share['share_path'], PHP_URL_PATH), $rel_path).
|
||||
($share['share_writable'] & 1 ? '' : '?ro=1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given path according to our fstab
|
||||
*
|
||||
* @param string $url
|
||||
* @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($url, $do_symlink = true, $use_symlinkcache = true, $replace_user_pass_host = true, $fix_url_query = false)
|
||||
{
|
||||
return self::replace($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called in response to stat() calls on the URL paths associated with the wrapper.
|
||||
*
|
||||
* Overwritten to set sharee as user in context for ACL checks.
|
||||
*
|
||||
* @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!
|
||||
* @param boolean $try_create_home =false should a user home-directory be created automatic, if it does not exist
|
||||
* @param boolean $check_symlink_components =true check if path contains symlinks in path components other then the last one
|
||||
* @return array
|
||||
*/
|
||||
function url_stat ( $path, $flags, $try_create_home=false, $check_symlink_components=true, $check_symlink_depth=self::MAX_SYMLINK_DEPTH, $try_reconnect=true )
|
||||
{
|
||||
if (($stat = parent::url_stat($path, $flags, $try_create_home, $check_symlink_components, $check_symlink_depth, $try_reconnect)))
|
||||
{
|
||||
$this->check_set_context($stat['url']);
|
||||
}
|
||||
return $stat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register __CLASS__ for self::SCHEMA
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
stream_wrapper_register(self::SCHEME, __CLASS__);
|
||||
}
|
||||
}
|
||||
|
||||
StreamWrapper::register();
|
@ -7,8 +7,7 @@
|
||||
* @package api
|
||||
* @subpackage vfs
|
||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @copyright (c) 2008-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @version $Id$
|
||||
* @copyright (c) 2008-20 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs\Sqlfs;
|
||||
@ -36,6 +35,8 @@ use EGroupware\Api;
|
||||
*/
|
||||
class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
{
|
||||
use Vfs\UserContextTrait;
|
||||
|
||||
/**
|
||||
* Mime type of directories, the old vfs uses 'Directory', while eg. WebDAV uses 'httpd/unix-directory'
|
||||
*/
|
||||
@ -102,13 +103,6 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
*/
|
||||
protected $operation = self::DEFAULT_OPERATION;
|
||||
|
||||
/**
|
||||
* optional context param when opening the stream, null if no context passed
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
var $context;
|
||||
|
||||
/**
|
||||
* Path off the file opened by stream_open
|
||||
*
|
||||
@ -210,7 +204,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
if (!$dir || $mode[0] == 'r' || // does $mode require the file to exist (r,r+)
|
||||
$mode[0] == 'x' && $stat || // or file should not exist, but does
|
||||
!($dir_stat=$this->url_stat($dir,STREAM_URL_STAT_QUIET)) || // or parent dir does not exist create it
|
||||
!Vfs::check_access($dir,Vfs::WRITABLE,$dir_stat)) // or we are not allowed to create it
|
||||
!$this->check_access($dir,Vfs::WRITABLE, $dir_stat)) // or we are not allowed to create it
|
||||
{
|
||||
self::_remove_password($url);
|
||||
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file does not exist or can not be created!");
|
||||
@ -233,12 +227,12 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
// we use the mode of the dir, so files in group dirs stay accessible by all members
|
||||
'fs_mode' => $dir_stat['mode'] & 0666,
|
||||
// for the uid we use the uid of the dir if not 0=root or the current user otherwise
|
||||
'fs_uid' => $dir_stat['uid'] ? $dir_stat['uid'] : Vfs::$user,
|
||||
'fs_uid' => $dir_stat['uid'] ? $dir_stat['uid'] : $this->user,
|
||||
// we allways use the group of the dir
|
||||
'fs_gid' => $dir_stat['gid'],
|
||||
'fs_created' => self::_pdo_timestamp(time()),
|
||||
'fs_modified' => self::_pdo_timestamp(time()),
|
||||
'fs_creator' => Vfs::$user,
|
||||
'fs_creator' => Vfs::$user, // real user, not effective one / $this->user
|
||||
'fs_mime' => 'application/octet-stream', // required NOT NULL!
|
||||
'fs_size' => 0,
|
||||
'fs_active' => self::_pdo_boolean(true),
|
||||
@ -276,8 +270,8 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($mode == 'r' && !Vfs::check_access($url,Vfs::READABLE ,$stat) ||// we are not allowed to read
|
||||
$mode != 'r' && !Vfs::check_access($url,Vfs::WRITABLE,$stat)) // or edit it
|
||||
if ($mode == 'r' && !$this->check_access($url,Vfs::READABLE , $stat) ||// we are not allowed to read
|
||||
$mode != 'r' && !$this->check_access($url,Vfs::WRITABLE, $stat)) // or edit it
|
||||
{
|
||||
self::_remove_password($url);
|
||||
$op = $mode == 'r' ? 'read' : 'edited';
|
||||
@ -355,7 +349,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
// todo: analyse the file for the mime-type
|
||||
'fs_mime' => Api\MimeMagic::filename2mime($this->opened_path),
|
||||
'fs_id' => $this->opened_fs_id,
|
||||
'fs_modifier' => Vfs::$user,
|
||||
'fs_modifier' => $this->user,
|
||||
'fs_modified' => self::_pdo_timestamp(time()),
|
||||
);
|
||||
|
||||
@ -533,7 +527,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
*/
|
||||
function stream_stat ( )
|
||||
{
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($this->opened_path)");
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."() opened_path=$this->opened_path, context=".json_encode(stream_context_get_options($this->context)));
|
||||
|
||||
return $this->url_stat($this->opened_path,0);
|
||||
}
|
||||
@ -558,7 +552,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
$this->url_stat($dir, STREAM_URL_STAT_LINK);
|
||||
|
||||
if (!$parent_stat || !($stat = $this->url_stat($path,STREAM_URL_STAT_LINK)) ||
|
||||
!$dir || !Vfs::check_access($dir, Vfs::WRITABLE, $parent_stat))
|
||||
!$dir || !$this->check_access($dir, Vfs::WRITABLE, $parent_stat))
|
||||
{
|
||||
self::_remove_password($url);
|
||||
if (self::LOG_LEVEL) error_log(__METHOD__."($url) permission denied!");
|
||||
@ -610,14 +604,14 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
$to_dir = Vfs::dirname($path_to);
|
||||
|
||||
if (!($from_stat = $this->url_stat($path_from, 0)) || !$from_dir ||
|
||||
!Vfs::check_access($from_dir, Vfs::WRITABLE, $from_dir_stat = $this->url_stat($from_dir, 0)))
|
||||
!$this->check_access($from_dir, Vfs::WRITABLE, $from_dir_stat = $this->url_stat($from_dir, 0)))
|
||||
{
|
||||
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 || !Vfs::check_access($to_dir, Vfs::WRITABLE, $to_dir_stat = $this->url_stat($to_dir, 0)))
|
||||
if (!$to_dir || !$this->check_access($to_dir, Vfs::WRITABLE, $to_dir_stat = $this->url_stat($to_dir, 0)))
|
||||
{
|
||||
self::_remove_password($url_from);
|
||||
self::_remove_password($url_to);
|
||||
@ -699,7 +693,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__." called from:".function_backtrace());
|
||||
$path = Vfs::parse_url($url,PHP_URL_PATH);
|
||||
|
||||
if ($this->url_stat($path,STREAM_URL_STAT_QUIET))
|
||||
if ($this->url_stat($url,STREAM_URL_STAT_QUIET))
|
||||
{
|
||||
self::_remove_password($url);
|
||||
if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) already exist!");
|
||||
@ -733,7 +727,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
}
|
||||
$parent = $this->url_stat($parent_path,0);
|
||||
}
|
||||
if (!$parent || !Vfs::check_access($parent_path,Vfs::WRITABLE,$parent))
|
||||
if (!$parent || !$this->check_access($parent_path,Vfs::WRITABLE, $parent))
|
||||
{
|
||||
self::_remove_password($url);
|
||||
if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) permission denied!");
|
||||
@ -796,7 +790,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
|
||||
if (!($parent = Vfs::dirname($path)) ||
|
||||
!($stat = $this->url_stat($path, 0)) || $stat['mime'] != self::DIR_MIME_TYPE ||
|
||||
!Vfs::check_access($parent, Vfs::WRITABLE, $this->url_stat($parent,0)))
|
||||
!$this->check_access($parent, Vfs::WRITABLE))
|
||||
{
|
||||
self::_remove_password($url);
|
||||
$err_msg = __METHOD__."($url,$options) ".(!$stat ? 'not found!' :
|
||||
@ -911,7 +905,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
|
||||
return $stmt->execute(array(
|
||||
'fs_modified' => self::_pdo_timestamp($time ? $time : time()),
|
||||
'fs_modifier' => Vfs::$user,
|
||||
'fs_modifier' => $this->user,
|
||||
'fs_id' => $stat['ino'],
|
||||
));
|
||||
}
|
||||
@ -1082,7 +1076,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
|
||||
if (!($stat = $this->url_stat($url,0)) || // dir not found
|
||||
!($stat['mode'] & self::MODE_DIR) && $stat['mime'] != self::DIR_MIME_TYPE || // no dir
|
||||
!Vfs::check_access($url,Vfs::EXECUTABLE|Vfs::READABLE,$stat)) // no access
|
||||
!$this->check_access($url,Vfs::EXECUTABLE|Vfs::READABLE, $stat)) // no access
|
||||
{
|
||||
self::_remove_password($url);
|
||||
$msg = !($stat['mode'] & self::MODE_DIR) && $stat['mime'] != self::DIR_MIME_TYPE ?
|
||||
@ -1152,6 +1146,8 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
|
||||
$path = Vfs::parse_url($url,PHP_URL_PATH);
|
||||
|
||||
$this->check_set_context($url);
|
||||
|
||||
// webdav adds a trailing slash to dirs, which causes url_stat to NOT find the file otherwise
|
||||
if ($path != '/' && substr($path,-1) == '/')
|
||||
{
|
||||
@ -1177,7 +1173,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
$parts = explode('/',$path);
|
||||
|
||||
// if we have extended acl access to the url, we dont need and can NOT include the sql for the readable check
|
||||
$eacl_access = static::check_extended_acl($path,Vfs::READABLE);
|
||||
$eacl_access = $this->check_extended_acl($path,Vfs::READABLE);
|
||||
|
||||
try {
|
||||
foreach($parts as $n => $name)
|
||||
@ -1208,13 +1204,13 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
// 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 (!Vfs::$is_root && !$eacl_access)
|
||||
{
|
||||
if (!Vfs::$user)
|
||||
if (!$this->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();
|
||||
$query .= ' AND '.$this->_sql_readable();
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1259,23 +1255,23 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function _sql_readable()
|
||||
protected function _sql_readable()
|
||||
{
|
||||
static $sql_read_acl=null;
|
||||
static $sql_read_acl=[];
|
||||
|
||||
if (is_null($sql_read_acl))
|
||||
if (!isset($sql_read_acl[$user = $this->user]))
|
||||
{
|
||||
foreach($GLOBALS['egw']->accounts->memberships(Vfs::$user,true) as $gid)
|
||||
foreach($GLOBALS['egw']->accounts->memberships($user, true) as $gid)
|
||||
{
|
||||
$memberships[] = abs($gid); // sqlfs stores the gid's positiv
|
||||
}
|
||||
// using octal numbers with mysql leads to funny results (select 384 & 0400 --> 384 not 256=0400)
|
||||
// 256 = 0400, 32 = 040
|
||||
$sql_read_acl = '((fs_mode & 4)=4 OR (fs_mode & 256)=256 AND fs_uid='.(int)Vfs::$user.
|
||||
$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__."() Vfs::\$user=".array2string(Vfs::$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;
|
||||
return $sql_read_acl[$user];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1341,10 +1337,9 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
* @param string $path
|
||||
* @return string|boolean content of the symlink or false if $url is no symlink (or not found)
|
||||
*/
|
||||
static function readlink($path)
|
||||
function readlink($path)
|
||||
{
|
||||
$vfs = new self();
|
||||
$link = !($lstat = $vfs->url_stat($path,STREAM_URL_STAT_LINK)) || is_null($lstat['readlink']) ? false : $lstat['readlink'];
|
||||
$link = !($lstat = $this->url_stat($path,STREAM_URL_STAT_LINK)) || is_null($lstat['readlink']) ? false : $lstat['readlink'];
|
||||
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = $link");
|
||||
|
||||
@ -1358,18 +1353,17 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
* @param string $link
|
||||
* @return boolean true on success false on error
|
||||
*/
|
||||
static function symlink($target,$link)
|
||||
function symlink($target, $link)
|
||||
{
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$target','$link')");
|
||||
|
||||
$inst = new static();
|
||||
if ($inst->url_stat($link,0))
|
||||
if ($this->url_stat($link,0))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$target','$link') $link exists, returning false!");
|
||||
return false; // $link already exists
|
||||
}
|
||||
if (!($dir = Vfs::dirname($link)) ||
|
||||
!Vfs::check_access($dir,Vfs::WRITABLE,$dir_stat=$inst->url_stat($dir,0)))
|
||||
!$this->check_access($dir,Vfs::WRITABLE, $dir_stat=$this->url_stat($dir,0)))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$target','$link') returning false! (!is_writable('$dir'), dir_stat=".array2string($dir_stat).")");
|
||||
return false; // parent dir does not exist or is not writable
|
||||
@ -1384,7 +1378,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
'fs_name' => self::limit_filename(Vfs::basename($link)),
|
||||
'fs_dir' => $dir_stat['ino'],
|
||||
'fs_mode' => ($dir_stat['mode'] & 0666),
|
||||
'fs_uid' => $dir_stat['uid'] ? $dir_stat['uid'] : Vfs::$user,
|
||||
'fs_uid' => $dir_stat['uid'] ? $dir_stat['uid'] : $this->user,
|
||||
'fs_gid' => $dir_stat['gid'],
|
||||
'fs_created' => self::_pdo_timestamp(time()),
|
||||
'fs_modified' => self::_pdo_timestamp(time()),
|
||||
@ -1407,13 +1401,13 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
* @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)
|
||||
function check_extended_acl($url,$check)
|
||||
{
|
||||
$url_path = Vfs::parse_url($url,PHP_URL_PATH);
|
||||
|
||||
if (is_null(self::$extended_acl))
|
||||
{
|
||||
self::_read_extended_acl();
|
||||
$this->_read_extended_acl();
|
||||
}
|
||||
$access = false;
|
||||
foreach(self::$extended_acl as $path => $rights)
|
||||
@ -1432,14 +1426,14 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
||||
* Read the extended acl via acl::get_grants('sqlfs')
|
||||
*
|
||||
*/
|
||||
static protected function _read_extended_acl()
|
||||
protected function _read_extended_acl()
|
||||
{
|
||||
if ((self::$extended_acl = Api\Cache::getSession(self::EACL_APPNAME, 'extended_acl')))
|
||||
{
|
||||
return; // ext. ACL read from session.
|
||||
}
|
||||
self::$extended_acl = array();
|
||||
if (($rights = $GLOBALS['egw']->acl->get_all_location_rights(Vfs::$user,self::EACL_APPNAME)))
|
||||
if (($rights = $GLOBALS['egw']->acl->get_all_location_rights($this->user, self::EACL_APPNAME)))
|
||||
{
|
||||
$pathes = self::id2path(array_keys($rights));
|
||||
}
|
||||
@ -1843,14 +1837,12 @@ GROUP BY A.fs_id';
|
||||
* @param array $props array of array with values for keys 'name', 'ns', 'val' (null to delete the prop)
|
||||
* @return boolean true if props are updated, false otherwise (eg. ressource not found)
|
||||
*/
|
||||
static function proppatch($path,array $props)
|
||||
function proppatch($path,array $props)
|
||||
{
|
||||
static $inst = null;
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."(".array2string($path).','.array2string($props));
|
||||
if (!is_numeric($path))
|
||||
{
|
||||
if (!isset($inst)) $inst = new self();
|
||||
if (!($stat = $inst->url_stat($path,0)))
|
||||
if (!($stat = $this->url_stat($path,0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -1860,7 +1852,7 @@ GROUP BY A.fs_id';
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Vfs::check_access($path,Api\Acl::EDIT,$stat))
|
||||
if (!$this->check_access($path,Api\Acl::EDIT, $stat))
|
||||
{
|
||||
return false; // permission denied
|
||||
}
|
||||
@ -1914,17 +1906,14 @@ GROUP BY A.fs_id';
|
||||
* @return array|boolean false on error ($path_ids does not exist), array with props (values for keys 'name', 'ns', 'value'), or
|
||||
* fs_id/path => array of props for $depth==1 or is_array($path_ids)
|
||||
*/
|
||||
static function propfind($path_ids,$ns=Vfs::DEFAULT_PROP_NAMESPACE)
|
||||
function propfind($path_ids,$ns=Vfs::DEFAULT_PROP_NAMESPACE)
|
||||
{
|
||||
static $inst = null;
|
||||
|
||||
$ids = is_array($path_ids) ? $path_ids : array($path_ids);
|
||||
foreach($ids as &$id)
|
||||
{
|
||||
if (!is_numeric($id))
|
||||
{
|
||||
if (!isset($inst)) $inst = new self();
|
||||
if (!($stat = $inst->url_stat($id,0)))
|
||||
if (!($stat = $this->url_stat($id,0)))
|
||||
{
|
||||
if (self::LOG_LEVEL) error_log(__METHOD__."(".array2string($path_ids).",$ns) path '$id' not found!");
|
||||
return false;
|
||||
@ -1978,7 +1967,7 @@ GROUP BY A.fs_id';
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
stream_register_wrapper(self::SCHEME, __CLASS__);
|
||||
stream_wrapper_register(self::SCHEME, __CLASS__);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware API: VFS - stream wrapper interface
|
||||
* EGroupware API: VFS - stream wrapper
|
||||
*
|
||||
* @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-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @version $Id$
|
||||
* @copyright (c) 2008-20 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs;
|
||||
@ -17,48 +16,26 @@ use EGroupware\Api\Vfs;
|
||||
use EGroupware\Api;
|
||||
|
||||
/**
|
||||
* eGroupWare API: VFS - stream wrapper interface
|
||||
* VFS - stream wrapper
|
||||
*
|
||||
* The new vfs stream wrapper uses a kind of fstab to mount different filesystems / stream wrapper types
|
||||
* together for eGW's virtual file system.
|
||||
*
|
||||
* @link http://www.php.net/manual/en/function.stream-wrapper-register.php
|
||||
*/
|
||||
class StreamWrapper implements StreamWrapperIface
|
||||
class StreamWrapper extends Base implements StreamWrapperIface
|
||||
{
|
||||
/**
|
||||
* 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'
|
||||
*/
|
||||
const DIR_MIME_TYPE = 'httpd/unix-directory';
|
||||
use UserContextTrait {
|
||||
check_access as parent_check_access;
|
||||
}
|
||||
|
||||
const PREFIX = 'vfs://default';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
var $context;
|
||||
/**
|
||||
* mode-bits, which have to be set for links
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Maximum depth of symlinks, if exceeded url_stat will return false
|
||||
*
|
||||
@ -66,18 +43,6 @@ class StreamWrapper 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
|
||||
*
|
||||
@ -146,7 +111,25 @@ class StreamWrapper implements StreamWrapperIface
|
||||
*/
|
||||
private $extra_dir_ptr;
|
||||
|
||||
private static $wrappers;
|
||||
/**
|
||||
* 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 string $path path
|
||||
* @param int $check mode to check: one or more or'ed together of: 4 = Vfs::READABLE,
|
||||
* 2 = Vfs::WRITABLE, 1 = Vfs::EXECUTABLE
|
||||
* @param array|boolean $stat =null stat array or false, to not query it again
|
||||
* @return boolean
|
||||
*/
|
||||
function check_access($path, $check, $stat=null)
|
||||
{
|
||||
$ret = self::_call_on_backend('check_access', [$path, $check, $stat], "null", 0, true);
|
||||
if (!isset($ret))
|
||||
{
|
||||
$ret = $this->parent_check_access($path, $check, $stat);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given path according to our fstab AND symlinks
|
||||
@ -174,119 +157,17 @@ class StreamWrapper implements StreamWrapperIface
|
||||
// if the url resolves to a symlink to the vfs, resolve this vfs:// url direct
|
||||
if ($url && Vfs::parse_url($url,PHP_URL_SCHEME) == self::SCHEME)
|
||||
{
|
||||
$user = Vfs::parse_url($url,PHP_URL_USER);
|
||||
$url = self::resolve_url(Vfs::parse_url($url,PHP_URL_PATH));
|
||||
if (!empty($user) && empty(parse_url($url, PHP_URL_USER)))
|
||||
{
|
||||
$url = str_replace('://', '://'.$user.'@', $url);
|
||||
}
|
||||
}
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,file_exists=$file_exists,resolve_last_symlink=$resolve_last_symlink) = '$url'$log");
|
||||
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.
|
||||
*
|
||||
@ -313,6 +194,8 @@ class StreamWrapper implements StreamWrapperIface
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$this->check_set_context($url);
|
||||
|
||||
if (!($this->opened_stream = $this->context ?
|
||||
fopen($url, $mode, false, $this->context) : fopen($url, $mode, false)))
|
||||
{
|
||||
@ -325,7 +208,7 @@ class StreamWrapper implements StreamWrapperIface
|
||||
|
||||
// are we requested to treat the opened file as new file (only for files opened NOT for reading)
|
||||
if ($mode[0] != 'r' && !$this->opened_stream_is_new && $this->context &&
|
||||
($opts = stream_context_get_params($this->context)) &&
|
||||
($opts = stream_context_get_options($this->context)) &&
|
||||
$opts['options'][self::SCHEME]['treat_as_new'])
|
||||
{
|
||||
$this->opened_stream_is_new = true;
|
||||
@ -538,10 +421,12 @@ class StreamWrapper implements StreamWrapperIface
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// set user-context
|
||||
$this->check_set_context($url);
|
||||
$stat = $this->url_stat($path, STREAM_URL_STAT_LINK);
|
||||
|
||||
self::symlinkCache_remove($path);
|
||||
$ok = unlink($url);
|
||||
$ok = unlink($url, $this->context);
|
||||
|
||||
// call "vfs_unlink" hook only after successful unlink, with data from (not longer possible) stat call
|
||||
if ($ok && !class_exists('setup_process', false))
|
||||
@ -585,13 +470,16 @@ class StreamWrapper implements StreamWrapperIface
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// set user-context
|
||||
$this->check_set_context($url_from);
|
||||
|
||||
// if file is moved from one filesystem / wrapper to an other --> copy it (rename fails cross wrappers)
|
||||
if (Vfs::parse_url($url_from,PHP_URL_SCHEME) == Vfs::parse_url($url_to,PHP_URL_SCHEME))
|
||||
{
|
||||
self::symlinkCache_remove($path_from);
|
||||
$ret = rename($url_from,$url_to);
|
||||
$ret = rename($url_from, $url_to, $this->context);
|
||||
}
|
||||
elseif (($from = fopen($url_from,'r')) && ($to = fopen($url_to,'w')))
|
||||
elseif (($from = fopen($url_from,'r', false, $this->context)) && ($to = fopen($url_to,'w')))
|
||||
{
|
||||
$ret = stream_copy_to_stream($from,$to) !== false;
|
||||
fclose($from);
|
||||
@ -642,6 +530,11 @@ class StreamWrapper implements StreamWrapperIface
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// set user context
|
||||
if (Vfs::parse_url($url, PHP_URL_USER))
|
||||
{
|
||||
$this->check_set_context($url);
|
||||
}
|
||||
// check if recursive option is set and needed
|
||||
if (($options & STREAM_MKDIR_RECURSIVE) &&
|
||||
($parent_url = Vfs::dirname($url)) &&
|
||||
@ -660,7 +553,7 @@ class StreamWrapper implements StreamWrapperIface
|
||||
$options &= ~STREAM_MKDIR_RECURSIVE;
|
||||
}
|
||||
|
||||
$ret = mkdir($url,$mode,$options);
|
||||
$ret = mkdir($url, $mode, $options, $this->context);
|
||||
|
||||
// call "vfs_mkdir" hook
|
||||
if ($ret && !class_exists('setup_process', false))
|
||||
@ -702,8 +595,14 @@ class StreamWrapper implements StreamWrapperIface
|
||||
}
|
||||
$stat = $this->url_stat($path, STREAM_URL_STAT_LINK);
|
||||
|
||||
// set user context
|
||||
if (Vfs::parse_url($url, PHP_URL_USER))
|
||||
{
|
||||
$this->check_set_context($url);
|
||||
}
|
||||
self::symlinkCache_remove($path);
|
||||
$ok = rmdir($url);
|
||||
$ok = rmdir($url, $this->context);
|
||||
clearstatcache(); // otherwise next stat call still returns it
|
||||
|
||||
// call "vfs_rmdir" hook, only after successful rmdir
|
||||
if ($ok && !class_exists('setup_process', false))
|
||||
@ -735,13 +634,16 @@ class StreamWrapper implements StreamWrapperIface
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."( $path,$options) resolve_url_symlinks() failed!");
|
||||
return false;
|
||||
}
|
||||
// need to set user-context from resolved url
|
||||
$this->check_set_context($this->opened_dir_url);
|
||||
|
||||
if (!($this->opened_dir = $this->context ?
|
||||
opendir($this->opened_dir_url, $this->context) : opendir($this->opened_dir_url)))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."( $path,$options) opendir($this->opened_dir_url) failed!");
|
||||
return false;
|
||||
}
|
||||
$this->opened_dir_writable = Vfs::check_access($this->opened_dir_url,Vfs::WRITABLE);
|
||||
$this->opened_dir_writable = $this->check_access($this->opened_dir_url,Vfs::WRITABLE);
|
||||
// check our fstab if we need to add some of the mountpoints
|
||||
$basepath = Vfs::parse_url($path,PHP_URL_PATH);
|
||||
foreach(array_keys(self::$fstab) as $mounted)
|
||||
@ -749,7 +651,7 @@ class StreamWrapper implements StreamWrapperIface
|
||||
if (((Vfs::dirname($mounted) == $basepath || Vfs::dirname($mounted).'/' == $basepath) && $mounted != '/') &&
|
||||
// only return children readable by the user, if dir is not writable
|
||||
(!self::HIDE_UNREADABLES || $this->opened_dir_writable ||
|
||||
Vfs::check_access($mounted,Vfs::READABLE)))
|
||||
$this->check_access($mounted,Vfs::READABLE)))
|
||||
{
|
||||
$this->extra_dirs[] = Vfs::basename($mounted);
|
||||
}
|
||||
@ -790,12 +692,30 @@ class StreamWrapper implements StreamWrapperIface
|
||||
*/
|
||||
function url_stat ( $path, $flags, $try_create_home=false, $check_symlink_components=true, $check_symlink_depth=self::MAX_SYMLINK_DEPTH, $try_reconnect=true )
|
||||
{
|
||||
if (!($url = self::resolve_url($path,!($flags & STREAM_URL_STAT_LINK), $check_symlink_components)))
|
||||
// we have no context, but $path is a URL with a valid user --> set it
|
||||
$this->check_set_context($path);
|
||||
|
||||
if (!($url = static::resolve_url($path, !($flags & STREAM_URL_STAT_LINK), $check_symlink_components, true, false, $mount_point)))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path',$flags) can NOT resolve path!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need to make sure the mount-point is readable eg. if something is mounted into an other users home-directory
|
||||
if (!isset($mount_point)) Vfs::mount_url($url, $mount_point); // resolve_url only returns mount-point for pathes or vfs urls
|
||||
if (!($mount_point === '/' || Vfs::dirname($mount_point) === '/') && // they all are public readable
|
||||
($class = self::scheme2class(Vfs::parse_url($url, PHP_URL_SCHEME))) &&
|
||||
!is_a($class, Vfs\Sqlfs\StreamWrapper::class) && // decendents of SqlFS stream-wrapper always check traversal right to /
|
||||
!$this->check_access(Vfs::dirname($mount_point), Vfs::READABLE))
|
||||
{
|
||||
return false; // mount-point is not reachable
|
||||
}
|
||||
|
||||
if (empty(parse_url($url, PHP_URL_USER)))
|
||||
{
|
||||
$url = str_replace('://', '://'.Api\Accounts::id2name($this->context ? stream_context_get_options($this->context)[self::SCHEME]['user'] : Vfs::$user).'@', $url);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($flags & STREAM_URL_STAT_LINK)
|
||||
{
|
||||
@ -901,6 +821,47 @@ class StreamWrapper implements StreamWrapperIface
|
||||
return $stat;*/
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $path path to check
|
||||
* @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable
|
||||
* @return boolean
|
||||
*/
|
||||
function check_extended_acl($path, $check)
|
||||
{
|
||||
if (!($url = self::resolve_url($path)))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path', $check) can NOT resolve path: ".function_backtrace(1));
|
||||
return false;
|
||||
}
|
||||
// check backend for extended acls (only if path given)
|
||||
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
|
||||
*
|
||||
@ -945,95 +906,6 @@ class StreamWrapper implements StreamWrapperIface
|
||||
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().
|
||||
*
|
||||
@ -1059,7 +931,7 @@ class StreamWrapper implements StreamWrapperIface
|
||||
while($file !== false &&
|
||||
(is_array($this->extra_dirs) && in_array($file,$this->extra_dirs) || // do NOT return extra_dirs twice
|
||||
self::HIDE_UNREADABLES && !$this->opened_dir_writable &&
|
||||
!Vfs::check_access(Vfs::concat($this->opened_dir_url,$file),Vfs::READABLE)));
|
||||
!$this->check_access(Vfs::concat($this->opened_dir_url,$file),Vfs::READABLE)));
|
||||
}
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."( $this->opened_dir ) = '$file'");
|
||||
return $file;
|
||||
@ -1096,230 +968,6 @@ class StreamWrapper implements StreamWrapperIface
|
||||
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)
|
||||
{
|
||||
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
|
||||
*
|
||||
@ -1329,12 +977,19 @@ class StreamWrapper implements StreamWrapperIface
|
||||
if (in_array(self::SCHEME, stream_get_wrappers())) {
|
||||
stream_wrapper_unregister(self::SCHEME);
|
||||
}
|
||||
stream_register_wrapper(self::SCHEME,__CLASS__);
|
||||
stream_wrapper_register(self::SCHEME,__CLASS__);
|
||||
|
||||
if (($fstab = $GLOBALS['egw_info']['server']['vfs_fstab']) && is_array($fstab) && count($fstab))
|
||||
{
|
||||
self::$fstab = $fstab;
|
||||
}
|
||||
|
||||
// set default context for our schema ('vfs') with current user
|
||||
if (!($context = stream_context_get_options(stream_context_get_default())) || empty($context[self::SCHEME]['user']))
|
||||
{
|
||||
$context[self::SCHEME]['user'] = (int)$GLOBALS['egw_info']['user']['account_id'];
|
||||
stream_context_set_default($context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
247
api/src/Vfs/UserContextTrait.php
Normal file
247
api/src/Vfs/UserContextTrait.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware API: VFS - Trait to store user / account_id in stream context
|
||||
*
|
||||
* @link https://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) 2020 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs;
|
||||
|
||||
use EGroupware\Api\Vfs;
|
||||
use EGroupware\Api;
|
||||
|
||||
/**
|
||||
* Trait to store user / account_id in stream context
|
||||
*
|
||||
* Used by Vfs and SqlFS stream-wrapper.
|
||||
*
|
||||
* @property int $user user / account_id stored in context
|
||||
*/
|
||||
trait UserContextTrait
|
||||
{
|
||||
/**
|
||||
* optional context param when opening the stream, null if no context passed
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* Contructor to set context/user incl. from user in url or passed in context
|
||||
*
|
||||
* @param resource|string|null $url_or_context url with user or context to set
|
||||
*/
|
||||
public function __construct($url_or_context=null)
|
||||
{
|
||||
if (is_resource($url_or_context))
|
||||
{
|
||||
$this->context = $url_or_context;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isset($this->context)) // PHP set's it before constructor is called!
|
||||
{
|
||||
$this->context = stream_context_get_default();
|
||||
}
|
||||
// if context set by PHP contains no user, set user from our default context (Vfs::$user)
|
||||
elseif (empty(stream_context_get_options($this->context)[Vfs::SCHEME]['user']))
|
||||
{
|
||||
stream_context_set_option($this->context, stream_context_get_options(stream_context_get_default()));
|
||||
}
|
||||
|
||||
if (is_string($url_or_context))
|
||||
{
|
||||
$this->check_set_context($url_or_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have an url with a user --> set it as context
|
||||
*
|
||||
* @param $url
|
||||
*/
|
||||
protected function check_set_context($url)
|
||||
{
|
||||
if ($url[0] !== '/' && ($account_lid = Vfs::parse_url($url, PHP_URL_USER)))
|
||||
{
|
||||
$this->user = $account_lid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string $path path
|
||||
* @param int $check mode to check: one or more or'ed together of: 4 = Vfs::READABLE,
|
||||
* 2 = Vfs::WRITABLE, 1 = Vfs::EXECUTABLE
|
||||
* @param array|boolean $stat =null stat array or false, to not query it again
|
||||
* @return boolean
|
||||
*/
|
||||
function check_access($path, $check, $stat=null)
|
||||
{
|
||||
if (Vfs::$is_root)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// throw exception if stat array is used insead of path, can be removed soon
|
||||
if (is_array($path))
|
||||
{
|
||||
throw new Exception\WrongParameter('path has to be string, use check_access($path,$check,$stat=null)!');
|
||||
}
|
||||
|
||||
// if we have no $stat, delegate whole check to vfs stream-wrapper to correctly deal with shares / effective user-ids
|
||||
if (is_null($stat))
|
||||
{
|
||||
$stat = $this->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__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!");
|
||||
return false; // file not found
|
||||
}
|
||||
|
||||
// only vfs stream-wrapper sets $stat['url'], use given url instead
|
||||
if (!isset($stat['url']) && $path[0] !== '/')
|
||||
{
|
||||
$stat['url'] = $path;
|
||||
}
|
||||
|
||||
// if we check writable and have a readonly mount --> return false, as backends dont know about r/o url parameter
|
||||
if ($check == Vfs::WRITABLE && Vfs\StreamWrapper::url_is_readonly($stat['url']))
|
||||
{
|
||||
//error_log(__METHOD__."(path=$path, check=writable, ...) failed because mount is readonly");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if we use an EGroupwre stream wrapper, or a stock php one
|
||||
// if it's not an EGroupware one, we can NOT use uid, gid and mode!
|
||||
if (($scheme = Vfs::parse_url($stat['url'], PHP_URL_SCHEME)) && !(class_exists(Vfs::scheme2class($scheme))))
|
||||
{
|
||||
switch($check)
|
||||
{
|
||||
case Vfs::READABLE:
|
||||
return is_readable($stat['url']);
|
||||
case Vfs::WRITABLE:
|
||||
return is_writable($stat['url']);
|
||||
case Vfs::EXECUTABLE:
|
||||
return is_executable($stat['url']);
|
||||
}
|
||||
}
|
||||
|
||||
// check if other rights grant access
|
||||
if (($stat['mode'] & $check) == $check)
|
||||
{
|
||||
//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'] == $this->user)
|
||||
{
|
||||
//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
|
||||
if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid'])
|
||||
{
|
||||
if (($memberships = Api\Accounts::getInstance()->memberships($this->user, true)) && in_array(-abs($stat['gid']), $memberships))
|
||||
{
|
||||
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// check extended acls (only if path given)
|
||||
$ret = method_exists($this, 'check_extended_acl') && $path && $this->check_extended_acl($stat['url'] ?? $path, $check);
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
switch($name)
|
||||
{
|
||||
case 'user':
|
||||
return $this->context ? stream_context_get_options($this->context)[Vfs::SCHEME]['user'] : Vfs::$user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
switch($name)
|
||||
{
|
||||
case 'user':
|
||||
if (!is_int($value) && is_string($value) && !is_numeric($value))
|
||||
{
|
||||
$value = Api\Accounts::getInstance()->name2id($value);
|
||||
}
|
||||
if ($value)
|
||||
{
|
||||
$options = [
|
||||
Vfs::SCHEME => ['user' => (int)$value]
|
||||
];
|
||||
// do NOT overwrite default context
|
||||
if ($this->context && $this->context !== stream_context_get_default())
|
||||
{
|
||||
stream_context_set_option($this->context, $options);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->context = stream_context_create($options);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user context for given url, eg. to use with regular stream functions
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $extra =[] addtional context options
|
||||
* @return resource context with user, plus optional extra options
|
||||
*/
|
||||
public static function userContext($url, array $extra=[])
|
||||
{
|
||||
if (($user = Vfs::parse_url($url, PHP_URL_USER)) &&
|
||||
($account_id = Api\Accounts::getInstance()->name2id($user)) &&
|
||||
($account_id != Vfs::$user) || $extra) // never set extra options on default context!
|
||||
{
|
||||
$context = stream_context_create(array_merge_recursive([Vfs::SCHEME => ['user' => (int)$account_id ?: Vfs::$user]], $extra));
|
||||
}
|
||||
else
|
||||
{
|
||||
$context = stream_context_get_default();
|
||||
}
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return $this->__get($name) !== null;
|
||||
}
|
||||
}
|
@ -74,6 +74,14 @@ abstract class LoggedInTest extends TestCase
|
||||
*/
|
||||
public static function tearDownAfterClass() : void
|
||||
{
|
||||
// Clean up VFS
|
||||
Vfs::clearstatcache();
|
||||
// Reset stream context, or current user will always be there
|
||||
stream_context_set_option(stream_context_get_default(),['vfs'=>['user' => null]]);
|
||||
|
||||
// Clear some link caching
|
||||
Link::init_static(true);
|
||||
|
||||
if($GLOBALS['egw'])
|
||||
{
|
||||
if($GLOBALS['egw']->session)
|
||||
@ -181,6 +189,9 @@ abstract class LoggedInTest extends TestCase
|
||||
// Disable asyc while we test
|
||||
$GLOBALS['egw_info']['server']['asyncservice'] = 'off';
|
||||
|
||||
// Set up Vfs
|
||||
Vfs::init_static();
|
||||
Vfs\StreamWrapper::init_static();
|
||||
while(ob_get_level() > $ob_level)
|
||||
{
|
||||
ob_end_flush();
|
||||
@ -228,4 +239,19 @@ abstract class LoggedInTest extends TestCase
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out the current user, log in as the given user
|
||||
*
|
||||
* @param $account_lid
|
||||
* @param $password
|
||||
*/
|
||||
protected function switchUser($account_lid, $password)
|
||||
{
|
||||
// Log out
|
||||
self::tearDownAfterClass();
|
||||
|
||||
// Log in
|
||||
static::load_egw($account_lid,$password);
|
||||
}
|
||||
}
|
58
api/tests/Vfs/Filesystem/StreamWrapperTest.php
Normal file
58
api/tests/Vfs/Filesystem/StreamWrapperTest.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test the basic Vfs::StreamWrapper
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright (c) 2020 Nathan Gray
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs\Filesystem;
|
||||
|
||||
require_once __DIR__ . '/../StreamWrapperBase.php';
|
||||
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
|
||||
|
||||
class StreamWrapperTest extends Vfs\StreamWrapperBase
|
||||
{
|
||||
public static $mountpoint = '/home/demo/filesystem';
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->files[] = $this->test_file = $this->getFilename();
|
||||
}
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function mount(): void
|
||||
{
|
||||
$this->mountFilesystem(static::$mountpoint);
|
||||
}
|
||||
|
||||
protected function allowAccess(string $test_name, string &$test_file, int $test_user, string $needed) : void
|
||||
{
|
||||
// We'll allow access by putting test user in Default group
|
||||
$command = new \admin_cmd_edit_user($test_user, ['account_groups' => array_merge($this->account['account_groups'],['Default'])]);
|
||||
$command->run();
|
||||
|
||||
// Add explicit permission on group
|
||||
Vfs::chmod($test_file, Vfs::mode2int('g+'.$needed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a filename that reflects the current test
|
||||
*/
|
||||
protected function getFilename($path = null)
|
||||
{
|
||||
return parent::getFilename(static::$mountpoint);
|
||||
}
|
||||
}
|
113
api/tests/Vfs/Links/StreamWrapperTest.php
Normal file
113
api/tests/Vfs/Links/StreamWrapperTest.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test the basic Vfs::StreamWrapper
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright (c) 2020 Nathan Gray
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs\Links;
|
||||
|
||||
require_once __DIR__ . '/../StreamWrapperBase.php';
|
||||
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
|
||||
|
||||
class StreamWrapperTest extends Vfs\StreamWrapperBase
|
||||
{
|
||||
protected $entries = [];
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
}
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
// Do local stuff first, parent will remove stuff that is needed
|
||||
|
||||
$bo = new \infolog_bo();
|
||||
foreach($this->entries as $entry)
|
||||
{
|
||||
$bo->delete($entry);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testSimpleReadWrite(): string
|
||||
{
|
||||
$info_id = $this->make_infolog();
|
||||
$this->files[] = $this->test_file = $this->getFilename(null, $info_id);
|
||||
|
||||
return parent::testSimpleReadWrite();
|
||||
}
|
||||
|
||||
public function testNoReadAccess(): void
|
||||
{
|
||||
$info_id = $this->make_infolog();
|
||||
$this->files[] = $this->test_file = $this->getFilename(null, $info_id);
|
||||
|
||||
parent::testNoReadAccess();
|
||||
}
|
||||
|
||||
public function testWithAccess(): void
|
||||
{
|
||||
$info_id = $this->make_infolog();
|
||||
$this->files[] = $this->test_file = $this->getFilename(null, $info_id);
|
||||
|
||||
parent::testWithAccess();
|
||||
}
|
||||
|
||||
protected function allowAccess(string $test_name, string &$test_file, int $test_user, string $needed) : void
|
||||
{
|
||||
// We'll allow access by putting test user in responsible
|
||||
$so = new \infolog_so();
|
||||
$element = $so->read(Array('info_id' => $this->entries[0]));
|
||||
$element['info_responsible'] = [$test_user];
|
||||
$so->write($element);
|
||||
}
|
||||
|
||||
protected function mount() : void
|
||||
{
|
||||
$this->mountLinks('/apps');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an infolog entry
|
||||
*/
|
||||
protected function make_infolog()
|
||||
{
|
||||
$bo = new \infolog_bo();
|
||||
$element = array(
|
||||
'info_subject' => "Test infolog for #{$this->getName()}",
|
||||
'info_des' => 'Test element for ' . $this->getName() . "\n" . Api\DateTime::to(),
|
||||
'info_status' => 'open'
|
||||
);
|
||||
|
||||
$element_id = $bo->write($element, true, true, true, true);
|
||||
$this->entries[] = $element_id;
|
||||
return $element_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a filename that reflects the current test
|
||||
* @param $path
|
||||
* @param $info_id
|
||||
* @return string
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function getFilename($path, $info_id)
|
||||
{
|
||||
if(is_null($path)) $path = '/apps/infolog/';
|
||||
if(substr($path,-1,1) !== '/') $path = $path . '/';
|
||||
$reflect = new \ReflectionClass($this);
|
||||
return $path .$info_id .'/'. $reflect->getShortName() . '_' . $this->getName() . '.txt';
|
||||
}
|
||||
|
||||
}
|
@ -19,7 +19,7 @@ use EGroupware\Api\Vfs;
|
||||
use EGroupware\Stylite\Vfs\Versioning;
|
||||
|
||||
|
||||
class ProppatchTest extends StreamWrapperBase
|
||||
class ProppatchTest extends LoggedInTest
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
@ -113,4 +113,17 @@ class ProppatchTest extends StreamWrapperBase
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a filename that reflects the current test
|
||||
*/
|
||||
protected function getFilename($path = null)
|
||||
{
|
||||
if(is_null($path)) $path = Vfs::get_home_dir().'/';
|
||||
if(substr($path,-1,1) !== '/') $path = $path . '/';
|
||||
|
||||
$reflect = new \ReflectionClass($this);
|
||||
return $path . $reflect->getShortName() . '_' . $this->getName(false) . '.txt';
|
||||
}
|
||||
|
||||
}
|
117
api/tests/Vfs/Sharing/StreamWrapperTest.php
Normal file
117
api/tests/Vfs/Sharing/StreamWrapperTest.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test the basics of Sharing::StreamWrapper
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright (c) 2020 Nathan Gray
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Vfs\Sharing;
|
||||
|
||||
require_once __DIR__ . '/../StreamWrapperBase.php';
|
||||
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
use EGroupware\Api\Vfs\Sharing;
|
||||
|
||||
|
||||
class StreamWrapperTest extends Vfs\StreamWrapperBase
|
||||
{
|
||||
protected $share = [];
|
||||
|
||||
static $test_dir = 'TestShareFolder';
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->createShare();
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testSimpleReadWrite(): string
|
||||
{
|
||||
$this->files[] = $this->test_file = $this->getFilename('',false);
|
||||
|
||||
return parent::testSimpleReadWrite();
|
||||
}
|
||||
|
||||
public function testNoReadAccess(): void
|
||||
{
|
||||
$this->files[] = $this->test_file = $this->getFilename('',false);
|
||||
|
||||
parent::testNoReadAccess();
|
||||
}
|
||||
|
||||
public function testWithAccess(): void
|
||||
{
|
||||
$this->files[] = $this->test_file = $this->getFilename('',false);
|
||||
|
||||
parent::testWithAccess();
|
||||
}
|
||||
|
||||
protected function allowAccess(string $test_name, string &$test_file, int $test_user, string $needed) : void
|
||||
{
|
||||
// Anyone who mounts will have access, but the available path changes
|
||||
$test_file = '/home/'. $GLOBALS['egw']->accounts->id2name($test_user) . '/' .
|
||||
Vfs\Sharing::SHARES_DIRECTORY .'/'.static::$test_dir .'/'. Vfs::basename($test_file);
|
||||
}
|
||||
|
||||
public function mount() : void
|
||||
{
|
||||
Api\Vfs\Sharing::setup_share(true,$this->share);
|
||||
}
|
||||
|
||||
public function createShare(&$dir='', $extra = array(), $create = 'createShare')
|
||||
{
|
||||
// First, create the directory to be shared
|
||||
$this->files[] = $dir = Vfs::get_home_dir() . '/'. static::$test_dir;
|
||||
Vfs::mkdir($dir);
|
||||
|
||||
// Create and use link
|
||||
$this->getShareExtra($dir, Sharing::WRITABLE, $extra);
|
||||
|
||||
$this->share = Vfs\Sharing::create('',$dir,Sharing::WRITABLE,$dir,'',$extra);
|
||||
$link = Vfs\Sharing::share2link($this->share);
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the extra information required to create a share link for the given
|
||||
* directory, with the given mode
|
||||
*
|
||||
* @param string $dir Share target
|
||||
* @param int $mode Share mode
|
||||
* @param Array $extra
|
||||
*/
|
||||
protected function getShareExtra($dir, $mode, &$extra)
|
||||
{
|
||||
switch($mode)
|
||||
{
|
||||
case Sharing::WRITABLE:
|
||||
$extra['share_writable'] = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a filename that reflects the current test
|
||||
* @param $path
|
||||
* @param bool $mounted Get the path if the share is mounted, or the original
|
||||
* @return string
|
||||
*/
|
||||
protected function getFilename($path = null, $mounted = true) : string
|
||||
{
|
||||
return parent::getFilename(Vfs::get_home_dir() . '/'.
|
||||
($mounted ? Vfs\Sharing::SHARES_DIRECTORY .'/' : '').static::$test_dir .'/'. $path);
|
||||
}
|
||||
|
||||
}
|
@ -55,6 +55,7 @@ class SharingACLTest extends SharingBase
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
LoggedInTest::setUpBeforeClass();
|
||||
parent::tearDown();
|
||||
if($this->account_id)
|
||||
{
|
||||
@ -198,9 +199,6 @@ class SharingACLTest extends SharingBase
|
||||
$this->assertNotNull($form, "Could not read the share link");
|
||||
$rows = $data['data']['content']['nm']['rows'];
|
||||
|
||||
Vfs::clearstatcache();
|
||||
Vfs::init_static();
|
||||
Vfs\StreamWrapper::init_static();
|
||||
|
||||
// Check we can't find the non-shared file
|
||||
$result = array_filter($rows, function($v) {
|
||||
@ -236,10 +234,6 @@ class SharingACLTest extends SharingBase
|
||||
$this->assertNotNull($form, "Could not read the share link");
|
||||
$rows = array_values($data['data']['content']['nm']['rows']);
|
||||
|
||||
Vfs::clearstatcache();
|
||||
Vfs::init_static();
|
||||
Vfs\StreamWrapper::init_static();
|
||||
|
||||
// Check we can't find the non-shared file
|
||||
$result = array_filter($rows, function($v) {
|
||||
return $v['name'] == $this->no_access;
|
||||
@ -318,6 +312,6 @@ class SharingACLTest extends SharingBase
|
||||
// Log out & clear cache
|
||||
LoggedInTest::tearDownAfterClass();
|
||||
|
||||
$this->checkSharedFile($link, $mimetype);
|
||||
$this->checkSharedFile($link, $mimetype, $share);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class SharingBackendTest extends SharingBase
|
||||
*/
|
||||
public function testHomeReadonly()
|
||||
{
|
||||
$dir = Vfs::get_home_dir().'/';
|
||||
$dir = Vfs::get_home_dir().'/'.$this->getName(false).'/';
|
||||
|
||||
$this->checkDirectory($dir, Sharing::READONLY);
|
||||
}
|
||||
@ -42,7 +42,7 @@ class SharingBackendTest extends SharingBase
|
||||
*/
|
||||
public function testHomeWritable()
|
||||
{
|
||||
$dir = Vfs::get_home_dir().'/';
|
||||
$dir = Vfs::get_home_dir().'/'.$this->getName(false).'/';
|
||||
|
||||
$this->checkDirectory($dir, Sharing::WRITABLE);
|
||||
}
|
||||
@ -124,6 +124,9 @@ class SharingBackendTest extends SharingBase
|
||||
*/
|
||||
public function testLinksReadonly()
|
||||
{
|
||||
// Need to mount apps
|
||||
$this->mountLinks("/apps");
|
||||
|
||||
// Create an infolog entry for testing purposes
|
||||
$info_id = $this->make_infolog();
|
||||
$bo = new \infolog_bo();
|
||||
@ -142,6 +145,9 @@ class SharingBackendTest extends SharingBase
|
||||
*/
|
||||
public function testLinksWritable()
|
||||
{
|
||||
// Need to mount apps
|
||||
$this->mountLinks("/apps");
|
||||
|
||||
// Create an infolog entry for testing purposes
|
||||
$bo = new \infolog_bo();
|
||||
$info_id = $this->make_infolog();
|
||||
|
@ -77,14 +77,15 @@ class SharingBase extends LoggedInTest
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
LoggedInTest::tearDownAfterClass();
|
||||
try
|
||||
{
|
||||
// Some tests may leave us logged out, which will cause failures in parent cleanup
|
||||
LoggedInTest::tearDownAfterClass();
|
||||
}
|
||||
catch(\Throwable $e) {}
|
||||
|
||||
LoggedInTest::setupBeforeClass();
|
||||
|
||||
// Re-init, since they look at user, fstab, etc.
|
||||
// Also, further tests that access the filesystem fail if we don't
|
||||
Vfs::clearstatcache();
|
||||
Vfs::init_static();
|
||||
Vfs\StreamWrapper::init_static();
|
||||
|
||||
// Need to ask about mounts, or other tests fail
|
||||
Vfs::mount();
|
||||
@ -156,6 +157,10 @@ class SharingBase extends LoggedInTest
|
||||
{
|
||||
$dir .= '/';
|
||||
}
|
||||
if(!Vfs::is_readable($dir))
|
||||
{
|
||||
Vfs::mkdir($dir);
|
||||
}
|
||||
$this->files += $this->addFiles($dir);
|
||||
|
||||
$logged_in_files = array_map(
|
||||
@ -233,7 +238,7 @@ class SharingBase extends LoggedInTest
|
||||
switch($mode)
|
||||
{
|
||||
case Sharing::READONLY:
|
||||
$this->assertFalse(Vfs::is_writable($file));
|
||||
$this->assertFalse(Vfs::is_writable($file), "Readonly share file '$file' is writable");
|
||||
if(!Vfs::is_dir($file))
|
||||
{
|
||||
// We expect this to fail
|
||||
@ -254,6 +259,24 @@ class SharingBase extends LoggedInTest
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the app entries into the filesystem
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
protected function mountLinks($path)
|
||||
{
|
||||
Vfs::$is_root = true;
|
||||
$url = Links\StreamWrapper::PREFIX . '/apps';
|
||||
$this->assertTrue(
|
||||
Vfs::mount($url, $path, false, false),
|
||||
"Unable to mount $url => $path"
|
||||
);
|
||||
Vfs::$is_root = false;
|
||||
|
||||
$this->mounts[] = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start versioning for the given path
|
||||
*
|
||||
@ -501,7 +524,7 @@ class SharingBase extends LoggedInTest
|
||||
else
|
||||
{
|
||||
// If it's a file, check to make sure we get the file
|
||||
$this->checkSharedFile($link, $mimetype);
|
||||
$this->checkSharedFile($link, $mimetype, $share);
|
||||
}
|
||||
|
||||
// Load share
|
||||
@ -520,8 +543,7 @@ class SharingBase extends LoggedInTest
|
||||
$this->assertTrue(Vfs::is_readable('/'), 'Could not read root (/) from link');
|
||||
|
||||
// Check other paths
|
||||
$this->assertFalse(Vfs::is_readable($path), "Was able to read $path as anoymous, it should be mounted as /");
|
||||
$this->assertFalse(Vfs::is_readable($path . '../'));
|
||||
$this->assertFalse(Vfs::is_readable($path), "Was able to read $path as anonymous, it should be mounted as /");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -537,6 +559,13 @@ class SharingBase extends LoggedInTest
|
||||
$curl = curl_init($link);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
$cookie = '';
|
||||
if($GLOBALS['egw']->session->sessionid || $share['share_with'])
|
||||
{
|
||||
$session_id = $GLOBALS['egw']->session->sessionid ?: $share['share_with'];
|
||||
$cookie .= ';'.Api\Session::EGW_SESSION_NAME."={$session_id}";
|
||||
}
|
||||
curl_setopt($curl, CURLOPT_COOKIE, $cookie);
|
||||
$html = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
@ -568,7 +597,7 @@ class SharingBase extends LoggedInTest
|
||||
|
||||
// Make sure we start at root, not somewhere else like the token mounted
|
||||
// as a sub-directory
|
||||
$this->assertEquals('/', $data->data->content->nm->path);
|
||||
$this->assertEquals('/', $data->data->content->nm->path, "Share was not mounted at /");
|
||||
|
||||
unset($data->data->content->nm->actions);
|
||||
//var_dump($data->data->content->nm);
|
||||
@ -580,16 +609,17 @@ class SharingBase extends LoggedInTest
|
||||
* @param $link Share URL
|
||||
* @param $file Vfs path to file
|
||||
*/
|
||||
public function checkSharedFile($link, $mimetype)
|
||||
public function checkSharedFile($link, $mimetype, $share)
|
||||
{
|
||||
stream_context_set_default(
|
||||
$context = stream_context_create(
|
||||
array(
|
||||
'http' => array(
|
||||
'method' => 'HEAD'
|
||||
'method' => 'HEAD',
|
||||
'header' => "Cookie: XDEBUG_SESSION=PHPSTORM;".Api\Session::EGW_SESSION_NAME.'=' . $share['share_with']
|
||||
)
|
||||
)
|
||||
);
|
||||
$headers = get_headers($link);
|
||||
$headers = get_headers($link, false, $context);
|
||||
$this->assertEquals('200', substr($headers[0], 9, 3), 'Did not find the file, got ' . $headers[0]);
|
||||
|
||||
$indexed_headers = array();
|
||||
|
@ -21,7 +21,7 @@ use EGroupware\Api\Vfs;
|
||||
use EGroupware\Stylite\Vfs\Versioning;
|
||||
|
||||
|
||||
class StreamWrapperBase extends LoggedInTest
|
||||
abstract class StreamWrapperBase extends LoggedInTest
|
||||
{
|
||||
/**
|
||||
* How much should be logged to the console (stdout)
|
||||
@ -32,6 +32,11 @@ class StreamWrapperBase extends LoggedInTest
|
||||
*/
|
||||
const LOG_LEVEL = 0;
|
||||
|
||||
/**
|
||||
* @var string If we're just doing a simple test with one file, use this file
|
||||
*/
|
||||
protected $test_file = '';
|
||||
|
||||
/**
|
||||
* Keep track of files to remove after
|
||||
* @var Array
|
||||
@ -50,6 +55,24 @@ class StreamWrapperBase extends LoggedInTest
|
||||
'maxdepth' => 5
|
||||
);
|
||||
|
||||
// User for testing - we share with this user & log in as them for checking
|
||||
protected $account_id;
|
||||
|
||||
// File that should not be available due to permissions
|
||||
protected $no_access;
|
||||
|
||||
// Use a completely new user, so we know it's there and "clean"
|
||||
protected $account = array(
|
||||
'account_lid' => 'user_test',
|
||||
'account_firstname' => 'Access',
|
||||
'account_lastname' => 'Test',
|
||||
'account_passwd' => 'passw0rd',
|
||||
'account_passwd_2' => 'passw0rd',
|
||||
// Don't let them in Default, any set ACLs will interfere with tests
|
||||
'account_primary_group' => 'Testers',
|
||||
'account_groups' => ['Testers']
|
||||
);
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
// Check we have basic access
|
||||
@ -61,19 +84,14 @@ class StreamWrapperBase extends LoggedInTest
|
||||
{
|
||||
$this->markTestSkipped('No write access to files dir "' .$GLOBALS['egw_info']['server']['files_dir'].'"' );
|
||||
}
|
||||
$this->mount();
|
||||
}
|
||||
|
||||
protected function tearDown() : void
|
||||
{
|
||||
LoggedInTest::tearDownAfterClass();
|
||||
LoggedInTest::setupBeforeClass();
|
||||
|
||||
// Re-init, since they look at user, fstab, etc.
|
||||
// Also, further tests that access the filesystem fail if we don't
|
||||
Vfs::clearstatcache();
|
||||
Vfs::init_static();
|
||||
Vfs\StreamWrapper::init_static();
|
||||
|
||||
// Make sure we're on the original user. Failures could cause us to be logged in as someone else
|
||||
$this->switchUser($GLOBALS['EGW_USER'], $GLOBALS['EGW_PASSWORD']);
|
||||
$this->mount();
|
||||
// Need to ask about mounts, or other tests fail
|
||||
Vfs::mount();
|
||||
|
||||
@ -82,12 +100,19 @@ class StreamWrapperBase extends LoggedInTest
|
||||
|
||||
if(static::LOG_LEVEL > 1)
|
||||
{
|
||||
if($this->account_id) error_log($this->getName() . ' user to be removed: ' . $this->account_id);
|
||||
error_log($this->getName() . ' files for removal:');
|
||||
error_log(implode("\n",$this->files));
|
||||
error_log($this->getName() . ' mounts for removal:');
|
||||
error_log(implode("\n",$this->mounts));
|
||||
}
|
||||
|
||||
// Remove our other test user
|
||||
if($this->account_id)
|
||||
{
|
||||
$GLOBALS['egw']->accounts->delete($this->account_id);
|
||||
}
|
||||
|
||||
// Remove any added files (as root to limit versioning issues)
|
||||
if(in_array('/',$this->files))
|
||||
{
|
||||
@ -107,6 +132,248 @@ class StreamWrapperBase extends LoggedInTest
|
||||
Vfs::$is_root = $backup;
|
||||
}
|
||||
|
||||
/////
|
||||
/// These tests will be run by every extending class, with
|
||||
/// the extending class's setUp(). They can be overridden, but
|
||||
/// we get free tests this way with no copy/paste
|
||||
/////
|
||||
|
||||
/**
|
||||
* Simple test that we can write something and it's there
|
||||
* By putting it in the base class, this test gets run for every backend
|
||||
*/
|
||||
public function testSimpleReadWrite() : string
|
||||
{
|
||||
if(!$this->test_file)
|
||||
{
|
||||
$this->markTestSkipped("No test file set - set it in setUp() or overriding test");
|
||||
}
|
||||
|
||||
// Check that the file is not there
|
||||
$pre_start = Vfs::stat($this->test_file);
|
||||
$this->assertEquals(null,$pre_start,
|
||||
"File '$this->test_file' was there before we started, check clean up"
|
||||
);
|
||||
|
||||
// Write
|
||||
$contents = $this->getName() . "\nJust a test ;)\n";
|
||||
$this->assertNotFalse(
|
||||
file_put_contents(Vfs::PREFIX . $this->test_file, $contents),
|
||||
"Could not write file $this->test_file"
|
||||
);
|
||||
|
||||
// Check contents are unchanged
|
||||
$this->assertEquals(
|
||||
$contents, file_get_contents(Vfs::PREFIX . $this->test_file),
|
||||
"Read file contents do not match what was written"
|
||||
);
|
||||
|
||||
return $this->test_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple delete of a file
|
||||
* By putting it in the base class, this test gets run for every backend
|
||||
*
|
||||
* @depends testSimpleReadWrite
|
||||
*/
|
||||
public function testDelete($file) : void
|
||||
{
|
||||
if(!$this->test_file && !$file)
|
||||
{
|
||||
$this->markTestSkipped("No test file set - set it in setUp() or overriding test");
|
||||
}
|
||||
|
||||
// Write
|
||||
if(!$file)
|
||||
{
|
||||
$contents = $this->getName() . "\nJust a test ;)\n";
|
||||
$this->assertNotFalse(
|
||||
file_put_contents(Vfs::PREFIX . $this->test_file, $contents),
|
||||
"Could not write file $this->test_file"
|
||||
);
|
||||
|
||||
$start = Vfs::stat($this->test_file);
|
||||
$this->assertNotNull(
|
||||
$start,
|
||||
"File '$this->test_file' was not what we expected to find after writing"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->test_file = $file;
|
||||
}
|
||||
|
||||
Vfs::unlink($this->test_file);
|
||||
|
||||
$post = Vfs::stat($this->test_file);
|
||||
$this->assertEquals(null,$post,
|
||||
"File '$this->test_file' was there after deleting"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a user with no permission to a file cannot access the file
|
||||
*
|
||||
* @depends testSimpleReadWrite
|
||||
* @throws Api\Exception\AssertionFailed
|
||||
*/
|
||||
public function testNoReadAccess() : void
|
||||
{
|
||||
if(!$this->test_file)
|
||||
{
|
||||
$this->markTestSkipped("No test file set - set it in setUp() or overriding test");
|
||||
}
|
||||
|
||||
// Check that the file is not there
|
||||
$pre_start = Vfs::stat($this->test_file);
|
||||
$this->assertEquals(null,$pre_start,
|
||||
"File '$this->test_file' was there before we started, check clean up"
|
||||
);
|
||||
|
||||
// Write
|
||||
$file = $this->test_file;
|
||||
$contents = $this->getName() . "\nJust a test ;)\n";
|
||||
$this->assertNotFalse(
|
||||
file_put_contents(Vfs::PREFIX . $file, $contents),
|
||||
"Could not write file $file"
|
||||
);
|
||||
|
||||
// Create another user who has no access to our file
|
||||
$user_b = $this->makeUser();
|
||||
|
||||
// Log in as them
|
||||
$this->switchUser($this->account['account_lid'], $this->account['account_passwd']);
|
||||
|
||||
$this->mount();
|
||||
|
||||
// Check the file
|
||||
$this->assertFalse(
|
||||
Vfs::is_readable($file),
|
||||
"File '$file' was accessible by another user who had no permission"
|
||||
);
|
||||
$this->assertFalse(
|
||||
file_get_contents(Vfs::PREFIX . $file),
|
||||
"Read someone else's file with no permission. " . Vfs::PREFIX . $file
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check that a user with permission to a file can access the file
|
||||
*
|
||||
* @depends testSimpleReadWrite
|
||||
* @throws Api\Exception\AssertionFailed
|
||||
*/
|
||||
public function testWithAccess() : void
|
||||
{
|
||||
if(!$this->test_file)
|
||||
{
|
||||
$this->markTestSkipped("No test file set - set it in setUp() or overriding test");
|
||||
}
|
||||
|
||||
// Check that the file is not there
|
||||
$pre_start = Vfs::stat($this->test_file);
|
||||
$this->assertEquals(null,$pre_start,
|
||||
"File '$this->test_file' was there before we started, check clean up"
|
||||
);
|
||||
|
||||
// Write
|
||||
$file = $this->test_file;
|
||||
$contents = $this->getName() . "\nJust a test ;)\n";
|
||||
$this->assertNotFalse(
|
||||
file_put_contents(Vfs::PREFIX . $file, $contents),
|
||||
"Could not write file $file"
|
||||
);
|
||||
$pre = Vfs::stat($this->test_file);
|
||||
|
||||
|
||||
// Create another user who has no access to our file
|
||||
$user_b = $this->makeUser();
|
||||
|
||||
// Allow access
|
||||
$this->allowAccess(
|
||||
$this->getName(false),
|
||||
$file,
|
||||
$user_b,
|
||||
'r'
|
||||
);
|
||||
|
||||
// Log in as them
|
||||
$this->switchUser($this->account['account_lid'], $this->account['account_passwd']);
|
||||
|
||||
$this->mount();
|
||||
|
||||
// Check the file
|
||||
$post = Vfs::stat($file);
|
||||
$this->assertNotNull($post,
|
||||
"File '$file' was not accessible by another user who had permission"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$contents,
|
||||
file_get_contents(Vfs::PREFIX . $file),
|
||||
"Problem reading contents of someone else's file (".Vfs::PREFIX . "$file) with permission"
|
||||
);
|
||||
$this->assertTrue(
|
||||
Vfs::is_readable($file),
|
||||
"Vfs says $file is not readable. It should be."
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
////// Handy functions ///////
|
||||
|
||||
/**
|
||||
* Create a test user, returns the account ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function makeUser(Array $account = []) : int
|
||||
{
|
||||
if(count($account) == 0)
|
||||
{
|
||||
$account = $this->account;
|
||||
}
|
||||
if(($account_id = $GLOBALS['egw']->accounts->name2id($account['account_lid'])))
|
||||
{
|
||||
// Delete if there in case something went wrong
|
||||
$GLOBALS['egw']->accounts->delete($account_id);
|
||||
}
|
||||
|
||||
// It needs its own group too, Default will mess with any ACL tests
|
||||
if(!$GLOBALS['egw']->accounts->exists($account['account_primary_group']))
|
||||
{
|
||||
$group = $this->makeTestGroup();
|
||||
}
|
||||
|
||||
// Execute
|
||||
$command = new \admin_cmd_edit_user(false, $account);
|
||||
$command->comment = 'Needed for unit test ' . $this->getName();
|
||||
$command->run();
|
||||
$this->account_id = $command->account;
|
||||
|
||||
if($group)
|
||||
{
|
||||
// Had to create the group, but we don't want current user in it
|
||||
$remove_group = new \admin_cmd_edit_group('Testers',['account_lid' => 'Testers', 'account_members' => [$this->account_id]]);
|
||||
$remove_group->run();
|
||||
}
|
||||
return $this->account_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a test group we can put our users in to avoid any ACLs on Default group
|
||||
*/
|
||||
protected function makeTestGroup()
|
||||
{
|
||||
// Execute
|
||||
$command = new \admin_cmd_edit_group(false, ['account_lid' => 'Testers', 'account_members' => $GLOBALS['egw_info']['user']['account_id']]);
|
||||
$command->comment = 'Needed for unit test ' . $this->getName();
|
||||
$command->run();
|
||||
return $command->account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a filename that reflects the current test
|
||||
*/
|
||||
@ -115,7 +382,48 @@ class StreamWrapperBase extends LoggedInTest
|
||||
if(is_null($path)) $path = Vfs::get_home_dir().'/';
|
||||
if(substr($path,-1,1) !== '/') $path = $path . '/';
|
||||
|
||||
return $path . get_class(this) . '_' . $this->getName() . '.txt';
|
||||
$reflect = new \ReflectionClass($this);
|
||||
return $path . $reflect->getShortName() . '_' . $this->getName() . '.txt';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the needed filesystem
|
||||
*
|
||||
* This may be called multiple times for each test as we change users, logout, etc.
|
||||
*/
|
||||
abstract protected function mount() : void;
|
||||
|
||||
/**
|
||||
* Allow access to the given file for the given user ID
|
||||
*
|
||||
* Using whatever way works best for the mount/streamwrapper being tested, allow the user access
|
||||
*
|
||||
* @param string $test_name
|
||||
* @param string $test_file
|
||||
* @param int $test_user
|
||||
* @param string $needed r, w, rw
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function allowAccess(string $test_name, string &$test_file, int $test_user, string $needed) : void;
|
||||
|
||||
/**
|
||||
* Mount the app entries into the filesystem
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
protected function mountLinks($path)
|
||||
{
|
||||
Vfs::$is_root = true;
|
||||
$url = Links\StreamWrapper::PREFIX . '/apps';
|
||||
$this->assertTrue(
|
||||
Vfs::mount($url, $path, false, false),
|
||||
"Unabe to mount $url => $path"
|
||||
);
|
||||
Vfs::$is_root = false;
|
||||
|
||||
$this->mounts[] = $path;
|
||||
Vfs::clearstatcache();
|
||||
Vfs::init_static();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,7 +495,7 @@ class StreamWrapperBase extends LoggedInTest
|
||||
Vfs::$is_root = true;
|
||||
|
||||
// I guess merge needs the dir in SQLFS first
|
||||
if(!Vfs::is_dir($dir)) Vfs::mkdir($path);
|
||||
if(!Vfs::is_dir($path)) Vfs::mkdir($path);
|
||||
Vfs::chmod($path, 0750);
|
||||
Vfs::chown($path, $GLOBALS['egw_info']['user']['account_id']);
|
||||
|
||||
@ -265,20 +573,4 @@ class StreamWrapperBase extends LoggedInTest
|
||||
*/
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an infolog entry
|
||||
*/
|
||||
protected function make_infolog()
|
||||
{
|
||||
$bo = new \infolog_bo();
|
||||
$element = array(
|
||||
'info_subject' => "Test infolog for #{$this->getName()}",
|
||||
'info_des' => 'Test element for ' . $this->getName() . "\n" . Api\DateTime::to(),
|
||||
'info_status' => 'open'
|
||||
);
|
||||
|
||||
$element_id = $bo->write($element, true, true, true, true);
|
||||
return $element_id;
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class StreamWrapperTest extends StreamWrapperBase
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->files[] = $this->test_file = $this->getFilename();
|
||||
}
|
||||
|
||||
protected function tearDown() : void
|
||||
@ -34,56 +34,27 @@ class StreamWrapperTest extends StreamWrapperBase
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that we can write something and it's there
|
||||
*/
|
||||
public function testSimpleReadWrite() : void
|
||||
public function testWithAccess() : void
|
||||
{
|
||||
$this->files[] = $test_file = $this->getFilename();
|
||||
$contents = $this->getName() . "\nJust a test ;)\n";
|
||||
$this->assertNotFalse(
|
||||
file_put_contents(Vfs::PREFIX . $test_file, $contents),
|
||||
"Could not write file $test_file"
|
||||
);
|
||||
// Put it in the group directory this time so we can give access
|
||||
$this->files[] = $this->test_file = $this->getFilename('/home/Default');
|
||||
|
||||
// Check contents are unchanged
|
||||
$this->assertEquals(
|
||||
$contents, file_get_contents(Vfs::PREFIX . $test_file),
|
||||
"Read file contents do not match what was written"
|
||||
);
|
||||
parent::testWithAccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple delete of a file
|
||||
*/
|
||||
public function testDelete() : void
|
||||
protected function mount(): void
|
||||
{
|
||||
$this->files[] = $test_file = $this->getFilename();
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
// Check that the file is not there
|
||||
$pre_start = Vfs::stat($test_file);
|
||||
$this->assertEquals(null,$pre_start,
|
||||
"File '$test_file' was there before we started, check clean up"
|
||||
);
|
||||
protected function allowAccess(string $test_name, string &$test_file, int $test_user, string $needed) : void
|
||||
{
|
||||
// We'll allow access by putting test user in Default group
|
||||
$command = new \admin_cmd_edit_user($test_user, ['account_groups' => array_merge($this->account['account_groups'],['Default'])]);
|
||||
$command->run();
|
||||
|
||||
// Write
|
||||
$contents = $this->getName() . "\nJust a test ;)\n";
|
||||
$this->assertNotFalse(
|
||||
file_put_contents(Vfs::PREFIX . $test_file, $contents),
|
||||
"Could not write file $test_file"
|
||||
);
|
||||
// Add explicit permission on group
|
||||
Vfs::chmod($test_file, Vfs::mode2int('g+'.$needed));
|
||||
|
||||
$start = Vfs::stat($test_file);
|
||||
$this->assertNotNull(
|
||||
$start,
|
||||
"File '$test_file' was not what we expected to find after writing"
|
||||
);
|
||||
|
||||
Vfs::unlink($test_file);
|
||||
|
||||
$post = Vfs::stat($test_file);
|
||||
$this->assertEquals(null,$post,
|
||||
"File '$test_file' was there after deleting"
|
||||
);
|
||||
}
|
||||
}
|
@ -49,12 +49,12 @@
|
||||
],
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.2"
|
||||
"php": "7.3"
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2,<=8.0.0alpha1",
|
||||
"php": ">=7.3,<=8.0.0alpha1",
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mysqli": "*",
|
||||
@ -87,6 +87,8 @@
|
||||
"egroupware/tracker": "self.version",
|
||||
"egroupware/z-push-dev": "^2.5",
|
||||
"fxp/composer-asset-plugin": "^1.2.2",
|
||||
"egroupware/guzzlestream": "dev-master",
|
||||
"egroupware/webdav": "dev-master",
|
||||
"npm-asset/as-jqplot": "1.0.*",
|
||||
"npm-asset/gridster": "0.5.*",
|
||||
"oomphinc/composer-installers-extender": "^1.1",
|
||||
|
657
composer.lock
generated
657
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bc3a7b62de6792d67aaf9f127480abea",
|
||||
"content-hash": "6a700a309b5c1bc30c563ccb2526e7e2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adldap2/adldap2",
|
||||
@ -932,6 +932,62 @@
|
||||
"homepage": "https://www.egroupware.org/",
|
||||
"time": "2020-08-19T13:40:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egroupware/guzzlestream",
|
||||
"version": "dev-master",
|
||||
"target-dir": "Guzzle/Stream",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EGroupware/stream.git",
|
||||
"reference": "d29fc35ebf3bd752308520aa5f17a3e5500f6af3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/EGroupware/stream/zipball/d29fc35ebf3bd752308520aa5f17a3e5500f6af3",
|
||||
"reference": "d29fc35ebf3bd752308520aa5f17a3e5500f6af3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzle/common": "^3.9.2",
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"replace": {
|
||||
"guzzle/stream": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"guzzle/http": "To convert Guzzle request objects to PHP streams"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Guzzle\\Stream": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle stream wrapper component",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"Guzzle",
|
||||
"component",
|
||||
"stream"
|
||||
],
|
||||
"time": "2020-09-23T16:33:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egroupware/icalendar",
|
||||
"version": "2.1.9",
|
||||
@ -976,12 +1032,12 @@
|
||||
{
|
||||
"name": "Chuck Hagenbuch",
|
||||
"email": "chuck@horde.org",
|
||||
"role": "Lead"
|
||||
"role": "lead"
|
||||
},
|
||||
{
|
||||
"name": "Jan Schneider",
|
||||
"email": "jan@horde.org",
|
||||
"role": "Lead"
|
||||
"role": "lead"
|
||||
},
|
||||
{
|
||||
"name": "Michael J Rubinsky",
|
||||
@ -1028,7 +1084,7 @@
|
||||
],
|
||||
"description": "Compiled version of magicsuggest customized for EGroupware project.",
|
||||
"homepage": "https://github.com/EGroupware/magicsuggest",
|
||||
"time": "2018-06-21T13:36:37+00:00"
|
||||
"time": "2018-06-21T10:14:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egroupware/news_admin",
|
||||
@ -1409,6 +1465,58 @@
|
||||
"homepage": "https://www.egroupware.org/",
|
||||
"time": "2020-08-28T17:39:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egroupware/webdav",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EGroupware/WebDAV.git",
|
||||
"reference": "889da78b6489965df8a379ccdc25853fe74da199"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/EGroupware/WebDAV/zipball/889da78b6489965df8a379ccdc25853fe74da199",
|
||||
"reference": "889da78b6489965df8a379ccdc25853fe74da199",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzle/http": "~3.0",
|
||||
"php": ">=5.3.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~3.7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"monolog/monolog": "Adds support for logging HTTP requests and responses",
|
||||
"symfony/finder": "Allows you to more easily filter the files that the stream wrapper returns"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Grale\\WebDav": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Geoffroy Letournel",
|
||||
"email": "geoffroy.letournel@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A simple PHP WebDAV client and stream wrapper",
|
||||
"homepage": "https://github.com/gletournel/WebDAV",
|
||||
"keywords": [
|
||||
"WebDAV",
|
||||
"php",
|
||||
"stream",
|
||||
"wrapper"
|
||||
],
|
||||
"time": "2020-09-23T16:16:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egroupware/z-push-dev",
|
||||
"version": "2.5.0",
|
||||
@ -1668,6 +1776,154 @@
|
||||
],
|
||||
"time": "2019-11-13T10:30:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzle/common",
|
||||
"version": "v3.9.2",
|
||||
"target-dir": "Guzzle/Common",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Guzzle3/common.git",
|
||||
"reference": "2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Guzzle3/common/zipball/2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc",
|
||||
"reference": "2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"symfony/event-dispatcher": ">=2.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Guzzle\\Common": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Common libraries used by Guzzle",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"collection",
|
||||
"common",
|
||||
"event",
|
||||
"exception"
|
||||
],
|
||||
"abandoned": "guzzle/guzzle",
|
||||
"time": "2014-08-11T04:32:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzle/http",
|
||||
"version": "v3.9.2",
|
||||
"target-dir": "Guzzle/Http",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Guzzle3/http.git",
|
||||
"reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Guzzle3/http/zipball/1e8dd1e2ba9dc42332396f39fbfab950b2301dc5",
|
||||
"reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzle/common": "self.version",
|
||||
"guzzle/parser": "self.version",
|
||||
"guzzle/stream": "self.version",
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Guzzle\\Http": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "HTTP libraries used by Guzzle",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"Guzzle",
|
||||
"client",
|
||||
"curl",
|
||||
"http",
|
||||
"http client"
|
||||
],
|
||||
"abandoned": "guzzle/guzzle",
|
||||
"time": "2014-08-11T04:32:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzle/parser",
|
||||
"version": "v3.9.2",
|
||||
"target-dir": "Guzzle/Parser",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Guzzle3/parser.git",
|
||||
"reference": "6874d171318a8e93eb6d224cf85e4678490b625c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Guzzle3/parser/zipball/6874d171318a8e93eb6d224cf85e4678490b625c",
|
||||
"reference": "6874d171318a8e93eb6d224cf85e4678490b625c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Guzzle\\Parser": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Interchangeable parsers used by Guzzle",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"URI Template",
|
||||
"cookie",
|
||||
"http",
|
||||
"message",
|
||||
"url"
|
||||
],
|
||||
"abandoned": "guzzle/guzzle",
|
||||
"time": "2014-02-05T18:29:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "imsglobal/lti-1p3-tool",
|
||||
"version": "dev-master",
|
||||
@ -2345,7 +2601,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library to wrap various compression techniques."
|
||||
"description": "An API for various compression techniques."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_Crypt",
|
||||
@ -2411,7 +2667,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library that provides blowfish encryption/decryption for PHP string data."
|
||||
"description": "Provides blowfish encryption/decryption for PHP string data."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_Date",
|
||||
@ -2527,7 +2783,7 @@
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"description": "A library that wraps various backends providing IDNA (Internationalized Domain Names in Applications) support."
|
||||
"description": "Normalized access to various backends providing IDNA (Internationalized Domain Names in Applications) support."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_Imap_Client",
|
||||
@ -2565,7 +2821,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library to access IMAP4rev1 (RFC 3501) mail servers. Also supports connections to POP3 (STD 53/RFC 1939)."
|
||||
"description": "Interface to access IMAP4rev1 (RFC 3501) mail servers. Also supports connections to POP3 (STD 53/RFC 1939)."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_ListHeaders",
|
||||
@ -2845,7 +3101,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library that provides an abstract PHP network socket client."
|
||||
"description": "Provides abstract class for use in creating PHP network socket clients."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_Stream",
|
||||
@ -2986,7 +3242,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library that provides a text-based diff engine and renderers for multiple diff output formats."
|
||||
"description": "A text-based diff engine and renderers for multiple diff output formats."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_Text_Flowed",
|
||||
@ -3014,7 +3270,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library that provides common methods for manipulating text using the encoding described in RFC 3676 ('flowed' text)."
|
||||
"description": "The Horde_Text_Flowed:: class provides common methods for manipulating text using the encoding described in RFC 3676 ('flowed' text)."
|
||||
},
|
||||
{
|
||||
"name": "pear-pear.horde.org/Horde_Translation",
|
||||
@ -3097,7 +3353,7 @@
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"description": "A library that provides functionality useful for all kind of applications."
|
||||
"description": "These classes provide functionality useful for all kind of applications."
|
||||
},
|
||||
{
|
||||
"name": "pear/archive_tar",
|
||||
@ -3784,12 +4040,6 @@
|
||||
}
|
||||
],
|
||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/synchro",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-05-27T12:24:03+00:00"
|
||||
},
|
||||
{
|
||||
@ -3882,20 +4132,6 @@
|
||||
"x.509",
|
||||
"x509"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/terrafrost",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpseclib",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-04T23:17:33+00:00"
|
||||
},
|
||||
{
|
||||
@ -6110,20 +6346,6 @@
|
||||
],
|
||||
"description": "Symfony Config Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-27T16:54:36+00:00"
|
||||
},
|
||||
{
|
||||
@ -6181,20 +6403,6 @@
|
||||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-10T07:47:39+00:00"
|
||||
},
|
||||
{
|
||||
@ -6268,20 +6476,6 @@
|
||||
],
|
||||
"description": "Symfony DependencyInjection Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-30T10:09:30+00:00"
|
||||
},
|
||||
{
|
||||
@ -6339,20 +6533,6 @@
|
||||
],
|
||||
"description": "Symfony ErrorHandler Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-17T09:56:45+00:00"
|
||||
},
|
||||
{
|
||||
@ -6423,20 +6603,6 @@
|
||||
],
|
||||
"description": "Symfony EventDispatcher Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-13T14:18:44+00:00"
|
||||
},
|
||||
{
|
||||
@ -6499,20 +6665,6 @@
|
||||
"interoperability",
|
||||
"standards"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-06T13:19:58+00:00"
|
||||
},
|
||||
{
|
||||
@ -6563,20 +6715,6 @@
|
||||
],
|
||||
"description": "Symfony Filesystem Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-27T16:54:36+00:00"
|
||||
},
|
||||
{
|
||||
@ -6632,20 +6770,6 @@
|
||||
],
|
||||
"description": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-17T07:39:58+00:00"
|
||||
},
|
||||
{
|
||||
@ -6737,20 +6861,6 @@
|
||||
],
|
||||
"description": "Symfony HttpKernel Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-09-02T08:09:29+00:00"
|
||||
},
|
||||
{
|
||||
@ -6813,20 +6923,6 @@
|
||||
"mime",
|
||||
"mime-type"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-17T09:56:45+00:00"
|
||||
},
|
||||
{
|
||||
@ -6889,20 +6985,6 @@
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -6974,20 +7056,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-04T06:02:08+00:00"
|
||||
},
|
||||
{
|
||||
@ -7055,20 +7123,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -7132,20 +7186,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -7265,20 +7305,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -7338,20 +7364,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -7414,20 +7426,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -7494,20 +7492,6 @@
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-14T12:35:20+00:00"
|
||||
},
|
||||
{
|
||||
@ -7636,20 +7620,6 @@
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-30T11:41:10+00:00"
|
||||
},
|
||||
{
|
||||
@ -7785,20 +7755,6 @@
|
||||
"debug",
|
||||
"dump"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-17T07:31:35+00:00"
|
||||
},
|
||||
{
|
||||
@ -7858,20 +7814,6 @@
|
||||
],
|
||||
"description": "Symfony Yaml Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-30T11:41:10+00:00"
|
||||
},
|
||||
{
|
||||
@ -8257,6 +8199,58 @@
|
||||
],
|
||||
"time": "2019-10-21T16:45:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "grale/webdav",
|
||||
"version": "v0.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gletournel/WebDAV.git",
|
||||
"reference": "c4d592e90f68806e491544d780fb44c78e3961cb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/gletournel/WebDAV/zipball/c4d592e90f68806e491544d780fb44c78e3961cb",
|
||||
"reference": "c4d592e90f68806e491544d780fb44c78e3961cb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzle/http": "~3.0",
|
||||
"php": ">=5.3.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~3.7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"monolog/monolog": "Adds support for logging HTTP requests and responses",
|
||||
"symfony/finder": "Allows you to more easily filter the files that the stream wrapper returns"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Grale\\WebDav": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Geoffroy Letournel",
|
||||
"email": "geoffroy.letournel@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A simple PHP WebDAV client and stream wrapper",
|
||||
"homepage": "https://github.com/gletournel/WebDAV",
|
||||
"keywords": [
|
||||
"WebDAV",
|
||||
"php",
|
||||
"stream",
|
||||
"wrapper"
|
||||
],
|
||||
"time": "2017-09-26T13:31:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.5.2",
|
||||
@ -9857,12 +9851,14 @@
|
||||
"egroupware/smallpart": 20,
|
||||
"egroupware/status": 20,
|
||||
"egroupware/swoolepush": 20,
|
||||
"egroupware/tracker": 20
|
||||
"egroupware/tracker": 20,
|
||||
"egroupware/guzzlestream": 20,
|
||||
"egroupware/webdav": 20
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.2,<=8.0.0alpha1",
|
||||
"php": ">=7.3,<=8.0.0alpha1",
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mysqli": "*",
|
||||
@ -9874,7 +9870,6 @@
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.2"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"php": "7.3"
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ class filemanager_hooks
|
||||
{
|
||||
foreach (Api\Hooks::process('filemanager-editor-link', 'collabora') as $app => $link)
|
||||
{
|
||||
if ($link && ($access = \EGroupware\Api\Vfs\Links\StreamWrapper::check_app_rights($app)) &&
|
||||
if ($link && !empty($GLOBALS['egw_info']['user']['apps'][$app]) &&
|
||||
(empty($GLOBALS['egw_info']['user']['preferences']['filemanager']['document_doubleclick_action']) ||
|
||||
$GLOBALS['egw_info']['user']['preferences']['filemanager']['document_doubleclick_action'] == $app))
|
||||
{
|
||||
|
@ -46,6 +46,12 @@ class infolog_bo
|
||||
* @var boolean
|
||||
*/
|
||||
var $log = false;
|
||||
|
||||
/**
|
||||
* Access permission cache for current user
|
||||
*/
|
||||
protected static $access_cache = array();
|
||||
|
||||
/**
|
||||
* Cached timezone data
|
||||
*
|
||||
@ -330,15 +336,13 @@ class infolog_bo
|
||||
*/
|
||||
function check_access($info,$required_rights,$other=0,$user=null)
|
||||
{
|
||||
static $cache = array();
|
||||
|
||||
$info_id = is_array($info) ? $info['info_id'] : $info;
|
||||
|
||||
if (!$user) $user = $this->user;
|
||||
if ($user == $this->user)
|
||||
{
|
||||
$grants = $this->grants;
|
||||
if ($info_id) $access =& $cache[$info_id][$required_rights]; // we only cache the current user!
|
||||
if ($info_id) $access =& static::$access_cache[$info_id][$required_rights]; // we only cache the current user!
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -410,6 +414,7 @@ class infolog_bo
|
||||
*/
|
||||
function init()
|
||||
{
|
||||
static::$access_cache = array();
|
||||
$this->so->init();
|
||||
}
|
||||
|
||||
|
68
vfs-context-links.php
Normal file
68
vfs-context-links.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
|
||||
$GLOBALS['egw_info'] = [
|
||||
'flags' => [
|
||||
'currentapp' => 'login',
|
||||
],
|
||||
];
|
||||
require_once __DIR__.'/header-default.inc.php';
|
||||
$_SESSION = []; // reset session, specially cache for links
|
||||
$egw->session->create($sysop='ralf', '', '', true, false);
|
||||
|
||||
$so = new infolog_so();
|
||||
$so->delete(['info_id' => [1, 2]]);
|
||||
$infolog_sysop = $so->write(['info_id' => 1, 'info_owner' => 5, 'info_subject' => 'Test-InfoLog Ralf', 'info_type' => 'task'], 0, null, true);
|
||||
// anonymous user give not grants, has not shared groups with Ralf or sysop
|
||||
$infolog_anon = $so->write(['info_id' => 2, 'info_owner' => ($anon=Api\Accounts::getInstance()->name2id('anonymous')), 'info_subject' => 'Test-InfoLog Anonymous', 'info_type' => 'task'], 0, null, true);
|
||||
//var_dump($so->read(['info_id' => $infolog_sysop]), $so->read(['info_id' => $infolog_anon]));
|
||||
// anonymous user needs infolog run rights for further tests
|
||||
$acl = new Api\Acl($anon);
|
||||
$acl->add_repository('infolog', 'run', $anon, 1);
|
||||
|
||||
$schema = 'stylite.links'; //'links';
|
||||
Vfs::$is_root = true;
|
||||
Vfs::mount("$schema://default/apps", '/apps', false, false);
|
||||
Vfs::$is_root = false;
|
||||
var_dump(Vfs::mount());
|
||||
|
||||
$infolog_sysop_dir = "/apps/infolog/$infolog_sysop";
|
||||
$infolog_anon_dir = "/apps/infolog/$infolog_anon";
|
||||
var_dump(file_put_contents("vfs://default$infolog_sysop_dir/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::proppatch('$infolog_sysop_dir/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])=" . array2string(Vfs::proppatch("$infolog_sysop_dir/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])),
|
||||
"Vfs::propfind('$infolog_sysop_dir/test.txt')=" . json_encode(Vfs::propfind("$infolog_sysop_dir/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
var_dump($f = fopen("vfs://default$infolog_sysop_dir/test.txt", 'r'), fread($f, 100), fclose($f));
|
||||
|
||||
Vfs::$is_root = true;
|
||||
var_dump(file_put_contents("vfs://default$infolog_anon_dir/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::proppatch('$infolog_anon_dir/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])=" . array2string(Vfs::proppatch("$infolog_anon_dir/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])),
|
||||
"Vfs::propfind('$infolog_anon_dir/test.txt')=" . json_encode(Vfs::propfind("$infolog_anon_dir/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump(Vfs::mount(/*"$schema://anonymous@default$infolog_anon_dir"*/"vfs://anonymous@default$infolog_anon_dir", $share_dir = "/home/$sysop/anon-infolog", false, false));
|
||||
Vfs::$is_root = false;
|
||||
|
||||
var_dump(Vfs::mount());
|
||||
|
||||
var_dump("Vfs::resolve_url('$share_dir/test.txt')=" . Vfs::resolve_url("$share_dir/test.txt"));
|
||||
var_dump("Vfs::url_stat('$share_dir/test.txt')=" . json_encode(Vfs::stat("$share_dir/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::is_readable('$share_dir/test.txt')=" . json_encode(Vfs::is_readable("$share_dir/test.txt")));
|
||||
var_dump("fopen('vfs://default$share_dir/test.txt', 'r')", $f = fopen("vfs://default$share_dir/test.txt", 'r'), fread($f, 100), fclose($f));
|
||||
var_dump("Vfs::propfind('$share_dir/test.txt')", Vfs::propfind("$share_dir/test.txt"));
|
||||
var_dump("Vfs::proppatch('$share_dir/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something else']])=" . array2string(Vfs::proppatch("$share_dir/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something else']])),
|
||||
"Vfs::propfind('$share_dir/test.txt')=" . json_encode(Vfs::propfind("$share_dir/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
var_dump("Vfs::url_stat('$share_dir/test-dir')=" . json_encode(Vfs::stat("$share_dir/test-dir")));
|
||||
var_dump("Vfs::mkdir('$share_dir/test-dir')=" . json_encode(Vfs::mkdir("$share_dir/test-dir")));
|
||||
var_dump("Vfs::url_stat('$share_dir/test-dir')=" . json_encode(Vfs::stat("$share_dir/test-dir"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump(file_put_contents("vfs://default$share_dir/test-dir/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::url_stat('$share_dir/test-dir/test.txt')=" . json_encode(Vfs::stat("$share_dir/test-dir"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump(file_get_contents("vfs://default$share_dir/test-dir/test.txt"));
|
||||
var_dump("Vfs::unlink('$share_dir/test-dir/test.txt')=" . json_encode(Vfs::unlink("$share_dir/test-dir/test.txt")));
|
||||
var_dump("Vfs::rmdir('$share_dir/test-dir')=" . json_encode(Vfs::rmdir("$share_dir/test-dir")));
|
||||
var_dump("Vfs::url_stat('$share_dir/test-dir')=" . json_encode(Vfs::stat("$share_dir/test-dir")));
|
||||
|
||||
var_dump("Vfs::scandir('$share_dir')=" . json_encode(Vfs::scandir($share_dir), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::remove('$share_dir/test.txt')=" . json_encode(Vfs::remove("$share_dir/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::scandir('$share_dir')=" . json_encode(Vfs::scandir($share_dir), JSON_UNESCAPED_SLASHES));
|
72
vfs-context-share.php
Normal file
72
vfs-context-share.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
|
||||
$GLOBALS['egw_info'] = [
|
||||
'flags' => [
|
||||
'currentapp' => 'login',
|
||||
],
|
||||
];
|
||||
require_once __DIR__.'/header-default.inc.php';
|
||||
|
||||
$GLOBALS['egw_info']['user'] = [
|
||||
'account_id' => 5,
|
||||
'account_lid' => $sysop='ralf',
|
||||
];
|
||||
|
||||
$other = 'birgit';
|
||||
|
||||
$schema = 'sqlfs';//'stylite.versioning'; //'sqlfs';
|
||||
Vfs::$is_root = true;
|
||||
Vfs::mount("$schema://default/home", '/home', false, false);
|
||||
Vfs::$is_root = false;
|
||||
var_dump(Vfs::mount());
|
||||
//var_dump(Vfs::scandir('/home'));
|
||||
//var_dump(Vfs::find('/home', ['maxdepth' => 1]));
|
||||
//var_dump(Vfs::scandir("/home/$sysop"));
|
||||
|
||||
var_dump(file_put_contents("vfs://default/home/$sysop/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::proppatch('/home/$sysop/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])=".array2string(Vfs::proppatch("/home/$sysop/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])),
|
||||
"Vfs::propfind('/home/$sysop/test.txt')=".json_encode(Vfs::propfind("/home/$sysop/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
var_dump($f=fopen("vfs://default/home/$sysop/test.txt", 'r'), fread($f, 100), fclose($f));
|
||||
//var_dump(Vfs::find("/home/$sysop", ['maxdepth' => 1]));
|
||||
|
||||
Vfs::$is_root = true;
|
||||
var_dump(file_put_contents("vfs://default/home/$other/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::proppatch('/home/$other/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])=".array2string(Vfs::proppatch("/home/$other/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])),
|
||||
"Vfs::propfind('/home/$other/test.txt')=".json_encode(Vfs::propfind("/home/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
$backup = Vfs::$user; Vfs::$user = Api\Accounts::getInstance()->name2id($other);
|
||||
$share = stylite_sharing::create("/home/$other", stylite_sharing::WRITABLE, '', $sysop);
|
||||
Vfs::$user = $backup;
|
||||
var_dump($share);
|
||||
var_dump(Vfs::mount($sharing_url="sharing://$share[share_token]@default/", "/home/$sysop/$other", false, false));
|
||||
Vfs::$is_root = false;
|
||||
|
||||
var_dump(Vfs::mount());
|
||||
|
||||
var_dump(Vfs::load_wrapper('sharing'));
|
||||
var_dump(stat($sharing_url.'test.txt'));
|
||||
var_dump(file_get_contents($sharing_url.'test.txt'));
|
||||
|
||||
var_dump("Vfs::resolve_url('/home/$sysop/$other/test.txt')=".Vfs::resolve_url("/home/$sysop/$other/test.txt"));
|
||||
var_dump("Vfs::stat('/home/$sysop/$other/test.txt')=".json_encode(Vfs::stat("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::is_readable('/home/$sysop/$other/test.txt')=".json_encode(Vfs::is_readable("/home/$sysop/$other/test.txt")));
|
||||
var_dump("fopen('vfs://default/home/$sysop/$other/test.txt', 'r')", $f=fopen("vfs://default/home/$sysop/$other/test.txt", 'r'), fread($f, 100), fclose($f));
|
||||
|
||||
var_dump("Vfs::propfind('/home/$sysop/$other/test.txt')", Vfs::propfind("/home/$sysop/$other/test.txt"));
|
||||
var_dump("Vfs::proppatch('/home/$sysop/$other/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something else']])=".array2string(Vfs::proppatch("/home/$sysop/$other/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something else']])),
|
||||
"Vfs::propfind('/home/$sysop/$other/test.txt')=".json_encode(Vfs::propfind("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
var_dump("Vfs::url_stat('/home/$sysop/$other/test-dir')=".json_encode(Vfs::stat("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::mkdir('/home/$sysop/$other/test-dir')=".json_encode(Vfs::mkdir("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::scandir('/home/$sysop/$other')=".json_encode(Vfs::scandir("/home/$sysop/$other"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::stat('/home/$sysop/$other/test-dir')=".json_encode(Vfs::stat("/home/$sysop/$other/test-dir"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::rmdir('/home/$sysop/$other/test-dir')=".json_encode(Vfs::rmdir("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::stat('/home/$sysop/$other/test-dir')=".json_encode(Vfs::stat("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::remove('/home/$sysop/$other/test.txt')=".json_encode(Vfs::remove("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::stat('/home/$sysop/$other/test.txt')=".json_encode(Vfs::stat("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::scandir('/home/$sysop/$other')=".json_encode(Vfs::scandir("/home/$sysop/$other"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
stylite_sharing::delete($share['share_id']);
|
40
vfs-context-webdav.php
Normal file
40
vfs-context-webdav.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use EGroupware\Api\Vfs;
|
||||
use Grale\WebDav;
|
||||
|
||||
$GLOBALS['egw_info'] = [
|
||||
'flags' => [
|
||||
'currentapp' => 'login',
|
||||
],
|
||||
];
|
||||
require_once __DIR__.'/header-default.inc.php';
|
||||
|
||||
$GLOBALS['egw_info']['user'] = [
|
||||
'account_id' => 5,
|
||||
'account_lid' => $sysop='ralf',
|
||||
];
|
||||
|
||||
$other = 'birgit';
|
||||
|
||||
WebDav\StreamWrapper::register();
|
||||
|
||||
var_dump(file_put_contents("vfs://default/home/$sysop/test.txt", "Just a test ;)\n"));
|
||||
|
||||
$base = "webdavs://$sysop:secret@boulder.egroupware.org/egroupware/webdav.php";
|
||||
var_dump(scandir("$base/home"));
|
||||
|
||||
Vfs::$is_root = true;
|
||||
Vfs::mount("$base/home/$sysop", "/home/$sysop/webdav", false, false);
|
||||
Vfs::$is_root = false;
|
||||
var_dump(Vfs::mount());
|
||||
var_dump(Vfs::scandir("/home/$sysop/webdav"));
|
||||
var_dump(file_get_contents("vfs://default/home/$sysop/webdav/test.txt"));
|
||||
var_dump(Vfs::find("/home/$sysop/webdav", ['maxdepth' => 1], true));
|
||||
//var_dump(Vfs::scandir("/home/$sysop"));
|
||||
|
||||
var_dump(scandir($share = "webdavs://pole.egroupware.org/egroupware/share.php/c2nqd6plwiTT8ha6U22sZXsLc7vkVdM3"));
|
||||
Vfs::$is_root = true;
|
||||
Vfs::mount("$share", "/home/$sysop/shares/PressRelease-20.1", false, false);
|
||||
Vfs::$is_root = false;
|
||||
var_dump(Vfs::find("/home/$sysop/shares/PressRelease-20.1", ['maxdepth' => 1], true));
|
66
vfs-context.php
Normal file
66
vfs-context.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
use EGroupware\Api\Vfs;
|
||||
|
||||
$GLOBALS['egw_info'] = [
|
||||
'flags' => [
|
||||
'currentapp' => 'login',
|
||||
],
|
||||
];
|
||||
require_once __DIR__.'/header-default.inc.php';
|
||||
|
||||
$GLOBALS['egw_info']['user'] = [
|
||||
'account_id' => 5,
|
||||
'account_lid' => $sysop='ralf',
|
||||
];
|
||||
|
||||
$other = 'birgit';
|
||||
|
||||
$schema = 'stylite.versioning'; //'sqlfs';
|
||||
Vfs::$is_root = true;
|
||||
Vfs::mount("$schema://default/home", '/home', false, false);
|
||||
Vfs::$is_root = false;
|
||||
var_dump(Vfs::mount());
|
||||
var_dump(Vfs::scandir('/home'));
|
||||
var_dump(Vfs::find('/home', ['maxdepth' => 1]));
|
||||
//var_dump(Vfs::scandir("/home/$sysop"));
|
||||
|
||||
Vfs::$is_root = true;
|
||||
Vfs::mount('filesystem://default/var/lib/egroupware', "/home/$other/something", false, false);
|
||||
Vfs::$is_root = false;
|
||||
var_dump(Vfs::stat("/home/$other/something"));
|
||||
|
||||
var_dump(file_put_contents("vfs://default/home/$sysop/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::proppatch('/home/$sysop/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])=".array2string(Vfs::proppatch("/home/$sysop/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])),
|
||||
"Vfs::propfind('/home/$sysop/test.txt')=".json_encode(Vfs::propfind("/home/$sysop/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
var_dump($f=fopen("vfs://default/home/$sysop/test.txt", 'r'), fread($f, 100), fclose($f));
|
||||
//var_dump(Vfs::find("/home/$sysop", ['maxdepth' => 1]));
|
||||
|
||||
Vfs::$is_root = true;
|
||||
var_dump(file_put_contents("vfs://default/home/$other/test.txt", "Just a test ;)\n"));
|
||||
var_dump("Vfs::proppatch('/home/$other/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])=".array2string(Vfs::proppatch("/home/$other/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']])),
|
||||
"Vfs::propfind('/home/$other/test.txt')=".json_encode(Vfs::propfind("/home/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump(Vfs::mount("vfs://$other@default/home/$other", "/home/$sysop/$other", false, false));
|
||||
Vfs::$is_root = false;
|
||||
|
||||
var_dump(Vfs::mount());
|
||||
|
||||
var_dump("Vfs::resolve_url('/home/$sysop/$other/test.txt')=".Vfs::resolve_url("/home/$sysop/$other/test.txt"));
|
||||
var_dump("Vfs::url_stat('/home/$sysop/$other/test.txt')=".json_encode(Vfs::stat("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::is_readable('/home/$sysop/$other/test.txt')=".json_encode(Vfs::is_readable("/home/$sysop/$other/test.txt")));
|
||||
var_dump("fopen('vfs://default/home/$sysop/$other/test.txt', 'r')", $f=fopen("vfs://default/home/$sysop/$other/test.txt", 'r'), fread($f, 100), fclose($f));
|
||||
var_dump("Vfs::propfind('/home/$sysop/$other/test.txt')", Vfs::propfind("/home/$sysop/$other/test.txt"));
|
||||
var_dump("Vfs::proppatch('/home/$sysop/$other/test.txt', [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something else']])=".array2string(Vfs::proppatch("/home/$sysop/$other/test.txt", [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something else']])),
|
||||
"Vfs::propfind('/home/$sysop/$other/test.txt')=".json_encode(Vfs::propfind("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
|
||||
|
||||
var_dump("Vfs::url_stat('/home/$sysop/$other/test-dir')=".json_encode(Vfs::stat("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::mkdir('/home/$sysop/$other/test-dir')=".json_encode(Vfs::mkdir("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::url_stat('/home/$sysop/$other/test-dir')=".json_encode(Vfs::stat("/home/$sysop/$other/test-dir"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::rmdir('/home/$sysop/$other/test-dir')=".json_encode(Vfs::rmdir("/home/$sysop/$other/test-dir")));
|
||||
var_dump("Vfs::url_stat('/home/$sysop/$other/test-dir')=".json_encode(Vfs::stat("/home/$sysop/$other/test-dir")));
|
||||
|
||||
var_dump("Vfs::scandir('/home/$sysop/$other')=".json_encode(Vfs::scandir("/home/$sysop/$other"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::remove('/home/$sysop/$other/test.txt')=".json_encode(Vfs::remove("/home/$sysop/$other/test.txt"), JSON_UNESCAPED_SLASHES));
|
||||
var_dump("Vfs::scandir('/home/$sysop/$other')=".json_encode(Vfs::scandir("/home/$sysop/$other"), JSON_UNESCAPED_SLASHES));
|
Loading…
Reference in New Issue
Block a user