mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-16 21:13:16 +01:00
322 lines
12 KiB
PHP
322 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* eGroupWare API: VFS - stream wrapper for linked files
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package api
|
|
* @subpackage vfs
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @copyright (c) 2008-14 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @version $Id: class.sqlfs_stream_wrapper.inc.php 24997 2008-03-02 21:44:15Z ralfbecker $
|
|
*/
|
|
|
|
/**
|
|
* Define parent for links_stream_wrapper, if not already defined
|
|
*
|
|
* Allows to base links_stream_wrapper on an other wrapper
|
|
*/
|
|
if (!class_exists('links_stream_wrapper_parent',false))
|
|
{
|
|
class links_stream_wrapper_parent extends sqlfs_stream_wrapper {}
|
|
}
|
|
|
|
/**
|
|
* EGroupware API: stream wrapper for linked files
|
|
*
|
|
* The files stored by the sqlfs_stream_wrapper in a /apps/$app/$id directory
|
|
*
|
|
* The links stream wrapper extends the sqlfs one, to implement an own ACL based on the access
|
|
* of the entry the files are linked to.
|
|
*
|
|
* Applications can define a 'file_access' method in the link registry with the following signature:
|
|
*
|
|
* boolean function file_access(string $id,int $check,string $rel_path)
|
|
*
|
|
* If the do not implement such a function the title function is used to test if the user has
|
|
* at least read access to an entry, and if true full (write) access to the files is granted.
|
|
*
|
|
* Entry directories are always reported existing and empty, if not existing in sqlfs.
|
|
*
|
|
* The stream wrapper interface is according to the docu on php.net
|
|
*
|
|
* @link http://www.php.net/manual/en/function.stream-wrapper-register.php
|
|
*/
|
|
class links_stream_wrapper extends links_stream_wrapper_parent
|
|
{
|
|
/**
|
|
* Scheme / protocoll used for this stream-wrapper
|
|
*/
|
|
const SCHEME = 'links';
|
|
/**
|
|
* Prefix to predend to get an url from a path
|
|
*/
|
|
const PREFIX = 'links://default';
|
|
/**
|
|
* Base url to store links
|
|
*/
|
|
const BASEURL = 'links://default/apps';
|
|
/**
|
|
* Enable some debug output to the error_log
|
|
*/
|
|
const DEBUG = false;
|
|
|
|
/**
|
|
* Implements ACL based on the access of the user to the entry the files are linked to.
|
|
*
|
|
* @param string $url url to check
|
|
* @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable
|
|
* @return boolean
|
|
*/
|
|
static function check_extended_acl($url,$check)
|
|
{
|
|
if (egw_vfs::$is_root)
|
|
{
|
|
return true;
|
|
}
|
|
$path = parse_url($url,PHP_URL_PATH);
|
|
|
|
list(,$apps,$app,$id,$rel_path) = explode('/',$path,5);
|
|
|
|
if ($apps != 'apps')
|
|
{
|
|
$access = false; // no access to anything, but /apps
|
|
$what = '!= apps';
|
|
}
|
|
elseif (!$app)
|
|
{
|
|
$access = !($check & egw_vfs::WRITABLE); // always grant read access to /apps
|
|
$what = '!$app';
|
|
}
|
|
elseif(!isset($GLOBALS['egw_info']['user']['apps'][$app]))
|
|
{
|
|
$access = false; // user has no access to the $app application
|
|
$what = 'no app-rights';
|
|
}
|
|
elseif (!$id)
|
|
{
|
|
$access = true; // grant read&write access to /apps/$app
|
|
$what = 'app dir';
|
|
}
|
|
// allow applications to implement their own access control to the file storage
|
|
// otherwise use the title method to check if user has (at least read access) to the entry
|
|
// which gives him then read AND write access to the file store of the entry
|
|
else
|
|
{
|
|
// vfs & stream-wrapper use posix rights, egw_link::file_access uses EGW_ACL_{EDIT|READ}!
|
|
$required = $check & egw_vfs::WRITABLE ? EGW_ACL_EDIT : EGW_ACL_READ;
|
|
$access = egw_link::file_access($app,$id,$required,$rel_path,egw_vfs::$user);
|
|
$what = "from egw_link::file_access('$app',$id,$required,'$rel_path,".egw_vfs::$user.")";
|
|
}
|
|
if (self::DEBUG) error_log(__METHOD__."($url,$check) user=".egw_vfs::$user." ($what) ".($access?"access granted ($app:$id:$rel_path)":'no access!!!'));
|
|
return $access;
|
|
}
|
|
|
|
/**
|
|
* This method is called in response to stat() calls on the URL paths associated with the wrapper.
|
|
*
|
|
* Reimplemented from sqlfs, as we have to pass the value of check_extends_acl(), due to the lack of late static binding.
|
|
* And to return vcard for url /apps/addressbook/$id/.entry
|
|
*
|
|
* @param string $path
|
|
* @param int $flags holds additional flags set by the streams API. It can hold one or more of the following values OR'd together:
|
|
* - STREAM_URL_STAT_LINK For resources with the ability to link to other resource (such as an HTTP Location: forward,
|
|
* or a filesystem symlink). This flag specified that only information about the link itself should be returned,
|
|
* not the resource pointed to by the link.
|
|
* This flag is set in response to calls to lstat(), is_link(), or filetype().
|
|
* - STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this flag is not set,
|
|
* you are responsible for reporting errors using the trigger_error() function during stating of the path.
|
|
* stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper!
|
|
* @return array
|
|
*/
|
|
static function url_stat ( $url, $flags )
|
|
{
|
|
$eacl_check=self::check_extended_acl($url,egw_vfs::READABLE);
|
|
|
|
// return vCard as /.entry
|
|
if ( $eacl_check && substr($url,-7) == '/.entry' &&
|
|
(list($app) = array_slice(explode('/',$url),-3,1)) && $app === 'addressbook')
|
|
{
|
|
$ret = array(
|
|
'ino' => md5($url),
|
|
'name' => '.entry',
|
|
'mode' => self::MODE_FILE|egw_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)) && $eacl_check)
|
|
{
|
|
list(,/*$apps*/,/*$app*/,$id,$rel_path) = explode('/', parse_url($url, PHP_URL_PATH), 5);
|
|
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' => egw_vfs::DIR_MIME_TYPE,
|
|
);
|
|
}
|
|
}
|
|
if (self::DEBUG) error_log(__METHOD__."('$url', $flags) calling parent::url_stat(,,".array2string($eacl_check).') returning '.array2string($ret));
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Set or delete extended acl for a given path and owner (or delete them if is_null($rights)
|
|
*
|
|
* Reimplemented, to NOT call the sqlfs functions, as we dont allow to modify the ACL (defined by the apps)
|
|
*
|
|
* @param string $path string with path
|
|
* @param int $rights=null rights to set, or null to delete the entry
|
|
* @param int/boolean $owner=null owner for whom to set the rights, null for the current user, or false to delete all rights for $path
|
|
* @param int $fs_id=null fs_id to use, to not query it again (eg. because it's already deleted)
|
|
* @return boolean true if acl is set/deleted, false on error
|
|
*/
|
|
static function eacl($path,$rights=null,$owner=null,$fs_id=null)
|
|
{
|
|
unset($path, $rights, $owner, $fs_id); // not used, but required by function signature
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get all ext. ACL set for a path
|
|
*
|
|
* Reimplemented, to NOT call the sqlfs functions, as we dont allow to modify the ACL (defined by the apps)
|
|
*
|
|
* @param string $path
|
|
* @return array/boolean array with array('path'=>$path,'owner'=>$owner,'rights'=>$rights) or false if $path not found
|
|
*/
|
|
function get_eacl($path)
|
|
{
|
|
unset($path); // not used, but required by function signature
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* mkdir for links
|
|
*
|
|
* Reimplemented as we have no static late binding to allow the extended sqlfs to call our eacl and to set no default rights for entry dirs
|
|
*
|
|
* This method is called in response to mkdir() calls on URL paths associated with the wrapper.
|
|
*
|
|
* It should attempt to create the directory specified by path.
|
|
* In order for the appropriate error message to be returned, do not define this method if your wrapper does not support creating directories.
|
|
*
|
|
* @param string $path
|
|
* @param int $mode not used(!), we inherit 005 for /apps/$app and set 000 for /apps/$app/$id
|
|
* @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE, we allways use recursive!
|
|
* @return boolean TRUE on success or FALSE on failure
|
|
*/
|
|
static function mkdir($path,$mode,$options)
|
|
{
|
|
unset($mode); // not used, but required by function signature
|
|
|
|
if($path[0] != '/')
|
|
{
|
|
if (strpos($path,'?') !== false) $query = parse_url($path,PHP_URL_QUERY);
|
|
$path = parse_url($path,PHP_URL_PATH).($query ? '?'.$query : '');
|
|
}
|
|
list(,$apps,$app,$id) = explode('/',$path);
|
|
|
|
$ret = false;
|
|
if ($apps == 'apps' && $app && !$id || self::check_extended_acl($path,egw_vfs::WRITABLE)) // app directory itself is allways ok
|
|
{
|
|
$current_is_root = egw_vfs::$is_root; egw_vfs::$is_root = true;
|
|
$current_user = egw_vfs::$user; egw_vfs::$user = 0;
|
|
|
|
$ret = parent::mkdir($path,0,$options|STREAM_MKDIR_RECURSIVE);
|
|
if ($id) parent::chmod($path,0); // no other rights
|
|
|
|
egw_vfs::$user = $current_user;
|
|
egw_vfs::$is_root = $current_is_root;
|
|
}
|
|
//error_log(__METHOD__."($path,$mode,$options) apps=$apps, app=$app, id=$id: returning $ret");
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* This method is called immediately after your stream object is created.
|
|
*
|
|
* Reimplemented from sqlfs to ensure self::url_stat is called, to fill sqlfs stat cache with our eacl!
|
|
* And to return vcard for url /apps/addressbook/$id/.entry
|
|
*
|
|
* @param string $url URL that was passed to fopen() and that this object is expected to retrieve
|
|
* @param string $mode mode used to open the file, as detailed for fopen()
|
|
* @param int $options additional flags set by the streams API (or'ed together):
|
|
* - STREAM_USE_PATH If path is relative, search for the resource using the include_path.
|
|
* - STREAM_REPORT_ERRORS If this flag is set, you are responsible for raising errors using trigger_error() during opening of the stream.
|
|
* If this flag is not set, you should not raise any errors.
|
|
* @param string $opened_path full path of the file/resource, if the open was successfull and STREAM_USE_PATH was set
|
|
* @return boolean true if the ressource was opened successful, otherwise false
|
|
*/
|
|
function stream_open ( $url, $mode, $options, &$opened_path )
|
|
{
|
|
// the following call is necessary to fill sqlfs_stream_wrapper::$stat_cache, WITH the extendes ACL!
|
|
$stat = self::url_stat($url,0);
|
|
//error_log(__METHOD__."('$url', '$mode', $options) stat=".array2string($stat));
|
|
|
|
// return vCard as /.entry
|
|
if ($stat && $mode[0] == 'r' && substr($url,-7) === '/.entry' &&
|
|
(list($app) = array_slice(explode('/',$url),-3,1)) && $app === 'addressbook')
|
|
{
|
|
list($id) = array_slice(explode('/',$url),-2,1);
|
|
$name = md5($url);
|
|
$ab_vcard = new addressbook_vcal('addressbook','text/vcard');
|
|
if (!($GLOBALS[$name] =& $ab_vcard->getVCard($id)))
|
|
{
|
|
error_log(__METHOD__."('$url', '$mode', $options) addressbook_vcal::getVCard($id) returned false!");
|
|
return false;
|
|
}
|
|
//error_log(__METHOD__."('$url', '$mode', $options) addressbook_vcal::getVCard($id) returned ".$GLOBALS[$name]);
|
|
require_once(EGW_API_INC.'/class.global_stream_wrapper.inc.php');
|
|
$this->opened_stream = fopen('global://'.$name,'r');
|
|
unset($GLOBALS[$name]); // unset it, so it does not use up memory, once the stream is closed
|
|
return true;
|
|
}
|
|
// create not existing entry directories on the fly
|
|
if ($mode[0] != 'r' && !parent::url_stat($dir = egw_vfs::dirname($url),0) && self::check_extended_acl($dir,egw_vfs::WRITABLE))
|
|
{
|
|
self::mkdir($dir,0,STREAM_MKDIR_RECURSIVE);
|
|
}
|
|
return parent::stream_open($url,$mode,$options,$opened_path);
|
|
}
|
|
|
|
/**
|
|
* This method is called immediately when your stream object is created for examining directory contents with opendir().
|
|
*
|
|
* Reimplemented to give no error, if entry directory does not exist.
|
|
*
|
|
* @param string $path URL that was passed to opendir() and that this object is expected to explore.
|
|
* @param $options
|
|
* @return booelan
|
|
*/
|
|
function dir_opendir ( $url, $options )
|
|
{
|
|
if (!parent::url_stat($url, STREAM_URL_STAT_QUIET) && self::url_stat($url, STREAM_URL_STAT_QUIET))
|
|
{
|
|
$this->opened_dir = array();
|
|
return true;
|
|
}
|
|
return parent::dir_opendir($url, $options);
|
|
}
|
|
}
|
|
|
|
stream_register_wrapper(links_stream_wrapper::SCHEME ,'links_stream_wrapper');
|