forked from extern/egroupware
work in progress on sharing stream-wrapper
This commit is contained in:
parent
2881ca0849
commit
1f8a003d03
@ -2123,79 +2123,6 @@ class Vfs extends Vfs\Base
|
||||
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
|
||||
* @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 = 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;
|
||||
}
|
||||
$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=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
|
||||
*
|
||||
|
@ -498,8 +498,8 @@ class Base
|
||||
*
|
||||
* @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 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
|
||||
@ -527,7 +527,7 @@ class Base
|
||||
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 false;
|
||||
return $fail_silent === 'null' ? null : false;
|
||||
}
|
||||
$callback = [$instanciate ? new $class($url) : $class, $name];
|
||||
if (!is_array($pathes))
|
||||
|
162
api/src/Vfs/Sharing/StreamWrapper.php
Normal file
162
api/src/Vfs/Sharing/StreamWrapper.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$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'] ?? '');
|
||||
|
||||
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'] ? '' : '?ro=1');
|
||||
}
|
||||
catch (Api\Exception $e) {
|
||||
_egw_log_exception($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called in response to stat() calls on the URL paths associated with the wrapper.
|
||||
*
|
||||
* It should return as many elements in common with the system function as possible.
|
||||
* Unknown or unavailable values should be set to a rational value (usually 0).
|
||||
*
|
||||
* If you plan to use your wrapper in a require_once you need to define stream_stat().
|
||||
* If you plan to allow any other tests like is_file()/is_dir(), you have to define url_stat().
|
||||
* stream_stat() must define the size of the file, or it will never be included.
|
||||
* url_stat() must define mode, or is_file()/is_dir()/is_executable(), and any of those functions affected by clearstatcache() simply won't work.
|
||||
* It's not documented, but directories must be a mode like 040777 (octal), and files a mode like 0100666.
|
||||
* If you wish the file to be executable, use 7s instead of 6s.
|
||||
* The last 3 digits are exactly the same thing as what you pass to chmod.
|
||||
* 040000 defines a directory, and 0100000 defines a file.
|
||||
*
|
||||
* @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'], true);
|
||||
}
|
||||
return $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 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 (!isset($stat)) $stat = $this->url_stat($path, 0);
|
||||
|
||||
return $this->parent_check_access($path, $check, $stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store properties for a single ressource (file or dir)
|
||||
*
|
||||
* @param string $path string with path
|
||||
* @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)
|
||||
*/
|
||||
function proppatch($path,array $props)
|
||||
{
|
||||
if (!($url = self::resolve_url($path)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Vfs::proppatch($url, $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read properties for a ressource (file, dir or all files of a dir)
|
||||
*
|
||||
* @param array|string $path (array of) string with path
|
||||
* @param string $ns ='http://egroupware.org/' namespace if propfind should be limited to a single one, otherwise use null
|
||||
* @return array|boolean array with props (values for keys 'name', 'ns', 'val'), or path => array of props for is_array($path)
|
||||
* false if $path does not exist
|
||||
*/
|
||||
function propfind($path,$ns=self::DEFAULT_PROP_NAMESPACE)
|
||||
{
|
||||
if (!($url = self::resolve_url($path)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Vfs::propfind($url, $ns);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register __CLASS__ for self::SCHEMA
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
stream_wrapper_register(self::SCHEME, __CLASS__);
|
||||
}
|
||||
}
|
||||
|
||||
StreamWrapper::register();
|
@ -1,6 +1,6 @@
|
||||
<?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
|
||||
@ -16,7 +16,7 @@ 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.
|
||||
@ -25,7 +25,9 @@ use EGroupware\Api;
|
||||
*/
|
||||
class StreamWrapper extends Base implements StreamWrapperIface
|
||||
{
|
||||
use UserContextTrait;
|
||||
use UserContextTrait {
|
||||
check_access as parent_check_access;
|
||||
}
|
||||
|
||||
const PREFIX = 'vfs://default';
|
||||
|
||||
@ -109,6 +111,26 @@ class StreamWrapper extends Base implements StreamWrapperIface
|
||||
*/
|
||||
private $extra_dir_ptr;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
@ -672,7 +694,7 @@ class StreamWrapper extends Base implements StreamWrapperIface
|
||||
// we have no context, but $path is a URL with a valid user --> set it
|
||||
$this->check_set_context($path);
|
||||
|
||||
if (!($url = self::resolve_url($path,!($flags & STREAM_URL_STAT_LINK), $check_symlink_components)))
|
||||
if (!($url = static::resolve_url($path,!($flags & STREAM_URL_STAT_LINK), $check_symlink_components)))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path',$flags) can NOT resolve path!");
|
||||
return false;
|
||||
|
@ -43,8 +43,11 @@ trait UserContextTrait
|
||||
$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(is_string($url_or_context))
|
||||
{
|
||||
|
68
vfs-context-share.php
Normal file
68
vfs-context-share.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';
|
||||
|
||||
$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://$share[share_token]@default/", "/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));
|
||||
/* fails
|
||||
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));*/
|
||||
/* mkdir goes into infinit recursion
|
||||
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));
|
||||
|
||||
stylite_sharing::delete($share['share_id']);
|
Loading…
Reference in New Issue
Block a user