diff --git a/api/src/Vfs/Dav/Directory.php b/api/src/Vfs/Dav/Directory.php new file mode 100644 index 0000000000..6146711081 --- /dev/null +++ b/api/src/Vfs/Dav/Directory.php @@ -0,0 +1,93 @@ + + * @copyright (c) 2015 by Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Vfs\Dav; + +use Sabre\DAV; +use EGroupware\Api\Vfs; + +/** + * VFS directory for use with SabreDAV + */ +class Directory extends DAV\FS\Directory +{ + /** + * VFS path without prefix / vfs schema + * + * @var string + */ + protected $vfs_path; + + /** + * Constructor + * + * @param string $path vfs path without prefix + */ + function __construct($path) + { + //error_log(__METHOD__."('$path')"); + $this->vfs_path = rtrim($path, '/'); + + parent::__construct(Vfs::PREFIX.$path); + } + + /** + * Returns the name of the node + * + * We override this method to remove url-decoding required by EGroupware VFS + * + * @return string + */ + function getName() + { + return Vfs::decodePath(parent::getName()); + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) + { + //error_log(__METHOD__."('$name') this->path=$this->path, this->vfs_path=$this->vfs_path"); + $path = $this->vfs_path . '/' . $name; + $vfs_path = $this->vfs_path . '/' . Vfs::encodePathComponent($name); + + if (!Vfs::file_exists($vfs_path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); + + if (Vfs::is_dir($vfs_path)) + { + return new Directory($vfs_path); + } + else + { + return new File($vfs_path); + } + } + + /** + * Returns available diskspace information + * + * @return array [ available-space, free-space ] + */ + function getQuotaInfo() + { + return [ false, false ]; + } +} diff --git a/api/src/Vfs/Dav/File.php b/api/src/Vfs/Dav/File.php new file mode 100644 index 0000000000..78a52d00b9 --- /dev/null +++ b/api/src/Vfs/Dav/File.php @@ -0,0 +1,87 @@ + + * @copyright (c) 2015 by Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Vfs\Dav; + +use Sabre\DAV; +use EGroupware\Api\Vfs; + + +/** + * VFS file for use with SabreDAV + */ +class File extends DAV\FS\File +{ + /** + * VFS path without prefix / vfs schema + * + * @var string + */ + protected $vfs_path; + + /** + * Constructor + * + * @param string $path vfs path without prefix + */ + function __construct($path) + { + //error_log(__METHOD__."('$path')"); + $this->vfs_path = $path; + + parent::__construct(Vfs::PREFIX.$path); + } + + /** + * Returns the name of the node + * + * We override this method to remove url-decoding required by EGroupware VFS + * + * @return string + */ + function getName() + { + return Vfs::decodePath(parent::getName()); + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return mixed + */ + function getETag() + { + if (($stat = Vfs::url_stat($this->vfs_path, STREAM_URL_STAT_QUIET))) + { + return '"'.$stat['ino'].':'.$stat['mtime'].':'.$stat['size'].'"'; + } + return null; + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + function getContentType() + { + return Vfs::mime_content_type($this->vfs_path); + } +} \ No newline at end of file diff --git a/api/src/Vfs/Sqlfs/StreamWrapper.php b/api/src/Vfs/Sqlfs/StreamWrapper.php index 739f4c6f34..3f5a0ee4b2 100644 --- a/api/src/Vfs/Sqlfs/StreamWrapper.php +++ b/api/src/Vfs/Sqlfs/StreamWrapper.php @@ -562,7 +562,11 @@ class StreamWrapper implements Vfs\StreamWrapperIface $path = Vfs::parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,STREAM_URL_STAT_LINK)) || !Vfs::check_access(Vfs::dirname($path),Vfs::WRITABLE, $parent_stat)) + // need to get parent stat from Sqlfs, not Vfs + if (!isset($parent_stat)) $parent_stat = static::url_stat(Vfs::dirname($path), STREAM_URL_STAT_LINK); + + if (!$parent_stat || !($stat = self::url_stat($path,STREAM_URL_STAT_LINK)) || + !Vfs::check_access(Vfs::dirname($path),Vfs::WRITABLE, $parent_stat)) { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url) permission denied!"); @@ -793,7 +797,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface $parent = Vfs::dirname($path); if (!($stat = self::url_stat($path,0)) || $stat['mime'] != self::DIR_MIME_TYPE || - !Vfs::check_access($parent,Vfs::WRITABLE)) + !Vfs::check_access($parent, Vfs::WRITABLE, static::url_stat($parent,0))) { self::_remove_password($url); $err_msg = __METHOD__."($url,$options) ".(!$stat ? 'not found!' : diff --git a/phpgwapi/inc/class.egw_sharing.inc.php b/phpgwapi/inc/class.egw_sharing.inc.php index 037b6bbb3d..278ea4178d 100644 --- a/phpgwapi/inc/class.egw_sharing.inc.php +++ b/phpgwapi/inc/class.egw_sharing.inc.php @@ -132,10 +132,22 @@ class egw_sharing /** * Create sharing session * - * @param boolean $keep_session =false false: create a new session, true: try mounting it into existing (already verified) 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 + * --> 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 * @return string with sessionid, does NOT return if no session created */ - public static function create_session($keep_session=false) + public static function create_session($keep_session=null) { self::$db = $GLOBALS['egw']->db; @@ -197,39 +209,14 @@ class egw_sharing { $share['share_root'] = '/'.$share['share_token']; - // if current user is not the share owner, we need to give him access to mounted share + // if current user is not the share owner, we cant just mount share if (egw_vfs::$user != $share['share_owner']) { - // check if sharing user has owner rights for shared path - egw_vfs::$user = $share['share_owner']; - egw_vfs::clearstatcache(); - if (egw_vfs::has_owner_rights($share['share_path'])) - { - $rights = $share['share_writable'] && egw_vfs::is_writable($share['share_path']) ? 7 : 5; - egw_vfs::$user = $GLOBALS['egw']->session->account_id; - egw_vfs::eacl($share['share_root'], $rights, egw_vfs::$user, true); // true = session-only, not permanent - } - // if not, we must not use an eacl, as it grants recursive rights! - // (one could eg. create a writable share for / and use it to escalate his own rights) - // --> create a new session with propper rights (loosing current session) - else - { - $keep_session = false; - } + $keep_session = false; } } if (!$keep_session) // do NOT change to else, as we might have set $keep_session=false! { - // create session without checking auth: create(..., false, false) - if (!($sessionid = $GLOBALS['egw']->session->create('anonymous', '', 'text', false, false))) - { - sleep(1); - $status = '500 Internal Server Error'; - header("HTTP/1.1 $status"); - header("X-WebDAV-Status: $status", true); - echo "Failed to create session: ".$GLOBALS['egw']->session->reason."\n"; - common::egw_exit(); - } // only allow filemanager app $GLOBALS['egw_info']['user']['apps'] = array( 'filemanager' => $GLOBALS['egw_info']['apps']['filemanager'] @@ -266,8 +253,34 @@ class egw_sharing // store sharing object in egw object and therefore in session $GLOBALS['egw']->sharing = new egw_sharing($share); - // for an existing session we need to store modified egw and egw_info again in session - if ($keep_session) + // we have a session we want to keep, but share owner is different from current user and we need filemanager UI, or no session + // --> create a new anon session + if ($keep_session === false && $GLOBALS['egw']->sharing->use_filemanager() || is_null($keep_session)) + { + // create session without checking auth: create(..., false, false) + if (!($sessionid = $GLOBALS['egw']->session->create('anonymous', '', 'text', false, false))) + { + sleep(1); + $status = '500 Internal Server Error'; + header("HTTP/1.1 $status"); + header("X-WebDAV-Status: $status", true); + echo "Failed to create session: ".$GLOBALS['egw']->session->reason."\n"; + common::egw_exit(); + } + // only allow filemanager app (gets overwritten by session::create) + $GLOBALS['egw_info']['user']['apps'] = array( + 'filemanager' => $GLOBALS['egw_info']['apps']['filemanager'] + ); + } + // 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 + elseif ($keep_session === false) + { + $GLOBALS['egw']->session->commit_session(); + } + + // update modified egw and egw_info again in session, if neccessary + if ($keep_session || $sessionid) { $_SESSION[egw_session::EGW_INFO_CACHE] = $GLOBALS['egw_info']; unset($_SESSION[egw_session::EGW_INFO_CACHE]['flags']); // dont save the flags, they change on each request @@ -278,6 +291,22 @@ class egw_sharing return $sessionid; } + /** + * Check if we use filemanager UI + * + * Only for directories, if browser supports it and filemanager is installed + * + * @return boolean + */ + public function use_filemanager() + { + return !(!egw_vfs::is_dir($this->share['share_root']) || $_SERVER['REQUEST_METHOD'] != 'GET' || + // or unsupported browsers like ie < 10 + html::$user_agent == 'msie' && html::$ua_version < 10.0 || + // or if no filemanager installed (WebDAV has own autoindex) + !file_exists(__DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php')); + } + /** * Server a request on a share specified in REQUEST_URI */ @@ -291,11 +320,7 @@ class egw_sharing return $GLOBALS['egw']->sharing->ServeRequest(); } // use pure WebDAV for everything but GET requests to directories - if (!egw_vfs::is_dir($this->share['share_root']) || $_SERVER['REQUEST_METHOD'] != 'GET' || - // or unsupported browsers like ie < 10 - html::$user_agent == 'msie' && html::$ua_version < 10.0 || - // or if no filemanager installed (WebDAV has own autoindex) - !file_exists(__DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php')) + if (!$this->use_filemanager()) { // send a content-disposition header, so browser knows how to name downloaded file if (!egw_vfs::is_dir($this->share['share_root'])) diff --git a/phpgwapi/inc/class.vfs_webdav_server.inc.php b/phpgwapi/inc/class.vfs_webdav_server.inc.php index 36c31e9fea..48762610a9 100644 --- a/phpgwapi/inc/class.vfs_webdav_server.inc.php +++ b/phpgwapi/inc/class.vfs_webdav_server.inc.php @@ -90,7 +90,9 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem { // recursive delete the directory try { - $ret = egw_vfs::remove($options['path']) && !file_exists($path); + $deleted = egw_vfs::remove($options['path']); + $ret = !empty($deleted[$options['path']]); + //error_log(__METHOD__."() egw_vfs::remove($options[path]) returned ".array2string($deleted)." --> ".array2string($ret)); } catch (Exception $e) { return '403 Forbidden: '.$e->getMessage(); @@ -129,7 +131,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem return "403 Forbidden"; } - if ( file_exists($parent."/".$name) ) { + if ( file_exists($path) ) { return "405 Method not allowed"; } @@ -137,7 +139,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem return "415 Unsupported media type"; } - $stat = mkdir($parent."/".$name, 0777); + $stat = mkdir($path, 0777); if (!$stat) { return "403 Forbidden"; }