diff --git a/filemanager/webdav.php b/filemanager/webdav.php index b87ef90ac0..238fb6f8d2 100644 --- a/filemanager/webdav.php +++ b/filemanager/webdav.php @@ -1,49 +1,54 @@ - - * @copyright (c) 2006 by Ralf Becker - * @version $Id: class.boetemplate.inc.php 21437 2006-04-24 20:42:42Z ralfbecker $ - */ - -/** - * check if the given user has access - * - * Create a session or if the user has no account return authenticate header and 401 Unauthorized - * - * @param array &$account - * @return int session-id - */ -function check_access(&$account) -{ - $account = array( - 'login' => $_SERVER['PHP_AUTH_USER'], - 'passwd' => $_SERVER['PHP_AUTH_PW'], - 'passwd_type' => 'text', - ); - if (!($sessionid = $GLOBALS['egw']->session->create($account))) - { - header('WWW-Authenticate: Basic realm="eGroupWare WebDAV"'); - header("HTTP/1.1 401 Unauthorized"); - header("X-WebDAV-Status: 401 Unauthorized", true); - exit; - } - return $sessionid; -} - -$GLOBALS['egw_info']['flags'] = array( - 'disable_Template_class' => True, - 'noheader' => True, - 'currentapp' => 'filemanager', - 'autocreate_session_callback' => 'check_access', -); -// if you move this file somewhere else, you need to adapt the path to the header! -include('../header.inc.php'); - -ExecMethod('phpgwapi.vfs_webdav_server.ServeRequest'); + + * @copyright (c) 2006 by Ralf Becker + * @version $Id$ + */ + +/** + * check if the given user has access + * + * Create a session or if the user has no account return authenticate header and 401 Unauthorized + * + * @param array &$account + * @return int session-id + */ +function check_access(&$account) +{ + $account = array( + 'login' => $_SERVER['PHP_AUTH_USER'], + 'passwd' => $_SERVER['PHP_AUTH_PW'], + 'passwd_type' => 'text', + ); + if (!($sessionid = $GLOBALS['egw']->session->create($account))) + { + header('WWW-Authenticate: Basic realm="eGroupWare WebDAV"'); + header("HTTP/1.1 401 Unauthorized"); + header("X-WebDAV-Status: 401 Unauthorized", true); + exit; + } + return $sessionid; +} + +$GLOBALS['egw_info']['flags'] = array( + 'disable_Template_class' => True, + 'noheader' => True, + 'currentapp' => 'filemanager', + 'autocreate_session_callback' => 'check_access', +); +// if you move this file somewhere else, you need to adapt the path to the header! +include('../header.inc.php'); + +// only enable one of the following WebDAV server: +// 1. this uses the old webdav class, using the old vfs classes direct (1.4 and current default) +ExecMethod('phpgwapi.oldvfs_webdav_server.ServeRequest'); + +// 2. this uses the new streamwrapper VFS interface +//ExecMethod('phpgwapi.vfs_webdav_server.ServeRequest'); diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index 810bdca2af..e8113af73a 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -59,6 +59,9 @@ */ class egw_vfs extends vfs_stream_wrapper { + const EXECUTABLE = 4; + const READABLE = 2; + const WRITABLE = 1; /** * fopen working on just the eGW VFS * @@ -129,7 +132,7 @@ class egw_vfs extends vfs_stream_wrapper { throw new egw_exception_assertion_failed("File '$path' is not an absolute path!"); } - return self::url_stat($path); + return self::url_stat($path,0); } @@ -185,6 +188,96 @@ class egw_vfs extends vfs_stream_wrapper return true; } + /** + * 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 int $check=4 mode to check: 4 = read, 2 = write, 1 = executable + * @return boolean + */ + static function is_readable($path,$check = 4) + { + if (!($stat = self::stat($path))) + { + return false; + } + return self::check_access($stat,$check); + } + + /** + * 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 array $stat + * @param int $check mode to check: 4 = read, 2 = write, 1 = executable + * @return boolean + */ + static function check_access($stat,$check) + { + //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)"); + + if (!$stat) + { + //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!"); + return false; // file not found + } + // check if other rights grant access + if ($stat['mode'] & $check) + { + error_log(__METHOD__."(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)) && $stat['uid'] && $stat['uid'] == $GLOBALS['egw_info']['user']['account_id']) + { + //error_log(__METHOD__."(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)) && $stat['gid']) + { + static $memberships; + if (is_null($memberships)) + { + $memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'],true); + } + if (in_array(-abs($stat['gid']),$memberships)) + { + //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!"); + return true; + } + } + // here could be a check for extended acls ... + + //error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no access!"); + return false; + } + + /** + * 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 + * @return boolean + */ + static function is_writable($path) + { + return self::is_readable($path,2); + } + + /** + * 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 + * @return boolean + */ + static function is_executable($path) + { + return self::is_readable($path,1); + } + /** * Private constructor to prevent instanciating this class, only it's static methods should be used */ diff --git a/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php b/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php index 1adb2f3ae8..7fc5ec4b1e 100644 --- a/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.oldvfs_stream_wrapper.inc.php @@ -11,9 +11,6 @@ * @version $Id$ */ -require_once(EGW_API_INC.'/class.vfs_home.inc.php'); -require_once(EGW_API_INC.'/class.iface_stream_wrapper.inc.php'); - /** * eGroupWare API: VFS - old (until eGW 1.4 inclusive) VFS stream wrapper * @@ -27,14 +24,20 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper { /** * If this class should do the operations direct in the filesystem, instead of going through the vfs - * */ const USE_FILESYSTEM_DIRECT = true; /** * Mime type of directories, the old vfs uses 'Directory', while eg. WebDAV uses 'httpd/unix-directory' - * */ const DIR_MIME_TYPE = 'Directory'; + /** + * Scheme / protocoll used for this stream-wrapper + */ + const SCHEME = 'oldvfs'; + /** + * Does url_stat returns a mime type, or has it to be determined otherwise (string with attribute name) + */ + const STAT_RETURN_MIME_TYPE = 'mime'; /** * How much should be logged to the apache error-log * @@ -145,7 +148,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper 'operation' => EGW_ACL_ADD, ))) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file does not exist or can not be created!"); if (!($options & STREAM_URL_STAT_QUIET)) { @@ -164,7 +167,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper 'operation' => EGW_ACL_EDIT, ))) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) file can not be edited!"); if (!($options & STREAM_URL_STAT_QUIET)) { @@ -457,7 +460,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper 'must_exist' => true, )) || ($type = self::$old_vfs->file_type($data)) === self::DIR_MIME_TYPE) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url) (type=$type) permission denied!"); return false; // no permission or file does not exist } @@ -509,8 +512,8 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper 'operation' => EGW_ACL_ADD, ))) { - self::remove_password($url_from); - self::remove_password($url_to); + self::_remove_password($url_from); + self::_remove_password($url_to); if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $path_to permission denied!"); return false; // no permission or file does not exist } @@ -520,8 +523,8 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper ($type_to === self::DIR_MIME_TYPE) !== (self::$old_vfs->file_type($data_from) === self::DIR_MIME_TYPE)) { $is_dir = $type_to === self::DIR_MIME_TYPE ? 'a' : 'no'; - self::remove_password($url_from); - self::remove_password($url_to); + self::_remove_password($url_from); + self::_remove_password($url_to); if (self::LOG_LEVEL) error_log(__METHOD__."($url_to,$url_from) $path_to is $is_dir directory!"); return false; // no permission or file does not exist } @@ -576,7 +579,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper 'must_exist' => false, ))) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url) permission denied!"); if (!($options & STREAM_URL_STAT_QUIET)) { @@ -616,7 +619,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper 'must_exist' => true, )) || ($type = self::$old_vfs->file_type($data)) !== self::DIR_MIME_TYPE) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$options) (type=$type) permission denied!"); if (!($options & STREAM_URL_STAT_QUIET)) { @@ -628,7 +631,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper // our vfs deletes recursive, while the stream-wrapper interface does not! if (($files = self::$old_vfs->ls($data))) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."($url,$options) dir is not empty!"); if (!($options & STREAM_URL_STAT_QUIET)) { @@ -699,7 +702,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper count($files) == 1 && $path == $files[0]['directory'].'/'.$files[0]['name'] && $files[0]['mime_type'] != self::DIR_MIME_TYPE) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$options) $url is not directory!"); $this->opened_dir = null; return false; @@ -769,7 +772,7 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper //print_r($info); if (!$info) { - self::remove_password($url); + self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$flags) file or directory not found!"); return false; } @@ -848,12 +851,14 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper ($info['mime_type'] == self::DIR_MIME_TYPE ? 040070 : 0100060), 'size' => $info['size'], 'uid' => $info['owner_id'] > 0 ? $info['owner_id'] : 0, - 'gid' => $info['owner_id'] < 0 ? $info['owner_id'] : 0, + 'gid' => $info['owner_id'] < 0 ? -$info['owner_id'] : 0, 'mtime' => strtotime($info['modified'] ? $info['modified'] : $info['created']), 'ctime' => strtotime($info['created']), 'nlink' => $info['mime_type'] == self::DIR_MIME_TYPE ? 2 : 1, + // eGW addition to return the mime type + 'mime' => $info['mime_type'], ); - //print_r($stat); + //error_log(__METHOD__."($info[name]) = ".print_r($stat,true)); return $stat; } @@ -873,13 +878,13 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper return $func_overload & 2 ? mb_substr($str,$start,$length,'ascii') : substr($str,$start,$length); } - + /** * Replace the password of an url with '...' for error messages * * @param string &$url */ - static private function remove_password(&$url) + static private function _remove_password(&$url) { $parts = parse_url($url); @@ -891,4 +896,4 @@ class oldvfs_stream_wrapper implements iface_stream_wrapper } } -stream_register_wrapper('oldvfs','oldvfs_stream_wrapper'); +stream_register_wrapper(oldvfs_stream_wrapper::SCHEME ,'oldvfs_stream_wrapper'); diff --git a/phpgwapi/inc/class.oldvfs_webdav_server.inc.php b/phpgwapi/inc/class.oldvfs_webdav_server.inc.php new file mode 100644 index 0000000000..e57678627b --- /dev/null +++ b/phpgwapi/inc/class.oldvfs_webdav_server.inc.php @@ -0,0 +1,480 @@ + + * @copyright (c) 2006 by Ralf Becker + * @version $Id$ + */ + +require_once('HTTP/WebDAV/Server.php'); +require_once(EGW_API_INC.'/class.vfs_home.inc.php'); + +/** + * FileManger - WebDAV access + * + * Using the PEAR HTTP/WebDAV/Server class (which need to be installed!) + */ +class oldvfs_webdav_server extends HTTP_WebDAV_Server +{ + /** + * instance of the vfs class + * + * @var vfs_home + */ + var $vfs; + + var $dav_powered_by = 'eGroupWare WebDAV server'; + + /** + * Debug level: 0 = nothing, 1 = function calls, 2 = more info, eg. complete $_SERVER array + * + * The debug messages are send to the apache error_log + * + * @var integer + */ + var $debug = 0; + + function __construct() + { + if ($this->debug === 2) foreach($_SERVER as $name => $val) error_log("vfs_webdav_server: \$_SERVER[$name]='$val'"); + + parent::HTTP_WebDAV_Server(); + + $this->vfs =& new vfs_home; + } + + /** + * PROPFIND method handler + * + * @param array general parameter passing array + * @param array return array for file properties + * @return bool true on success + */ + function PROPFIND(&$options, &$files) + { + $vfs_data = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + // at first only list the given path itself + 'checksubdirs' => False, + 'nofiles' => True + ); + if (!($vfs_files = $this->vfs->ls($vfs_data))) // path not found + { + // check if the users home-dir is just not yet created (should be done by the vfs-class!) + // ToDo: group-dirs + if ($vfs_data['string'] == '/home/'.$GLOBALS['egw_info']['user']['account_lid']) + { + $this->vfs->override_acl = true; // user has no right to create dir in /home + $created = $this->vfs->mkdir(array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + )); + $this->vfs->override_acl = false; + + if (!$created) + { + if ($this->debug) error_log("vfs_webdav_server::PROPFIND(path='$options[path]',depth=$options[depth]) could not create home dir"); + } + $vfs_files = $this->vfs->ls($vfs_data); + } + if (!$vfs_files) + { + if ($this->debug) error_log("vfs_webdav_server::PROPFIND(path='$options[path]',depth=$options[depth]) return false (path not found)"); + return false; // path not found + } + } + // if depth > 0 and path is a directory => show it's contents + if (!empty($options['depth']) && $vfs_files[0]['mime_type'] == 'Directory') + { + $vfs_data['checksubdirs'] = (int) $options['depth'] != 1; + $vfs_data['nofiles'] = false; + + if ($vfs_files[0]['directory'] == '/') // sub-dirs of the root? + { + $vfs_files = array(); // dont return the directory, it shows up double in konq + } + else // return the dir itself with a trailing slash, otherwise empty dirs are reported as non-existent + { + $vfs_files[0]['name'] .= '/'; + } + $vfs_files = array_merge($vfs_files,$this->vfs->ls($vfs_data)); + } + if ($this->debug) error_log("vfs_webdav_server::PROPFIND(path='$options[path]',depth=$options[depth]) ".count($vfs_files).' files'); + + $files['files'] = array(); + $egw_charset = $GLOBALS['egw']->translation->charset(); + foreach($vfs_files as $fileinfo) + { + if ($this->debug) error_log('dir="'.$fileinfo['directory'].'", name="'.$fileinfo['name'].'": '.$fileinfo['mime_type']); + foreach(array('modified','created') as $date) + { + // our vfs has no modified set, if never modified, use created + list($y,$m,$d,$h,$i,$s) = split("[- :]",$fileinfo[$date] ? $fileinfo[$date] : $fileinfo['created']); + $fileinfo[$date] = mktime((int)$h,(int)$i,(int)$s,(int)$m,(int)$d,(int)$y); + } + $info = array( + 'path' => $GLOBALS['egw']->translation->convert($fileinfo['directory'].'/'.$fileinfo['name'],$egw_charset,'utf-8'), + 'props' => array( + $this->mkprop('displayname',$GLOBALS['egw']->translation->convert($fileinfo['name'],$egw_charset,'utf-8')), + $this->mkprop('creationdate',$fileinfo['created']), + $this->mkprop('getlastmodified',$fileinfo['modified']), + ), + ); + if ($fileinfo['mime_type'] == 'Directory') + { + $info['props'][] = $this->mkprop('resourcetype', 'collection'); + $info['props'][] = $this->mkprop('getcontenttype', 'httpd/unix-directory'); + } + else + { + $info['props'][] = $this->mkprop('resourcetype', ''); + $info['props'][] = $this->mkprop('getcontenttype', $fileinfo['mime_type']); + $info['props'][] = $this->mkprop('getcontentlength', $fileinfo['size']); + } + $files['files'][] = $info; + } + if ($this->debug == 2) foreach($files['files'] as $info) error_log(print_r($info,true)); + // ok, all done + return true; + } + + /** + * GET method handler + * + * @param array parameter passing array + * @return bool true on success + */ + function GET(&$options) + { + if ($this->debug) error_log('vfs_webdav_server::GET('.print_r($options,true).')'); + + $vfs_data = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + 'checksubdirs' => False, + 'nofiles' => True + ); + // sanity check + if (!($vfs_file = $this->vfs->ls($vfs_data))) + { + return false; + } + $options['mimetype'] = $vfs_file[0]['mime_type']; + $options['size'] = $vfs_file[0]['size']; + + if (($options['data'] = $this->vfs->read($vfs_data)) === false) + { + return '403 Forbidden'; // not sure if this is the right code for access denied + } + return true; + } + + /** + * PUT method handler + * + * @param array parameter passing array + * @return bool true on success + */ + function PUT(&$options) + { + if ($this->debug) error_log('vfs_webdav_server::PUT('.print_r($options,true).')'); + + $vfs_data = array( + 'string' => dirname($GLOBALS['egw']->translation->convert($options['path'],'utf-8')), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + 'checksubdirs' => False, + 'nofiles' => True + ); + if (!($vfs_file = $this->vfs->ls($vfs_data)) || $vfs_file[0]['mime_type'] != 'Directory') + { + return '409 Conflict'; + } + $vfs_data = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + ); + $options['new'] = !$this->vfs->file_exists($vfs_data); + + $vfs_data['content'] = ''; + while(!feof($options['stream'])) + { + $vfs_data['content'] .= fread($options['stream'],8192); + } + return $this->vfs->write($vfs_data); + } + + /** + * MKCOL method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function MKCOL($options) + { + if ($this->debug) error_log('vfs_webdav_server::MKCOL('.print_r($options,true).')'); + + $vfs_data = array( + 'string' => dirname($GLOBALS['egw']->translation->convert($options['path'],'utf-8')), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + 'checksubdirs' => False, + 'nofiles' => True + ); + if (!($vfs_file = $this->vfs->ls($vfs_data))) + { + return '409 Conflict'; + } + if ($this->debug) error_log(print_r($vfs_file,true)); + + if ($vfs_file[0]['mime_type'] != 'Directory') + { + return '403 Forbidden'; + } + + $vfs_data = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + ); + if ($this->vfs->file_exists($vfs_data) ) + { + return '405 Method not allowed'; + } + + if (!empty($_SERVER['CONTENT_LENGTH'])) // no body parsing yet + { + return '415 Unsupported media type'; + } + + if (!$this->vfs->mkdir($vfs_data)) + { + return '403 Forbidden'; + } + + return '201 Created'; + } + + /** + * DELETE method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function DELETE($options) + { + if ($this->debug) error_log('vfs_webdav_server::DELETE('.print_r($options,true).')'); + + $vfs_data = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + ); + if (!$this->vfs->file_exists($vfs_data)) + { + return '404 Not found'; + } + if (!$this->vfs->rm($vfs_data)) + { + return '403 Forbidden'; + } + return '204 No Content'; + } + + /** + * MOVE method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function MOVE($options) + { + return $this->COPY($options, true); + } + + /** + * COPY method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function COPY($options, $del=false) + { + if ($this->debug) error_log('vfs_webdav_server::'.($del ? 'MOVE' : 'COPY').'('.print_r($options,true).')'); + + // TODO Property updates still broken (Litmus should detect this?) + + if (!empty($_SERVER['CONTENT_LENGTH'])) // no body parsing yet + { + return '415 Unsupported media type'; + } + + // no copying to different WebDAV Servers yet + if (isset($options['dest_url'])) + { + return '502 bad gateway'; + } + + $source = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + ); + if (!$this->vfs->file_exists($source)) + { + return '404 Not found'; + } + + $dest = array( + 'string' => $options['dest'], + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + ); + $new = !$this->vfs->file_exists($dest); + $existing_col = false; + + if (!$new) + { + if ($del && $this->vfs->file_type($dest) == 'Directory') + { + if (!$options['overwrite']) + { + return '412 precondition failed'; + } + $dest['string'] .= basename($GLOBALS['egw']->translation->convert($options['path'],'utf-8')); + if ($this->vfs->file_exists($dest)) + { + $options['dest'] .= basename($GLOBALS['egw']->translation->convert($options['path'],'utf-8')); + } + else + { + $new = true; + $existing_col = true; + } + } + } + + if (!$new) + { + if ($options['overwrite']) + { + $stat = $this->DELETE(array('path' => $options['dest'])); + if (($stat{0} != '2') && (substr($stat, 0, 3) != '404')) + { + return $stat; + } + } + else + { + return '412 precondition failed'; + } + } + + if ($this->vfs->file_type($source) == 'Directory' && ($options['depth'] != 'infinity')) + { + // RFC 2518 Section 9.2, last paragraph + return '400 Bad request'; + } + + $op = $del ? 'mv' : 'cp'; + $vfs_data = array( + 'from' => $source['string'], + 'to' => $dest['string'], + 'relatives' => array(RELATIVE_ROOT,RELATIVE_ROOT) + ); + if (!$this->vfs->$op($vfs_data)) + { + return '500 Internal server error'; + } + return ($new && !$existing_col) ? '201 Created' : '204 No Content'; + } + + /** + * PROPPATCH method handler + * + * The current version only allows Webdrive to set creation and modificaton dates. + * They are not stored as (arbitrary) WebDAV properties with their own namespace and name, + * but in the regular vfs attributes. + * + * @todo Store a properties in the DB and retrieve them in PROPFIND again. + * @param array general parameter passing array + * @return bool true on success + */ + function PROPPATCH(&$options) + { + foreach ($options["props"] as $key => $prop) { + $attributes = array(); + switch($prop['ns']) + { + // allow Webdrive to set creation and modification time + case 'http://www.southrivertech.com/': + switch($prop['name']) + { + case 'srt_modifiedtime': + case 'getlastmodified': + $attributes['modified'] = strtotime($prop['val']); + break; + case 'srt_creationtime': + $attributes['created'] = strtotime($prop['val']); + break; + } + break; + + case 'DAV:': + switch($prop['name']) + { + // allow netdrive to change the modification time + case 'getlastmodified': + $attributes['modified'] = strtotime($prop['val']); + break; + // not sure why, the filesystem example of the WebDAV class does it ... + default: + $options["props"][$key]['status'] = "403 Forbidden"; + break; + } + break; + } + if ($this->debug) $props[] = '('.$prop["ns"].')'.$prop['name'].'='.$prop['val']; + } + if ($attributes) + { + $vfs_data = array( + 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), + 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root + 'attributes'=> $attributes, + ); + $this->vfs->set_attributes($vfs_data); + } + if ($this->debug) + { + error_log(__CLASS__.'::'.__METHOD__.": path=$options[path], props=".implode(', ',$props)); + if ($attributes) error_log(__CLASS__.'::'.__METHOD__.": path=$options[path], set attributes=".str_replace("\n",' ',print_r($attributes,true))); + } + + + return ""; // this is as the filesystem example handler does it, no true or false ... + } + + /** + * auth check in the session creation in dav.php, to avoid being redirected to login.php + * + * @param string $type + * @param string $login account_lid or account_lid@domain + * @param string $password this is checked in the session creation + * @return boolean true if authorized or false otherwise + */ + function checkAuth($type,$login,$password) + { + list($account_lid,$domain) = explode('@',$login); + + $auth = ($login === $GLOBALS['egw_info']['user']['account_lid'] || + ($account_lid === $GLOBALS['egw_info']['user']['account_lid'] && $domain === $GLOBALS['egw']->session->account_domain)) && + $GLOBALS['egw_info']['user']['apps']['filemanager']; + + if ($this->debug) error_log("vfs_webdav_server::checkAuth('$type','$login','\$password'): account_lid='$account_lid', domain='$domain' ==> ".(int)$auth); + + return $auth; + } +} \ No newline at end of file diff --git a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php index 22bfe92b65..30ad9fa808 100644 --- a/phpgwapi/inc/class.vfs_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.vfs_stream_wrapper.inc.php @@ -22,9 +22,13 @@ class vfs_stream_wrapper implements iface_stream_wrapper { /** - * Scheme / protocoll used for this stream-wrapper + * 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'; /** * optional context param when opening the stream, null if no context passed * @@ -40,7 +44,8 @@ class vfs_stream_wrapper implements iface_stream_wrapper * @var array */ protected static $fstab = array( - '/' => 'oldvfs://$user:$pass@$host/', + '/' => 'sqlfs://$user:$pass@$host/', +// '/' => 'oldvfs://$user:$pass@$host/', // '/files' => 'oldvfs://$user:$pass@$host/home/Default', // '/images' => 'http://localhost/egroupware/phpgwapi/templates/idots/images', // '/home/ralf/linux' => '/home/ralf', // we probably need to forbid direct filesystem access for security reasons! @@ -88,7 +93,17 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return $cache[$path]; } - $parts = parse_url($path); + // setting default user, passwd and domain, if it's not contained int the url + static $defaults; + if (is_null($defaults)) + { + $defaults = array( + 'user' => $GLOBALS['egw_info']['user']['account_lid'], + 'pass' => $GLOBALS['egw_info']['user']['passwd'], + 'host' => $GLOBALS['egw_info']['user']['domain'], + ); + } + $parts = array_merge(parse_url($path),$defaults); if (empty($parts['path'])) $parts['path'] = '/'; @@ -319,7 +334,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } - return mkdir($path,$mode,$options); + return mkdir($url,$mode,$options); } /** @@ -338,7 +353,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } - return rmdir($path,$options); + return rmdir($url); } /** @@ -366,6 +381,60 @@ class vfs_stream_wrapper implements iface_stream_wrapper return touch($url,$time); } + /** + * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * + * The methods use the following ways to get the mime type (in that order) + * - directories (is_dir()) --> self::DIR_MIME_TYPE + * - stream implemented by class defining the STAT_RETURN_MIME_TYPE constant --> use mime-type returned by url_stat + * - for regular filesystem use mime_content_type function if available + * - use eGW's mime-magic class + * + * @param string $path + * @return string mime-type (self::DIR_MIME_TYPE for directories) + */ + static function mime_content_type($path) + { + if (!($url = self::resolve_url($path))) + { + return false; + } + if (is_dir($url)) + { + $mime = self::DIR_MIME_TYPE; + } + if (!$mime && ($scheme = parse_url($url,PHP_URL_SCHEME))) + { + // check it it's an eGW stream wrapper returning mime-type via url_stat + if (class_exists($class = $scheme.'_stream_wrapper') && ($mime_attr = constant($class.'::STAT_RETURN_MIME_TYPE'))) + { + $stat = call_user_func(array($scheme.'_stream_wrapper','url_stat'),parse_url($url,PHP_URL_PATH),0); + if ($stat[$mime_attr]) + { + $mime = $stat[$mime_attr]; + } + } + } + // if we operate on the regular filesystem and the mime_content_type function is available --> use it + if (!$mime && !$scheme && function_exists('mime_content_type')) + { + $mime = mime_content_type($path); + } + // using eGW's own mime magic + // ToDo: rework mime_magic as all methods cound be static! + if (!$mime) + { + static $mime_magic; + if (is_null($mime_magic)) + { + $mime_magic = mime_magic(); + } + $mime = $mime_magic->filename2mime(parse_url($url,PHP_URL_PATH)); + } + //error_log(__METHOD__."($path) mime=$mime"); + return $mime; + } + /** * This method is called immediately when your stream object is created for examining directory contents with opendir(). * @@ -431,6 +500,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper { return false; } + error_log(__METHOD__."('$path',$flags) calling stat($url)"); return stat($url); } @@ -498,8 +568,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper self::$wrappers[] = 'webdav'; break; case 'oldvfs': - require_once(EGW_API_INC.'/class.oldvfs_stream_wrapper.inc.php'); - self::$wrappers[] = 'oldvfs'; + case 'sqlfs': + require_once(EGW_API_INC.'/class.'.$scheme.'_stream_wrapper.inc.php'); + self::$wrappers[] = $scheme; break; case '': return true; // default file, always loaded diff --git a/phpgwapi/inc/class.vfs_webdav_server.inc.php b/phpgwapi/inc/class.vfs_webdav_server.inc.php index 6e0b4a4d4a..dd21a9d51b 100644 --- a/phpgwapi/inc/class.vfs_webdav_server.inc.php +++ b/phpgwapi/inc/class.vfs_webdav_server.inc.php @@ -1,37 +1,40 @@ - * @copyright (c) 2006 by Ralf Becker + * @author Hartmut Holzgraefe original HTTP/WebDAV/Server/Filesystem class, of which some code is used * @version $Id$ */ -require_once('HTTP/WebDAV/Server.php'); -require_once(EGW_API_INC.'/class.vfs_home.inc.php'); +require_once('HTTP/WebDAV/Server/Filesystem.php'); +require_once(EGW_API_INC.'/class.egw_vfs.inc.php'); /** - * FileManger - WebDAV access + * FileManger - WebDAV access using the new stream wrapper VFS interface * - * Using the PEAR HTTP/WebDAV/Server class (which need to be installed!) + * Using the PEAR HTTP/WebDAV/Server/Filesystem class (which need to be installed!) + * + * @todo table to store locks and properties + * @todo filesystem class uses PEAR's System::find which we dont require nor know if it works on custom streamwrapper */ -class vfs_webdav_server extends HTTP_WebDAV_Server +class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem { - /** - * instance of the vfs class - * - * @var vfs_home - */ - var $vfs; - var $dav_powered_by = 'eGroupWare WebDAV server'; + /** + * Base directory is the URL of our VFS root + * + * @var string + */ + var $base = 'vfs://default'; + /** * Debug level: 0 = nothing, 1 = function calls, 2 = more info, eg. complete $_SERVER array * @@ -41,354 +44,151 @@ class vfs_webdav_server extends HTTP_WebDAV_Server */ var $debug = 0; - function vfs_webdav_server() + /** + * Serve a webdav request + * + * Reimplemented to not check our vfs base path with realpath and connect to mysql DB + * + * @access public + * @param string + */ + function ServeRequest($base = false) { - if ($this->debug === 2) foreach($_SERVER as $name => $val) error_log("vfs_webdav_server: \$_SERVER[$name]='$val'"); - - parent::HTTP_WebDAV_Server(); + // special treatment for litmus compliance test + // reply on its identifier header + // not needed for the test itself but eases debugging + foreach (apache_request_headers() as $key => $value) + { + if (stristr($key, "litmus")) + { + error_log("Litmus test $value"); + header("X-Litmus-reply: ".$value); + } + } - $this->vfs =& new vfs_home; + // let the base class do all the work + HTTP_WebDAV_Server::ServeRequest(); } - /** - * PROPFIND method handler - * - * @param array general parameter passing array - * @param array return array for file properties - * @return bool true on success - */ - function PROPFIND(&$options, &$files) - { - $vfs_data = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - // at first only list the given path itself - 'checksubdirs' => False, - 'nofiles' => True - ); - if (!($vfs_files = $this->vfs->ls($vfs_data))) // path not found - { - // check if the users home-dir is just not yet created (should be done by the vfs-class!) - // ToDo: group-dirs - if ($vfs_data['string'] == '/home/'.$GLOBALS['egw_info']['user']['account_lid']) - { - $this->vfs->override_acl = true; // user has no right to create dir in /home - $created = $this->vfs->mkdir(array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - )); - $this->vfs->override_acl = false; - - if (!$created) - { - if ($this->debug) error_log("vfs_webdav_server::PROPFIND(path='$options[path]',depth=$options[depth]) could not create home dir"); - } - $vfs_files = $this->vfs->ls($vfs_data); - } - if (!$vfs_files) - { - if ($this->debug) error_log("vfs_webdav_server::PROPFIND(path='$options[path]',depth=$options[depth]) return false (path not found)"); - return false; // path not found - } - } - // if depth > 0 and path is a directory => show it's contents - if (!empty($options['depth']) && $vfs_files[0]['mime_type'] == 'Directory') - { - $vfs_data['checksubdirs'] = (int) $options['depth'] != 1; - $vfs_data['nofiles'] = false; + /** + * DELETE method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function DELETE($options) + { + $path = $this->base . "/" .$options["path"]; - if ($vfs_files[0]['directory'] == '/') // sub-dirs of the root? - { - $vfs_files = array(); // dont return the directory, it shows up double in konq - } - else // return the dir itself with a trailing slash, otherwise empty dirs are reported as non-existent - { - $vfs_files[0]['name'] .= '/'; - } - $vfs_files = array_merge($vfs_files,$this->vfs->ls($vfs_data)); - } - if ($this->debug) error_log("vfs_webdav_server::PROPFIND(path='$options[path]',depth=$options[depth]) ".count($vfs_files).' files'); - - $files['files'] = array(); - $egw_charset = $GLOBALS['egw']->translation->charset(); - foreach($vfs_files as $fileinfo) - { - if ($this->debug) error_log('dir="'.$fileinfo['directory'].'", name="'.$fileinfo['name'].'": '.$fileinfo['mime_type']); - foreach(array('modified','created') as $date) - { - // our vfs has no modified set, if never modified, use created - list($y,$m,$d,$h,$i,$s) = split("[- :]",$fileinfo[$date] ? $fileinfo[$date] : $fileinfo['created']); - $fileinfo[$date] = mktime((int)$h,(int)$i,(int)$s,(int)$m,(int)$d,(int)$y); - } - $info = array( - 'path' => $GLOBALS['egw']->translation->convert($fileinfo['directory'].'/'.$fileinfo['name'],$egw_charset,'utf-8'), - 'props' => array( - $this->mkprop('displayname',$GLOBALS['egw']->translation->convert($fileinfo['name'],$egw_charset,'utf-8')), - $this->mkprop('creationdate',$fileinfo['created']), - $this->mkprop('getlastmodified',$fileinfo['modified']), - ), - ); - if ($fileinfo['mime_type'] == 'Directory') + if (!file_exists($path)) + { + return "404 Not found"; + } + + if (is_dir($path)) + { + /*$query = "DELETE FROM {$this->db_prefix}properties + WHERE path LIKE '".$this->_slashify($options["path"])."%'"; + mysql_query($query); */ + // recursive delete the directory + if ($dir = egw_vfs::dir_opendir($options["path"])) { - $info['props'][] = $this->mkprop('resourcetype', 'collection'); - $info['props'][] = $this->mkprop('getcontenttype', 'httpd/unix-directory'); + while(($file = readdir($dir))) + { + if ($file == '.' || $file == '..') continue; + + if (is_dir($path.'/'.$file)) + { + // recursivly call ourself with the dir + $opts = $options; + $opts['path'] .= '/'.$file; + $this->DELETE($opts); + } + else + { + unlink($path.'/'.$file); + } + } + closedir($dir); } - else - { - $info['props'][] = $this->mkprop('resourcetype', ''); - $info['props'][] = $this->mkprop('getcontenttype', $fileinfo['mime_type']); - $info['props'][] = $this->mkprop('getcontentlength', $fileinfo['size']); - } - $files['files'][] = $info; - } - if ($this->debug == 2) foreach($files['files'] as $info) error_log(print_r($info,true)); - // ok, all done - return true; - } - - /** - * GET method handler - * - * @param array parameter passing array - * @return bool true on success - */ - function GET(&$options) - { - if ($this->debug) error_log('vfs_webdav_server::GET('.print_r($options,true).')'); - - $vfs_data = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - 'checksubdirs' => False, - 'nofiles' => True - ); - // sanity check - if (!($vfs_file = $this->vfs->ls($vfs_data))) - { - return false; - } - $options['mimetype'] = $vfs_file[0]['mime_type']; - $options['size'] = $vfs_file[0]['size']; - - if (($options['data'] = $this->vfs->read($vfs_data)) === false) - { - return '403 Forbidden'; // not sure if this is the right code for access denied - } - return true; - } + } + else + { + unlink($path); + } + /*$query = "DELETE FROM {$this->db_prefix}properties + WHERE path = '$options[path]'"; + mysql_query($query);*/ + + return "204 No Content"; + } + + /** + * Get properties for a single file/resource + * + * @param string resource path + * @return array resource properties + */ + function fileinfo($path) + { + error_log(__METHOD__."($path)"); + // map URI path to filesystem path + $fspath = $this->base . $path; + + // create result array + $info = array(); + // TODO remove slash append code when base clase is able to do it itself + $info["path"] = is_dir($fspath) ? $this->_slashify($path) : $path; + $info["props"] = array(); + + // no special beautified displayname here ... + $info["props"][] = $this->mkprop("displayname", strtoupper($path)); + + // creation and modification time + $info["props"][] = $this->mkprop("creationdate", filectime($fspath)); + $info["props"][] = $this->mkprop("getlastmodified", filemtime($fspath)); + + // type and size (caller already made sure that path exists) + if (is_dir($fspath)) { + // directory (WebDAV collection) + $info["props"][] = $this->mkprop("resourcetype", "collection"); + $info["props"][] = $this->mkprop("getcontenttype", "httpd/unix-directory"); + } else { + // plain file (WebDAV resource) + $info["props"][] = $this->mkprop("resourcetype", ""); + if (egw_vfs::is_readable($path)) { + $info["props"][] = $this->mkprop("getcontenttype", egw_vfs::mime_content_type($path)); + } else { + error_log(__METHOD__."($path) $fspath is not readable!"); + $info["props"][] = $this->mkprop("getcontenttype", "application/x-non-readable"); + } + $info["props"][] = $this->mkprop("getcontentlength", filesize($fspath)); + } +/* + // get additional properties from database + $query = "SELECT ns, name, value + FROM {$this->db_prefix}properties + WHERE path = '$path'"; + $res = mysql_query($query); + while ($row = mysql_fetch_assoc($res)) { + $info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]); + } + mysql_free_result($res); +*/ + //error_log(__METHOD__."($path) info=".print_r($info,true)); + return $info; + } /** - * PUT method handler - * - * @param array parameter passing array - * @return bool true on success - */ - function PUT(&$options) - { - if ($this->debug) error_log('vfs_webdav_server::PUT('.print_r($options,true).')'); - - $vfs_data = array( - 'string' => dirname($GLOBALS['egw']->translation->convert($options['path'],'utf-8')), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - 'checksubdirs' => False, - 'nofiles' => True - ); - if (!($vfs_file = $this->vfs->ls($vfs_data)) || $vfs_file[0]['mime_type'] != 'Directory') - { - return '409 Conflict'; - } - $vfs_data = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - ); - $options['new'] = !$this->vfs->file_exists($vfs_data); - - $vfs_data['content'] = ''; - while(!feof($options['stream'])) - { - $vfs_data['content'] .= fread($options['stream'],8192); - } - return $this->vfs->write($vfs_data); - } - - /** - * MKCOL method handler + * Used eg. by get * - * @param array general parameter passing array - * @return bool true on success + * @todo replace all calls to _mimetype with egw_vfs::mime_content_type() + * @param string $path + * @return string */ - function MKCOL($options) - { - if ($this->debug) error_log('vfs_webdav_server::MKCOL('.print_r($options,true).')'); - - $vfs_data = array( - 'string' => dirname($GLOBALS['egw']->translation->convert($options['path'],'utf-8')), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - 'checksubdirs' => False, - 'nofiles' => True - ); - if (!($vfs_file = $this->vfs->ls($vfs_data))) - { - return '409 Conflict'; - } - if ($this->debug) error_log(print_r($vfs_file,true)); - - if ($vfs_file[0]['mime_type'] != 'Directory') - { - return '403 Forbidden'; - } - - $vfs_data = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - ); - if ($this->vfs->file_exists($vfs_data) ) - { - return '405 Method not allowed'; - } - - if (!empty($_SERVER['CONTENT_LENGTH'])) // no body parsing yet - { - return '415 Unsupported media type'; - } - - if (!$this->vfs->mkdir($vfs_data)) - { - return '403 Forbidden'; - } - - return '201 Created'; - } - - /** - * DELETE method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - function DELETE($options) + function _mimetype($path) { - if ($this->debug) error_log('vfs_webdav_server::DELETE('.print_r($options,true).')'); - - $vfs_data = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - ); - if (!$this->vfs->file_exists($vfs_data)) - { - return '404 Not found'; - } - if (!$this->vfs->rm($vfs_data)) - { - return '403 Forbidden'; - } - return '204 No Content'; - } - - /** - * MOVE method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - function MOVE($options) - { - return $this->COPY($options, true); - } - - /** - * COPY method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - function COPY($options, $del=false) - { - if ($this->debug) error_log('vfs_webdav_server::'.($del ? 'MOVE' : 'COPY').'('.print_r($options,true).')'); - - // TODO Property updates still broken (Litmus should detect this?) - - if (!empty($_SERVER['CONTENT_LENGTH'])) // no body parsing yet - { - return '415 Unsupported media type'; - } - - // no copying to different WebDAV Servers yet - if (isset($options['dest_url'])) - { - return '502 bad gateway'; - } - - $source = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - ); - if (!$this->vfs->file_exists($source)) - { - return '404 Not found'; - } - - $dest = array( - 'string' => $options['dest'], - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - ); - $new = !$this->vfs->file_exists($dest); - $existing_col = false; - - if (!$new) - { - if ($del && $this->vfs->file_type($dest) == 'Directory') - { - if (!$options['overwrite']) - { - return '412 precondition failed'; - } - $dest['string'] .= basename($GLOBALS['egw']->translation->convert($options['path'],'utf-8')); - if ($this->vfs->file_exists($dest)) - { - $options['dest'] .= basename($GLOBALS['egw']->translation->convert($options['path'],'utf-8')); - } - else - { - $new = true; - $existing_col = true; - } - } - } - - if (!$new) - { - if ($options['overwrite']) - { - $stat = $this->DELETE(array('path' => $options['dest'])); - if (($stat{0} != '2') && (substr($stat, 0, 3) != '404')) - { - return $stat; - } - } - else - { - return '412 precondition failed'; - } - } - - if ($this->vfs->file_type($source) == 'Directory' && ($options['depth'] != 'infinity')) - { - // RFC 2518 Section 9.2, last paragraph - return '400 Bad request'; - } - - $op = $del ? 'mv' : 'cp'; - $vfs_data = array( - 'from' => $source['string'], - 'to' => $dest['string'], - 'relatives' => array(RELATIVE_ROOT,RELATIVE_ROOT) - ); - if (!$this->vfs->$op($vfs_data)) - { - return '500 Internal server error'; - } - return ($new && !$existing_col) ? '201 Created' : '204 No Content'; + return egw_vfs::mime_content_type($path); } /** @@ -404,6 +204,8 @@ class vfs_webdav_server extends HTTP_WebDAV_Server */ function PROPPATCH(&$options) { + $path = $GLOBALS['egw']->translation->convert($options['path'],'utf-8'); + foreach ($options["props"] as $key => $prop) { $attributes = array(); switch($prop['ns']) @@ -414,10 +216,11 @@ class vfs_webdav_server extends HTTP_WebDAV_Server { case 'srt_modifiedtime': case 'getlastmodified': - $attributes['modified'] = strtotime($prop['val']); + egw_vfs::touch($path,strtotime($prop['val'])); break; case 'srt_creationtime': - $attributes['created'] = strtotime($prop['val']); + // not supported via the streamwrapper interface atm. + //$attributes['created'] = strtotime($prop['val']); break; } break; @@ -427,7 +230,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server { // allow netdrive to change the modification time case 'getlastmodified': - $attributes['modified'] = strtotime($prop['val']); + egw_vfs::touch($path,strtotime($prop['val'])); break; // not sure why, the filesystem example of the WebDAV class does it ... default: @@ -438,26 +241,147 @@ class vfs_webdav_server extends HTTP_WebDAV_Server } if ($this->debug) $props[] = '('.$prop["ns"].')'.$prop['name'].'='.$prop['val']; } - if ($attributes) - { - $vfs_data = array( - 'string' => $GLOBALS['egw']->translation->convert($options['path'],'utf-8'), - 'relatives' => array(RELATIVE_ROOT), // filename is relative to the vfs-root - 'attributes'=> $attributes, - ); - $this->vfs->set_attributes($vfs_data); - } if ($this->debug) { - error_log(__CLASS__.'::'.__METHOD__.": path=$options[path], props=".implode(', ',$props)); - if ($attributes) error_log(__CLASS__.'::'.__METHOD__.": path=$options[path], set attributes=".str_replace("\n",' ',print_r($attributes,true))); + error_log(__METHOD__.": path=$options[path], props=".implode(', ',$props)); + if ($attributes) error_log(__METHOD__.": path=$options[path], set attributes=".str_replace("\n",' ',print_r($attributes,true))); } - return ""; // this is as the filesystem example handler does it, no true or false ... } + + /** + * LOCK method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function LOCK(&$options) + { + // behaving like LOCK is not implemented + return "412 Precondition failed"; +/* + // get absolute fs path to requested resource + $fspath = $this->base . $options["path"]; + + // TODO recursive locks on directories not supported yet + if (is_dir($fspath) && !empty($options["depth"])) { + return "409 Conflict"; + } + + $options["timeout"] = time()+300; // 5min. hardcoded + + if (isset($options["update"])) { // Lock Update + $where = "WHERE path = '$options[path]' AND token = '$options[update]'"; + + $query = "SELECT owner, exclusivelock FROM {$this->db_prefix}locks $where"; + $res = mysql_query($query); + $row = mysql_fetch_assoc($res); + mysql_free_result($res); + + if (is_array($row)) { + $query = "UPDATE {$this->db_prefix}locks + SET expires = '$options[timeout]' + , modified = ".time()." + $where"; + mysql_query($query); + + $options['owner'] = $row['owner']; + $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared"; + $options['type'] = $row["exclusivelock"] ? "write" : "read"; + + return true; + } else { + return false; + } + } + + $query = "INSERT INTO {$this->db_prefix}locks + SET token = '$options[locktoken]' + , path = '$options[path]' + , created = ".time()." + , modified = ".time()." + , owner = '$options[owner]' + , expires = '$options[timeout]' + , exclusivelock = " .($options['scope'] === "exclusive" ? "1" : "0") + ; + mysql_query($query); + + return mysql_affected_rows() ? "200 OK" : "409 Conflict";*/ + } /** + * UNLOCK method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function UNLOCK(&$options) + { + // behaving like LOCK is not implemented + return "405 Method not allowed"; +/* + $query = "DELETE FROM {$this->db_prefix}locks + WHERE path = '$options[path]' + AND token = '$options[token]'"; + mysql_query($query); + + return mysql_affected_rows() ? "204 No Content" : "409 Conflict";*/ + } + + /** + * checkLock() helper + * + * @param string resource path to check for locks + * @return bool true on success + */ + function checkLock($path) + { + // behave like checkLock is not implemented + return false; +/* + $result = false; + + $query = "SELECT owner, token, created, modified, expires, exclusivelock + FROM {$this->db_prefix}locks + WHERE path = '$path' + "; + $res = mysql_query($query); + + if ($res) { + $row = mysql_fetch_array($res); + mysql_free_result($res); + + if ($row) { + $result = array( "type" => "write", + "scope" => $row["exclusivelock"] ? "exclusive" : "shared", + "depth" => 0, + "owner" => $row['owner'], + "token" => $row['token'], + "created" => $row['created'], + "modified" => $row['modified'], + "expires" => $row['expires'] + ); + } + } + + return $result;*/ + } + + /** + * Remove not (yet) implemented LOCK methods, so we can use the mostly unchanged HTTP_WebDAV_Server_Filesystem class + * + * @return array + */ + function _allow() + { + $allow = parent::_allow(); + unset($allow['LOCK']); + unset($allow['UNLOCK']); + return $allow; + } + + /** * auth check in the session creation in dav.php, to avoid being redirected to login.php * * @param string $type