From 99e1cad8662f69096798ca355e0300b16a016269 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 24 Oct 2012 16:25:53 +0000 Subject: [PATCH] * Filemanager/WebDAV: support for newer ownCloud clients (eg. version 1.1.1) --- phpgwapi/inc/class.vfs_webdav_server.inc.php | 207 ++++++++++++++++++- remote.php | 90 ++++++++ 2 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 remote.php diff --git a/phpgwapi/inc/class.vfs_webdav_server.inc.php b/phpgwapi/inc/class.vfs_webdav_server.inc.php index e8ad55d506..11debfef0a 100644 --- a/phpgwapi/inc/class.vfs_webdav_server.inc.php +++ b/phpgwapi/inc/class.vfs_webdav_server.inc.php @@ -55,9 +55,9 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem * Reimplemented to not check our vfs base path with realpath and connect to mysql DB * * @access public - * @param string + * @param $prefix=null prefix filesystem path with given path, eg. "/webdav" for owncloud 4.5 remote.php */ - function ServeRequest($base = false) + function ServeRequest($prefix=null) { // special treatment for litmus compliance test // reply on its identifier header @@ -67,7 +67,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem header("X-Litmus-reply: ".$this->_SERVER['HTTP_X_LITMUS']); } // let the base class do all the work - HTTP_WebDAV_Server::ServeRequest(); + HTTP_WebDAV_Server::ServeRequest($prefix); } /** @@ -296,6 +296,9 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem $info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontenttype', 'application/x-non-readable'); } $info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontentlength', filesize($fspath)); + // for files generate etag from inode (sqlfs: fs_id), modification time and size + $stat = stat($fspath); + $info['props'][] = HTTP_WebDAV_Server::mkprop('getetag', '"'.$stat['ino'].':'.$stat['mtime'].':'.$stat['size'].'"'); } /* returning the supportedlock property causes Windows DAV provider and Konqueror to not longer work ToDo: return it only if explicitly requested ($options['props']) @@ -310,8 +313,6 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem '); */ - // ToDo: etag from inode and modification time - //error_log(__METHOD__."($path) info=".array2string($info)); return $info; } @@ -622,6 +623,10 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem */ function GET(&$options) { + if (is_dir($this->base . $options["path"])) + { + return $this->autoindex($options); + } if (($ok = parent::GET($options)) && $this->force_download) { if(html::$user_agent == 'msie' && self::$ua_version < 9.0) @@ -636,4 +641,196 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem } return $ok; } + + /** + * Display an automatic index (listing and properties) for a collection + * + * @param array $options parameter passing array, index "path" contains requested path + */ + protected function autoindex($options) + { + $propfind_options = array( + 'path' => $options['path'], + 'depth' => 1, + ); + $files = array(); + if (($ret = $this->PROPFIND($propfind_options,$files)) !== true) + { + return $ret; // no collection + } + header('Content-type: text/html; charset='.translation::charset()); + echo "\n\n\t".'EGroupware WebDAV server '.htmlspecialchars($options['path'])."\n"; + echo "\t\n"; + echo "\t\n"; + echo "\n\n"; + + echo '

WebDAV '; + list(,$base) = explode(parse_url($GLOBALS['egw_info']['server']['webserver_url'], PHP_URL_PATH), $this->base_uri, 2); + $path = $base; + foreach(explode('/',$this->_unslashify($options['path'])) as $n => $name) + { + $path .= ($n != 1 ? '/' : '').$name; + echo html::a_href(htmlspecialchars($name.'/'),$path); + } + echo "

\n"; + + static $props2show = array( + 'DAV:displayname' => 'Displayname', + 'DAV:getlastmodified' => 'Last modified', + 'DAV:getetag' => 'ETag', + 'DAV:getcontenttype' => 'Content type', + 'DAV:resourcetype' => 'Resource type', + //'DAV:owner' => 'Owner', + //'DAV:current-user-privilege-set' => 'current-user-privilege-set', + //'DAV:getcontentlength' => 'Size', + //'DAV:sync-token' => 'sync-token', + ); + $n = 0; + foreach($files['files'] as $file) + { + if (!isset($collection_props)) + { + $collection_props = $this->props2array($file['props']); + echo '

'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."

\n"; + continue; // own entry --> displaying properies later + } + if(!$n++) + { + echo "\n\t\n\t\t\n\t\t"; + foreach($props2show as $label) echo "\t\t\n"; + echo "\t\n"; + } + $props = $this->props2array($file['props']); + //echo $file['path']; _debug_array($props); + $class = $class == 'row_on' ? 'row_off' : 'row_on'; + + if (substr($file['path'],-1) == '/') + { + $name = basename(substr($file['path'],0,-1)).'/'; + } + else + { + $name = basename($file['path']); + } + + echo "\t\n\t\t\n\t\t\n"; + foreach($props2show as $prop => $label) + { + echo "\t\t\n"; + } + echo "\t\n"; + } + if (!$n) + { + echo '

'.lang('Collection empty.')."

\n"; + } + else + { + echo "
#".lang('Name')."".lang($label)."
$n". + html::a_href(htmlspecialchars($name),$base.strtr($file['path'], array( + '%' => '%25', + '#' => '%23', + '?' => '%3F', + )))."".($prop=='DAV:getlastmodified'&&!empty($props[$prop])?date('Y-m-d H:i:s',$props[$prop]):$props[$prop])."
\n"; + } + echo '

'.lang('Properties')."

\n"; + echo "\n\t\n"; + foreach($collection_props as $name => $value) + { + $class = $class == 'row_on' ? 'row_off' : 'row_on'; + $ns = explode(':',$name); + $name = array_pop($ns); + $ns = implode(':',$ns); + echo "\t\n\t\t\n"; + echo "\t\t\n\t\n"; + } + echo "
".lang('Namespace')."".lang('Name')."".lang('Value')."
".htmlspecialchars($ns)."".htmlspecialchars($name)."".$value."
\n"; + /*$dav = array(1); + $allow = false; + $this->OPTIONS($options['path'], $dav, $allow); + echo "

DAV: ".implode(', ', $dav)."

\n";*/ + + echo "\n\n"; + + common::egw_exit(); + } + + /** + * Format a property value for output + * + * @param mixed $value + * @return string + */ + protected function prop_value($value) + { + if (is_array($value)) + { + if (isset($value[0]['ns'])) + { + $value = $this->_hierarchical_prop_encode($value); + } + $value = array2string($value); + } + if ($value[0] == '<' && function_exists('tidy_repair_string')) + { + $value = tidy_repair_string($value, array( + 'indent' => true, + 'show-body-only' => true, + 'output-encoding' => 'utf-8', + 'input-encoding' => 'utf-8', + 'input-xml' => true, + 'output-xml' => true, + 'wrap' => 0, + )); + } + if (($href=preg_match('/\<(D:)?href\>[^<]+\<\/(D:)?href\>/i',$value))) + { + $value = preg_replace('/\<(D:)?href\>('.preg_quote($this->base_uri.'/','/').')?([^<]+)\<\/(D:)?href\>/i','<\\1href>\\3',$value); + } + $value = $value[0] == '<' || strpos($value, "\n") !== false ? '
'.htmlspecialchars($value).'
' : htmlspecialchars($value); + + if ($href) + { + $value = preg_replace('/<a href="(.+)">/', '', $value); + $value = str_replace('</a>', '', $value); + } + return $value; + } + + /** + * Return numeric indexed array with values for keys 'ns', 'name' and 'val' as array 'ns:name' => 'val' + * + * @param array $props + * @return array + */ + protected function props2array(array $props) + { + $arr = array(); + foreach($props as $prop) + { + $ns_hash = array('DAV:' => 'D'); + switch($prop['ns']) + { + case 'DAV:'; + $ns = 'DAV'; + break; + default: + $ns = $prop['ns']; + } + if (is_array($prop['val'])) + { + $prop['val'] = $this->_hierarchical_prop_encode($prop['val'], $prop['ns'], $ns_defs='', $ns_hash); + // hack to show real namespaces instead of not (visibly) defined shortcuts + unset($ns_hash['DAV:']); + $value = strtr($v=$this->prop_value($prop['val']),array_flip($ns_hash)); + } + else + { + $value = $this->prop_value($prop['val']); + } + $arr[$ns.':'.$prop['name']] = $value; + } + return $arr; + } } \ No newline at end of file diff --git a/remote.php b/remote.php new file mode 100644 index 0000000000..d4a9ac81ae --- /dev/null +++ b/remote.php @@ -0,0 +1,90 @@ + + * @copyright (c) 2006-12 by Ralf Becker + * @version $Id$ + */ + +$starttime = microtime(true); + +/** + * 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) +{ + if (isset($_GET['auth'])) + { + list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) = explode(':',base64_decode($_GET['auth']),2); + } + return egw_digest_auth::autocreate_session_callback($account); +} + +$GLOBALS['egw_info'] = array( + 'flags' => array( + 'disable_Template_class' => True, + 'noheader' => True, + 'currentapp' => preg_match('|/remote.php/webdav/apps/([A-Za-z0-9_-]+)/|', $_SERVER['REQUEST_URI'], $matches) ? $matches[1] : 'filemanager', + 'autocreate_session_callback' => 'check_access', + 'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm) + 'auth_realm' => 'EGroupware WebDAV server', // cant use vfs_webdav_server::REALM as autoloading and include path not yet setup! + ) +); +require_once(__DIR__.'/phpgwapi/inc/class.egw_digest_auth.inc.php'); + +// if you move this file somewhere else, you need to adapt the path to the header! +try +{ + include(__DIR__.'/header.inc.php'); +} +catch (egw_exception_no_permission_app $e) +{ + if (isset($GLOBALS['egw_info']['user']['apps']['filemanager'])) + { + $GLOBALS['egw_info']['currentapp'] = 'filemanager'; + } + elseif (isset($GLOBALS['egw_info']['user']['apps']['sitemgr-link'])) + { + $GLOBALS['egw_info']['currentapp'] = 'sitemgr-link'; + } + else + { + throw $e; + } +} +//$headertime = microtime(true); + +// temporary mount ownCloud default /clientsync as /home/$user, if not explicitly mounted +// so ownCloud dir contains users home-dir by default +if (strpos($_SERVER['REQUEST_URI'],'/remote.php/webdav/clientsync') !== false && + ($fstab=egw_vfs::mount()) && !isset($fstab['/clientsync'])) +{ + $is_root_backup = egw_vfs::$is_root; + egw_vfs::$is_root = true; + $ok = egw_vfs::mount($url='vfs://default/home/$user', $clientsync='/clientsync', null, false); + egw_vfs::$is_root = $is_root_backup; + //error_log("mounting ownCloud default '$clientsync' as '$url' ".($ok ? 'successful' : 'failed!')); +} + +// webdav is stateless: we dont need to keep the session open, it only blocks other calls to same basic-auth session +$GLOBALS['egw']->session->commit_session(); + +$webdav_server = new vfs_webdav_server(); +$webdav_server->ServeRequest('/webdav'); +//error_log(sprintf('WebDAV %s request: status "%s", took %5.3f s'.($headertime?' (header include took %5.3f s)':''),$_SERVER['REQUEST_METHOD'].' '.$_SERVER['PATH_INFO'],$webdav_server->_http_status,microtime(true)-$starttime,$headertime-$starttime));