Page MenuHomePhorge

No OneTemporary

diff --git a/lib/drivers/kolab/kolab_file_storage.php b/lib/drivers/kolab/kolab_file_storage.php
index fb230b8..88c1e43 100644
--- a/lib/drivers/kolab/kolab_file_storage.php
+++ b/lib/drivers/kolab/kolab_file_storage.php
@@ -1,1447 +1,1447 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class kolab_file_storage implements file_storage
{
/**
* @var rcube
*/
protected $rc;
/**
* @var array
*/
protected $folders;
/**
* @var array
*/
protected $config = array();
/**
* @var string
*/
protected $title;
/**
* @var array
*/
protected $icache = array();
/**
* Class constructor
*/
public function __construct()
{
$this->rc = rcube::get_instance();
// Get list of plugins
// WARNING: We can use only plugins that are prepared for this
// e.g. are not using output or rcmail objects or
// doesn't throw errors when using them
$plugins = (array) $this->rc->config->get('fileapi_plugins', array('kolab_auth', 'kolab_folders'));
$plugins = array_unique(array_merge($plugins, array('libkolab')));
// Kolab WebDAV server supports plugins, no need to overwrite object
if (!is_a($this->rc->plugins, 'rcube_plugin_api')) {
// Initialize/load plugins
$this->rc->plugins = kolab_file_plugin_api::get_instance();
$this->rc->plugins->init($this, '');
}
// this way we're compatible with Roundcube Framework 1.2
// we can't use load_plugins() here
foreach ($plugins as $plugin) {
$this->rc->plugins->load_plugin($plugin, true);
}
$this->init();
}
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @param bool True on success, False on failure
*/
public function authenticate($username, $password)
{
$auth = $this->rc->plugins->exec_hook('authenticate', array(
'host' => $this->select_host($username),
'user' => $username,
'pass' => $password,
'valid' => true,
));
// Authenticate - get Roundcube user ID
if ($auth['valid'] && !$auth['abort']
&& ($this->login($auth['user'], $auth['pass'], $auth['host']))) {
return true;
}
$this->rc->plugins->exec_hook('login_failed', array(
'host' => $auth['host'],
'user' => $auth['user'],
));
}
/**
* Get password and name of authenticated user
*
* @return array Authenticated user data
*/
public function auth_info()
{
return array(
'username' => $this->config['username'] ?: $_SESSION['username'],
'password' => $this->config['password'] ?: $this->rc->decrypt($_SESSION['password']),
);
}
/**
* Storage host selection
*/
private function select_host($username)
{
// Get IMAP host
$host = $this->rc->config->get('default_host');
if (is_array($host)) {
list($user, $domain) = explode('@', $username);
// try to select host by mail domain
if (!empty($domain)) {
foreach ($host as $storage_host => $mail_domains) {
if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
$host = $storage_host;
break;
}
else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
$host = is_numeric($storage_host) ? $mail_domains : $storage_host;
break;
}
}
}
// take the first entry if $host is not found
if (is_array($host)) {
list($key, $val) = each($host);
$host = is_numeric($key) ? $val : $key;
}
}
return rcube_utils::parse_host($host);
}
/**
* Authenticates a user in IMAP
*/
private function login($username, $password, $host)
{
if (empty($username)) {
return false;
}
$login_lc = $this->rc->config->get('login_lc');
$default_port = $this->rc->config->get('default_port', 143);
// parse $host
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
if (!empty($a_host['port'])) {
$port = $a_host['port'];
}
else if ($ssl && $ssl != 'tls' && (!$default_port || $default_port == 143)) {
$port = 993;
}
}
if (!$port) {
$port = $default_port;
}
// Convert username to lowercase. If storage backend
// is case-insensitive we need to store always the same username
if ($login_lc) {
if ($login_lc == 2 || $login_lc === true) {
$username = mb_strtolower($username);
}
else if (strpos($username, '@')) {
// lowercase domain name
list($local, $domain) = explode('@', $username);
$username = $local . '@' . mb_strtolower($domain);
}
}
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
$host = rcube_utils::idn_to_ascii($host);
$username = rcube_utils::idn_to_ascii($username);
// user already registered?
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];
}
// authenticate user in IMAP
$storage = $this->rc->get_storage();
if (!$storage->connect($host, $username, $password, $port, $ssl)) {
return false;
}
// No user in database, but IMAP auth works
if (!is_object($user)) {
if ($this->rc->config->get('auto_create_user')) {
// create a new user record
$user = rcube_user::create($username, $host);
if (!$user) {
rcube::raise_error(array(
'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to create a user record",
), true, false);
return false;
}
}
else {
rcube::raise_error(array(
'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__,
'message' => "Access denied for new user $username. 'auto_create_user' is disabled",
), true, false);
return false;
}
}
// set session vars
$_SESSION['user_id'] = $user->ID;
$_SESSION['username'] = $user->data['username'];
$_SESSION['storage_host'] = $host;
$_SESSION['storage_port'] = $port;
$_SESSION['storage_ssl'] = $ssl;
$_SESSION['password'] = $this->rc->encrypt($password);
$this->init($user);
// force reloading of mailboxes list/data
// Roundcube already does that (T1050)
//$storage->clear_cache('mailboxes', true);
return true;
}
protected function init($user = null)
{
$this->rc->plugins->exec_hook('startup');
if ($_SESSION['user_id'] || $user) {
// overwrite config with user preferences
$this->rc->user = $user ? $user : new rcube_user($_SESSION['user_id']);
$this->rc->config->set_user_prefs((array)$this->rc->user->get_prefs());
$storage = $this->rc->get_storage();
$storage->set_charset($this->rc->config->get('default_charset', RCUBE_CHARSET));
setlocale(LC_ALL, 'en_US.utf8', 'en_US.UTF-8');
}
}
/**
* Configures environment
*
* @param array $config Configuration
* @param string $title Source identifier
*/
public function configure($config, $title = null)
{
$this->config = array_merge($this->config, $config);
// @TODO: this is currently not possible to have multiple sessions in Roundcube
}
/**
* Returns current instance title
*
* @return string Instance title (mount point)
*/
public function title()
{
return '';
}
/**
* Storage driver capabilities
*
* @return array List of capabilities
*/
public function capabilities()
{
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize) {
$max_filesize = $max_postsize;
}
$storage = $this->rc->get_storage();
$quota = $storage->get_capability('QUOTA');
return array(
file_storage::CAPS_MAX_UPLOAD => $max_filesize,
file_storage::CAPS_QUOTA => $quota,
file_storage::CAPS_LOCKS => true,
file_storage::CAPS_SUBSCRIPTIONS => true,
);
}
/**
* Save configuration of external driver (mount point)
*
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_create($driver)
{
$drivers = $this->driver_list();
if ($drivers[$driver['title']]) {
throw new Exception("Driver exists", file_storage::ERROR);
}
$config = kolab_storage_config::get_instance();
$status = $config->save($driver, 'file_driver');
if (!$status) {
throw new Exception("Driver create failed", file_storage::ERROR);
}
$this->driver_list = null;
}
/**
* Delete configuration of external driver (mount point)
*
* @param string $name Driver instance name
*
* @throws Exception
*/
public function driver_delete($name)
{
$drivers = $this->driver_list();
if ($driver = $drivers[$name]) {
$config = kolab_storage_config::get_instance();
$status = $config->delete($driver['uid']);
if (!$status) {
throw new Exception("Driver delete failed", file_storage::ERROR);
}
$this->driver_list = null;
return;
}
throw new Exception("Driver not found", file_storage::ERROR);
}
/**
* Return list of registered drivers (mount points)
*
* @return array List of drivers data
* @throws Exception
*/
public function driver_list()
{
// use internal cache, this is specifically for iRony
// which may call this code path many times in one request
if ($this->driver_list !== null) {
return $this->driver_list;
}
// get current relations state
$config = kolab_storage_config::get_instance();
$default = true;
$filter = array(
array('type', '=', 'file_driver'),
);
$drivers = $config->get_objects($filter, $default, 100);
$result = array();
foreach ($drivers as $driver) {
$result[$driver['title']] = $driver;
}
return $this->driver_list = $result;
}
/**
* Update configuration of external driver (mount point)
*
* @param string $title Driver instance title
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_update($title, $driver)
{
$drivers = $this->driver_list();
if (!$drivers[$title]) {
throw new Exception("Driver not found", file_storage::ERROR);
}
$config = kolab_storage_config::get_instance();
$status = $config->save($driver, 'file_driver');
if (!$status) {
throw new Exception("Driver update failed", file_storage::ERROR);
}
$this->driver_list = null;
}
/**
* Returns metadata of the driver
*
* @return array Driver meta data (image, name, form)
*/
public function driver_metadata()
{
$image_content = file_get_contents(__DIR__ . '/kolab.png');
$metadata = array(
'image' => 'data:image/png;base64,' . base64_encode($image_content),
'name' => 'Kolab Groupware',
'ref' => 'http://kolab.org',
'description' => 'Kolab Groupware server',
'form' => array(
'host' => 'hostname',
'username' => 'username',
'password' => 'password',
),
);
return $metadata;
}
/**
* Validate metadata (config) of the driver
*
* @param array $metadata Driver metadata
*
* @return array Driver meta data to be stored in configuration
* @throws Exception
*/
public function driver_validate($metadata)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Create a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_create($file_name, $file)
{
$exists = $this->get_file_object($file_name, $folder);
if (!empty($exists)) {
throw new Exception("Storage error. File exists.", file_storage::ERROR);
}
$object = $this->to_file_object(array(
'name' => $file_name,
'type' => $file['type'],
'path' => $file['path'],
'content' => $file['content'],
));
// save the file object in IMAP
$saved = $folder->save($object, 'file');
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving object to Kolab server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Update a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_update($file_name, $file)
{
$file_object = $this->get_file_object($file_name, $folder);
if (empty($file_object)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$key = key($file_object['_attachments']);
$file_object['_attachments'] = array(
0 => array(
'name' => $file_name,
'path' => $file['path'],
'content' => $file['content'],
'mimetype' => $file['type'],
),
$key => false,
);
// save the file object in IMAP
$saved = $folder->save($file_object, 'file', $file_object['_msguid']);
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving object to Kolab server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$deleted = $folder->delete($file);
if (!$deleted) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting object from Kolab server"),
true, false);
throw new Exception("Storage error. Deleting file failed.", file_storage::ERROR);
}
}
/**
* Return file body.
*
* @param string $file_name Name of a file (with folder path)
* @param array $params Parameters (force-download)
* @param resource $fp Print to file pointer instead (send no headers)
*
* @throws Exception
*/
public function file_get($file_name, $params = array(), $fp = null)
{
$file = $this->get_file_object($file_name, $folder, true);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
// write to file pointer, send no headers
if ($fp) {
if ($file['size']) {
$folder->get_attachment($file['_msguid'], $file['fileid'], $file['_mailbox'], false, $fp);
}
return;
}
if (!empty($params['force-download'])) {
$disposition = 'attachment';
header("Content-Type: application/octet-stream");
// @TODO
// if ($browser->ie)
// header("Content-Type: application/force-download");
}
else {
$mimetype = file_utils::real_mimetype($params['force-type'] ? $params['force-type'] : $file['type']);
$disposition = 'inline';
header("Content-Transfer-Encoding: binary");
header("Content-Type: $mimetype");
}
$filename = addcslashes($file['name'], '"');
// Workaround for nasty IE bug (#1488844)
// If Content-Disposition header contains string "attachment" e.g. in filename
// IE handles data as attachment not inline
/*
@TODO
if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) {
$filename = str_ireplace('attachment', 'attach', $filename);
}
*/
header("Content-Length: " . $file['size']);
header("Content-Disposition: $disposition; filename=\"$filename\"");
if ($file['size'] && empty($params['head'])) {
$folder->get_attachment($file['_msguid'], $file['fileid'], $file['_mailbox'], true);
}
}
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name)
{
$file = $this->get_file_object($file_name, $folder, true);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
return array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
- 'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
- 'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
+ 'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']),
+ 'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']),
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search', 'prefix')
*
* @return array List of files (file properties array indexed by filename)
* @throws Exception
*/
public function file_list($folder_name, $params = array())
{
$filter = array(array('type', '=', 'file'));
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
switch ($idx) {
case 'name':
$filter[] = array('filename', '~', $value);
break;
case 'class':
foreach (file_utils::class2mimetypes($value) as $tag) {
$for[] = array('tags', '~', ' ' . $tag);
}
$filter[] = array($for, 'OR');
break;
}
}
}
// get files list
$files = $this->get_files($folder_name, $filter);
$result = array();
// convert to kolab_storage files list data format
foreach ($files as $idx => $file) {
$file = $this->from_file_object($file);
if (!isset($file['name'])) {
continue;
}
$filename = $params['prefix'] . $folder_name . file_storage::SEPARATOR . $file['name'];
$result[$filename] = array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
- 'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
- 'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
+ 'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']),
+ 'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']),
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
unset($files[$idx]);
}
// @TODO: pagination, search (by filename, mimetype)
// Sorting
$sort = !empty($params['sort']) ? $params['sort'] : 'name';
$index = array();
if ($sort == 'mtime') {
$sort = 'modified';
}
if (in_array($sort, array('name', 'size', 'modified'))) {
foreach ($result as $key => $val) {
$index[$key] = $val[$sort];
}
array_multisort($index, SORT_ASC, SORT_NUMERIC, $result);
}
if ($params['reverse']) {
$result = array_reverse($result, true);
}
return $result;
}
/**
* Copy a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_copy($file_name, $new_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$new = $this->get_file_object($new_name, $new_folder);
if (!empty($new)) {
throw new Exception("Storage error. File exists.", file_storage::ERROR_FILE_EXISTS);
}
$file = $this->from_file_object($file);
// Save to temp file
// @TODO: use IMAP CATENATE extension
$temp_dir = unslashify($this->rc->config->get('temp_dir'));
$file_path = tempnam($temp_dir, 'rcmAttmnt');
$fh = fopen($file_path, 'w');
if (!$fh) {
throw new Exception("Storage error. File copying failed.", file_storage::ERROR);
}
if ($file['size']) {
$folder->get_attachment($file['uid'], $file['fileid'], null, false, $fh, true);
}
fclose($fh);
if (!file_exists($file_path)) {
throw new Exception("Storage error. File copying failed.", file_storage::ERROR);
}
// Update object
$file['_attachments'] = array(
0 => array(
'name' => $new_name,
'path' => $file_path,
'mimetype' => $file['type'],
'size' => $file['size'],
));
$fields = array('created', 'changed', '_attachments', 'notes', 'sensitivity', 'categories', 'x-custom');
$file = array_intersect_key($file, array_combine($fields, $fields));
$saved = $new_folder->save($file, 'file');
@unlink($file_path);
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error updating object on Kolab server"),
true, false);
throw new Exception("Storage error. File copying failed.", file_storage::ERROR);
}
}
/**
* Move (or rename) a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_move($file_name, $new_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$new = $this->get_file_object($new_name, $new_folder);
if (!empty($new)) {
throw new Exception("Storage error. File exists.", file_storage::ERROR_FILE_EXISTS);
}
// Move the file
if ($folder->name != $new_folder->name) {
$saved = $folder->move($file['uid'], $new_folder->name);
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error moving object on Kolab server"),
true, false);
throw new Exception("Storage error. File move failed.", file_storage::ERROR);
}
$folder = $new_folder;
}
if ($file_name === $new_name) {
return;
}
// Update object (changing the name)
$cid = key($file['_attachments']);
$file['_attachments'][$cid]['name'] = $new_name;
$file['_attachments'][0] = $file['_attachments'][$cid];
$file['_attachments'][$cid] = false;
$saved = $folder->save($file, 'file');
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error updating object on Kolab server"),
true, false);
throw new Exception("Storage error. File rename failed.", file_storage::ERROR);
}
}
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_create($folder_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$success = kolab_storage::folder_create($folder_name, 'file', true);
if (!$success) {
throw new Exception("Storage error. Unable to create the folder", file_storage::ERROR);
}
}
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_delete($folder_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$success = kolab_storage::folder_delete($folder_name);
if (!$success) {
throw new Exception("Storage error. Unable to delete the folder.", file_storage::ERROR);
}
}
/**
* Move/Rename a folder.
*
* @param string $folder_name Name of a folder with full path
* @param string $new_name New name of a folder with full path
*
* @throws Exception on error
*/
public function folder_move($folder_name, $new_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$new_name = rcube_charset::convert($new_name, RCUBE_CHARSET, 'UTF7-IMAP');
$success = kolab_storage::folder_rename($folder_name, $new_name);
if (!$success) {
throw new Exception("Storage error. Unable to rename the folder", file_storage::ERROR);
}
}
/**
* Subscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_subscribe($folder_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$storage = $this->rc->get_storage();
if (!$storage->subscribe($folder_name)) {
throw new Exception("Storage error. Unable to subscribe the folder", file_storage::ERROR);
}
}
/**
* Unsubscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_unsubscribe($folder_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$storage = $this->rc->get_storage();
if (!$storage->unsubscribe($folder_name)) {
throw new Exception("Storage error. Unable to unsubsribe the folder", file_storage::ERROR);
}
}
/**
* Returns list of folders.
*
* @param array $params List parameters ('type', 'search', 'extended', 'permissions')
*
* @return array List of folders
* @throws Exception
*/
public function folder_list($params = array())
{
$unsubscribed = $params['type'] & file_storage::FILTER_UNSUBSCRIBED;
$rights = ($params['type'] & file_storage::FILTER_WRITABLE) ? 'w' : null;
$imap = $this->rc->get_storage();
$folders = $imap->list_folders_subscribed('', '*', 'file', $rights);
if (!is_array($folders)) {
throw new Exception("Storage error. Unable to get folders list.", file_storage::ERROR);
}
// create/subscribe 'Files' folder in case there's no folder of type 'file'
if (empty($folders) && !$unsubscribed) {
$default = 'Files';
// the folder may exist but be unsubscribed
if (!$imap->folder_exists($default)) {
if (kolab_storage::folder_create($default, 'file', true)) {
$folders[] = $default;
}
}
else if (kolab_storage::folder_type($default) == 'file') {
if ($imap->subscribe($default)) {
$folders[] = $default;
}
}
}
else {
if ($unsubscribed) {
$subscribed = $folders;
$folders = $imap->list_folders('', '*', 'file', $rights);
$folders = array_diff($folders, $subscribed);
}
// convert folder names to UTF-8
$callback = function($folder) {
if (strpos($folder, '&') !== false) {
return rcube_charset::convert($folder, 'UTF7-IMAP', RCUBE_CHARSET);
}
return $folder;
};
$folders = array_map($callback, $folders);
}
// searching
if (isset($params['search'])) {
$search = mb_strtoupper($params['search']);
$prefix = null;
$ns = $imap->get_namespace('other');
if (!empty($ns)) {
$prefix = rcube_charset::convert($ns[0][0], 'UTF7-IMAP', RCUBE_CHARSET);
}
$folders = array_filter($folders, function($folder) use ($search, $prefix) {
$path = explode('/', $folder);
// search in folder name not the full path
if (strpos(mb_strtoupper($path[count($path)-1]), $search) !== false) {
return true;
}
// if it is an other user folder, we'll match the user name
// and return all folders of the matching user
else if (strpos($folder, $prefix) === 0 && strpos(mb_strtoupper($path[1]), $search) !== false) {
return true;
}
return false;
});
}
$folders = array_values($folders);
// In extended format we return array of arrays
if ($params['extended']) {
if (!$rights && $params['permissions']) {
// get list of known writable folders from cache
$cache_key = 'mailboxes.permissions';
$permissions = (array) $imap->get_cache($cache_key);
}
foreach ($folders as $idx => $folder_name) {
$folder = array('folder' => $folder_name);
// check if folder is readonly
if (isset($permissions)) {
if (!array_key_exists($folder_name, $permissions)) {
$acl = $this->folder_rights($folder_name);
$permissions[$folder_name] = $acl;
}
if (!($permissions[$folder_name] & file_storage::ACL_WRITE)) {
$folder['readonly'] = true;
}
}
$folders[$idx] = $folder;
}
if ($cache_key) {
$imap->update_cache($cache_key, $permissions);
}
}
return $folders;
}
/**
* Check folder rights.
*
* @param string $folder Folder name
*
* @return int Folder rights (sum of file_storage::ACL_*)
*/
public function folder_rights($folder)
{
$storage = $this->rc->get_storage();
$folder = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP');
$rights = file_storage::ACL_READ;
// get list of known writable folders from cache
$cache_key = 'mailboxes.permissions';
$permissions = (array) $storage->get_cache($cache_key);
if (array_key_exists($folder, $permissions)) {
return $permissions[$folder];
}
// For better performance, assume personal folders are writeable
if ($storage->folder_namespace($folder) == 'personal') {
$rights |= file_storage::ACL_WRITE;
}
else {
$myrights = $storage->my_rights($folder);
if (in_array('t', (array) $myrights)) {
$rights |= file_storage::ACL_WRITE;
}
$permissions[$folder] = $rights;
$storage->update_cache($cache_key, $permissions);
}
return $rights;
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $path File/folder path
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
* @throws Exception
*/
public function lock_list($path, $child_locks = false)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->path2uri($path);
// get locks list
$list = $this->lock_db->lock_list($uri, $child_locks);
// convert back resource string into URIs
foreach ($list as $idx => $lock) {
$list[$idx]['uri'] = $this->uri2path($lock['uri']);
}
return $list;
}
/**
* Locks a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
* - depth: 0/'infinite'
* - scope: 'shared'/'exclusive'
* - owner: string
* - token: string
* - timeout: int
*
* @throws Exception
*/
public function lock($path, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->path2uri($path);
if (!$this->lock_db->lock($uri, $lock)) {
throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR);
}
}
/**
* Removes a lock from a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
*
* @throws Exception
*/
public function unlock($path, $lock)
{
$this->init_lock_db();
// convert path to global resource string
$uri = $this->path2uri($path);
if (!$this->lock_db->unlock($uri, $lock)) {
throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR);
}
}
/**
* Return disk quota information for specified folder.
*
* @param string $folder_name Name of a folder with full path
*
* @return array Quota
* @throws Exception
*/
public function quota($folder)
{
$storage = $this->rc->get_storage();
$quota = $storage->get_quota();
$quota = $this->rc->plugins->exec_hook('quota', $quota);
unset($quota['abort']);
return $quota;
}
/**
* Get files from a folder (with performance fix)
*/
protected function get_files($folder, $filter, $all = true)
{
if (!($folder instanceof kolab_storage_folder)) {
$folder = $this->get_folder_object($folder);
}
// for better performance it's good to assume max. number of records
$folder->set_order_and_limit(null, $all ? 0 : 1);
return $folder->select($filter);
}
/**
* Get file object.
*
* @param string $file_name Name of a file (with folder path)
* @param kolab_storage_folder $folder Reference to folder object
* @param bool $cache Use internal cache
*
* @return array File data
* @throws Exception
*/
protected function get_file_object(&$file_name, &$folder = null, $cache = false)
{
$original_name = $file_name;
// extract file path and file name
$path = explode(file_storage::SEPARATOR, $file_name);
$file_name = array_pop($path);
$folder_name = implode(file_storage::SEPARATOR, $path);
if ($folder_name === '') {
throw new Exception("Missing folder name", file_storage::ERROR);
}
$folder = $this->get_folder_object($folder_name);
if ($cache && !empty($this->icache[$original_name])) {
return $this->icache[$original_name];
}
$filter = array(
array('type', '=', 'file'),
array('filename', '=', $file_name)
);
$files = $this->get_files($folder, $filter, false);
$file = $files[0];
if ($cache) {
$this->icache[$original_name] = $file;
}
return $file;
}
/**
* Get folder object.
*
* @param string $folder_name Name of a folder with full path
*
* @return kolab_storage_folder Folder object
* @throws Exception
*/
protected function get_folder_object($folder_name)
{
if ($folder_name === null || $folder_name === '') {
throw new Exception("Missing folder name", file_storage::ERROR);
}
if (empty($this->folders[$folder_name])) {
$storage = $this->rc->get_storage();
$separator = $storage->get_hierarchy_delimiter();
$folder_name = str_replace(file_storage::SEPARATOR, $separator, $folder_name);
$imap_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$folder = kolab_storage::get_folder($imap_name, 'file');
if (!$folder || !$folder->valid) {
$error = $folder->get_error();
if ($error === kolab_storage::ERROR_IMAP_CONN || $error === kolab_storage::ERROR_CACHE_DB) {
throw new Exception("The storage is temporarily unavailable.", file_storage::ERROR_UNAVAILABLE);
}
else if ($error === kolab_storage::ERROR_NO_PERMISSION) {
throw new Exception("Storage error. Access not permitted", file_storage::ERROR_FORBIDDEN);
}
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
$this->folders[$folder_name] = $folder;
}
return $this->folders[$folder_name];
}
/**
* Simplify internal structure of the file object
*/
protected function from_file_object($file)
{
if (empty($file['_attachments'])) {
return $file;
}
$attachment = array_shift($file['_attachments']);
$file['name'] = $attachment['name'];
$file['size'] = $attachment['size'];
$file['type'] = $attachment['mimetype'];
$file['fileid'] = $attachment['id'];
unset($file['_attachments']);
return $file;
}
/**
* Convert to kolab_format internal structure of the file object
*/
protected function to_file_object($file)
{
// @TODO if path is empty and fileid exists it is an update
// get attachment body and save it in path
$file['_attachments'] = array(
0 => array(
'name' => $file['name'],
'path' => $file['path'],
'content' => $file['content'],
'mimetype' => $file['type'],
'size' => $file['size'],
));
unset($file['name']);
unset($file['size']);
unset($file['type']);
unset($file['path']);
unset($file['fileid']);
return $file;
}
/**
* Convert file/folder path into a global URI.
*
* @param string $path File/folder path
*
* @return string URI
* @throws Exception
*/
public function path2uri($path)
{
$storage = $this->rc->get_storage();
$namespace = $storage->get_namespace();
$separator = $storage->get_hierarchy_delimiter();
$path = str_replace(file_storage::SEPARATOR, $separator, $path);
$owner = $this->rc->get_user_name();
// find the owner and remove namespace prefix
foreach ($namespace as $type => $ns) {
foreach ($ns as $root) {
if (is_array($root) && $root[0] && strpos($path, $root[0]) === 0) {
$path = substr($path, strlen($root[0]));
switch ($type) {
case 'shared':
// in theory there can be more than one shared root
// we add it to dummy user name, so we can revert conversion
$owner = "shared({$root[0]})";
break;
case 'other':
list($user, $path) = explode($separator, $path, 2);
if (strpos($user, '@') === false) {
$domain = strstr($owner, '@');
if (!empty($domain)) {
$user .= $domain;
}
}
$owner = $user;
break;
}
break 2;
}
}
}
return 'imap://' . rawurlencode($owner) . '@' . $storage->options['host']
. '/' . file_utils::encode_path($path);
}
/**
* Convert global URI into file/folder path.
*
* @param string $uri URI
*
* @return string File/folder path
* @throws Exception
*/
public function uri2path($uri)
{
if (!preg_match('|^imap://([^@]+)@([^/]+)/(.*)$|', $uri, $matches)) {
throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
}
$storage = $this->rc->get_storage();
$separator = $storage->get_hierarchy_delimiter();
$owner = $this->rc->get_user_name();
$user = rawurldecode($matches[1]);
$path = file_utils::decode_path($matches[3]);
// personal namespace
if ($user == $owner) {
// do nothing
// Note: that might not work if personal namespace uses e.g. INBOX/ prefix.
}
// shared namespace
else if (preg_match('/^shared\((.*)\)$/', $user, $matches)) {
$path = $matches[1] . $path;
}
// other users namespace
else {
$namespace = $storage->get_namespace('other');
list($local, $domain) = explode('@', $user);
// here we assume there's only one other users namespace root
$path = $namespace[0][0] . $local . $separator . $path;
}
return str_replace($separator, file_storage::SEPARATOR, $path);
}
/**
* Initializes file_locks object
*/
protected function init_lock_db()
{
if (!$this->lock_db) {
$this->lock_db = new file_locks;
}
}
}
diff --git a/lib/drivers/seafile/seafile_file_storage.php b/lib/drivers/seafile/seafile_file_storage.php
index 56771ba..2df0e06 100644
--- a/lib/drivers/seafile/seafile_file_storage.php
+++ b/lib/drivers/seafile/seafile_file_storage.php
@@ -1,1340 +1,1340 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2014, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class seafile_file_storage implements file_storage
{
/**
* @var rcube
*/
protected $rc;
/**
* @var array
*/
protected $config = array();
/**
* @var seafile_api
*/
protected $api;
/**
* List of SeaFile libraries
*
* @var array
*/
protected $libraries;
/**
* Instance title (mount point)
*
* @var string
*/
protected $title;
/**
* Class constructor
*/
public function __construct()
{
$this->rc = rcube::get_instance();
}
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @param bool True on success, False on failure
*/
public function authenticate($username, $password)
{
$this->init(true);
$token = $this->api->authenticate($username, $password);
if ($token) {
$_SESSION[$this->title . 'seafile_user'] = $username;
$_SESSION[$this->title . 'seafile_token'] = $this->rc->encrypt($token);
$_SESSION[$this->title . 'seafile_pass'] = $this->rc->encrypt($password);
return true;
}
$this->api = false;
return false;
}
/**
* Get password and name of authenticated user
*
* @return array Authenticated user data
*/
public function auth_info()
{
return array(
'username' => $_SESSION[$this->title . 'seafile_user'],
'password' => $this->rc->decrypt($_SESSION[$this->title . 'seafile_pass']),
);
}
/**
* Initialize SeaFile Web API connection
*/
protected function init($skip_auth = false)
{
if ($this->api !== null) {
return $this->api !== false;
}
// read configuration
$config = array(
'host' => $this->rc->config->get('fileapi_seafile_host', 'localhost'),
'ssl_verify_peer' => $this->rc->config->get('fileapi_seafile_ssl_verify_peer', true),
'ssl_verify_host' => $this->rc->config->get('fileapi_seafile_ssl_verify_host', true),
'cache' => $this->rc->config->get('fileapi_seafile_cache'),
'cache_ttl' => $this->rc->config->get('fileapi_seafile_cache_ttl', '14d'),
'debug' => $this->rc->config->get('fileapi_seafile_debug', false),
);
$this->config = array_merge($config, $this->config);
// initialize Web API
$this->api = new seafile_api($this->config);
if ($skip_auth) {
return true;
}
// try session token
if ($_SESSION[$this->title . 'seafile_token']
&& ($token = $this->rc->decrypt($_SESSION[$this->title . 'seafile_token']))
) {
$valid = $this->api->ping($token);
}
if (!$valid) {
// already authenticated in session
if ($_SESSION[$this->title . 'seafile_user']) {
$user = $_SESSION[$this->title . 'seafile_user'];
$pass = $this->rc->decrypt($_SESSION[$this->title . 'seafile_pass']);
}
// try user/pass of the main driver
else {
$user = $this->config['username'];
$pass = $this->config['password'];
}
if ($user) {
$valid = $this->authenticate($user, $pass);
}
}
// throw special exception, so we can ask user for the credentials
if (!$valid && empty($_SESSION[$this->title . 'seafile_user'])) {
throw new Exception("User credentials not provided", file_storage::ERROR_NOAUTH);
}
else if (!$valid && $this->api->is_error() == seafile_api::TOO_MANY_REQUESTS) {
throw new Exception("SeaFile storage temporarily unavailable (too many requests)", file_storage::ERROR);
}
return $valid;
}
/**
* Configures environment
*
* @param array $config Configuration
* @param string $title Source identifier
*/
public function configure($config, $title = null)
{
$this->config = array_merge($this->config, $config);
$this->title = $title;
}
/**
* Returns current instance title
*
* @return string Instance title (mount point)
*/
public function title()
{
return $this->title;
}
/**
* Storage driver capabilities
*
* @return array List of capabilities
*/
public function capabilities()
{
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize) {
$max_filesize = $max_postsize;
}
return array(
file_storage::CAPS_MAX_UPLOAD => $max_filesize,
file_storage::CAPS_QUOTA => true,
file_storage::CAPS_LOCKS => true,
);
}
/**
* Save configuration of external driver (mount point)
*
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_create($driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Delete configuration of external driver (mount point)
*
* @param string $title Driver instance name
*
* @throws Exception
*/
public function driver_delete($title)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Return list of registered drivers (mount points)
*
* @return array List of drivers data
* @throws Exception
*/
public function driver_list()
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Update configuration of external driver (mount point)
*
* @param string $title Driver instance name
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_update($title, $driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns metadata of the driver
*
* @return array Driver meta data (image, name, form)
*/
public function driver_metadata()
{
$image_content = file_get_contents(__DIR__ . '/seafile.png');
$metadata = array(
'image' => 'data:image/png;base64,' . base64_encode($image_content),
'name' => 'SeaFile',
'ref' => 'http://seafile.com',
'description' => 'Storage implementing SeaFile API access',
'form' => array(
'host' => 'hostname',
'username' => 'username',
'password' => 'password',
),
);
// these are returned when authentication on folders list fails
if ($this->config['username']) {
$metadata['form_values'] = array(
'host' => $this->config['host'],
'username' => $this->config['username'],
);
}
return $metadata;
}
/**
* Validate metadata (config) of the driver
*
* @param array $metadata Driver metadata
*
* @return array Driver meta data to be stored in configuration
* @throws Exception
*/
public function driver_validate($metadata)
{
if (!is_string($metadata['username']) || !strlen($metadata['username'])) {
throw new Exception("Missing user name.", file_storage::ERROR);
}
if (!is_string($metadata['password']) || !strlen($metadata['password'])) {
throw new Exception("Missing user password.", file_storage::ERROR);
}
if (!is_string($metadata['host']) || !strlen($metadata['host'])) {
throw new Exception("Missing host name.", file_storage::ERROR);
}
$this->config['host'] = $metadata['host'];
if (!$this->authenticate($metadata['username'], $metadata['password'])) {
throw new Exception("Unable to authenticate user", file_storage::ERROR_NOAUTH);
}
return array(
'host' => $metadata['host'],
'username' => $metadata['username'],
'password' => $metadata['password'],
);
}
/**
* Create a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_create($file_name, $file)
{
list($fn, $repo_id) = $this->find_library($file_name);
if (empty($repo_id)) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
if ($file['path']) {
$file['data'] = $file['path'];
}
else if (is_resource($file['content'])) {
$file['data'] = $file['content'];
}
else {
$fp = fopen('php://temp', 'wb');
fwrite($fp, $file['content'], strlen($file['content']));
$file['data'] = $fp;
unset($file['content']);
}
$created = $this->api->file_upload($repo_id, $fn, $file);
if ($fp) {
fclose($fp);
}
if (!$created) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving file to SeaFile server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Update a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_update($file_name, $file)
{
list($fn, $repo_id) = $this->find_library($file_name);
if (empty($repo_id)) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
if ($file['path']) {
$file['data'] = $file['path'];
}
else if (is_resource($file['content'])) {
$file['data'] = $file['content'];
}
else {
$fp = fopen('php://temp', 'wb');
fwrite($fp, $file['content'], strlen($file['content']));
$file['data'] = $fp;
unset($file['content']);
}
$saved = $this->api->file_update($repo_id, $fn, $file);
if ($fp) {
fclose($fp);
}
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving file to SeaFile server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name)
{
list($file_name, $repo_id) = $this->find_library($file_name);
if ($repo_id && $file_name != '/') {
$deleted = $this->api->file_delete($repo_id, $file_name);
}
if (!$deleted) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting object from SeaFile server"),
true, false);
throw new Exception("Storage error. Deleting file failed.", file_storage::ERROR);
}
}
/**
* Return file body.
*
* @param string $file_name Name of a file (with folder path)
* @param array $params Parameters (force-download, force-type, head)
* @param resource $fp Print to file pointer instead (send no headers)
*
* @throws Exception
*/
public function file_get($file_name, $params = array(), $fp = null)
{
list($fn, $repo_id) = $this->find_library($file_name);
$file = $this->api->file_info($repo_id, $fn);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
// get file location on SeaFile server for download
if ($file['size'] && empty($params['head'])) {
$link = $this->api->file_get($repo_id, $fn);
}
// write to file pointer, send no headers
if ($fp) {
if ($file['size']) {
$this->save_file_content($link, $fp);
}
return;
}
if (!empty($params['force-download'])) {
$disposition = 'attachment';
header("Content-Type: application/octet-stream");
// @TODO
// if ($browser->ie)
// header("Content-Type: application/force-download");
}
else {
$mimetype = file_utils::real_mimetype($params['force-type'] ? $params['force-type'] : $file['type']);
$disposition = 'inline';
header("Content-Transfer-Encoding: binary");
header("Content-Type: $mimetype");
}
$filename = addcslashes($file['name'], '"');
// Workaround for nasty IE bug (#1488844)
// If Content-Disposition header contains string "attachment" e.g. in filename
// IE handles data as attachment not inline
/*
@TODO
if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) {
$filename = str_ireplace('attachment', 'attach', $filename);
}
*/
header("Content-Length: " . $file['size']);
header("Content-Disposition: $disposition; filename=\"$filename\"");
// just send redirect to SeaFile server
if ($file['size'] && empty($params['head'])) {
// In view-mode we can't redirect to SeaFile server because:
// - it responds with Content-Disposition: attachment, which causes that
// e.g. previewing images is not possible
// - pdf/odf viewers can't follow redirects for some reason (#4590)
if (empty($params['force-download'])) {
if ($fp = fopen('php://output', 'wb')) {
$this->save_file_content($link, $fp);
fclose($fp);
die;
}
}
header("Location: $link");
}
}
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name)
{
list($file, $repo_id) = $this->find_library($file_name);
$file = $this->api->file_info($repo_id, $file);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
return array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
- 'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
- 'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
+ 'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']),
+ 'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']),
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search', 'prefix')
*
* @return array List of files (file properties array indexed by filename)
* @throws Exception
*/
public function file_list($folder_name, $params = array())
{
list($folder, $repo_id) = $this->find_library($folder_name);
// prepare search filter
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
if ($idx == 'name') {
$params['search'][$idx] = mb_strtoupper($value);
}
else if ($idx == 'class') {
$params['search'][$idx] = file_utils::class2mimetypes($value);
}
}
}
// get directory entries
$entries = $this->api->directory_entries($repo_id, $folder, 'file');
$result = array();
foreach ((array) $entries as $idx => $file) {
if ($file['type'] != 'file') {
continue;
}
$file = $this->from_file_object($file);
// search filter
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
if ($idx == 'name') {
if (strpos(mb_strtoupper($file['name']), $value) === false) {
continue 2;
}
}
else if ($idx == 'class') {
foreach ($value as $v) {
if (stripos($file['type'], $v) !== false) {
continue 2;
}
}
continue 2;
}
}
}
$filename = $params['prefix'] . $folder_name . file_storage::SEPARATOR . $file['name'];
$result[$filename] = array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
- 'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
- 'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
+ 'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']),
+ 'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']),
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
unset($entries[$idx]);
}
// @TODO: pagination, search (by filename, mimetype)
// Sorting
$sort = !empty($params['sort']) ? $params['sort'] : 'name';
$index = array();
if ($sort == 'mtime') {
$sort = 'modified';
}
if (in_array($sort, array('name', 'size', 'modified'))) {
foreach ($result as $key => $val) {
$index[$key] = $val[$sort];
}
array_multisort($index, SORT_ASC, SORT_NUMERIC, $result);
}
if ($params['reverse']) {
$result = array_reverse($result, true);
}
return $result;
}
/**
* Copy a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_copy($file_name, $new_name)
{
list($src_name, $repo_id) = $this->find_library($file_name);
list($dst_name, $dst_repo_id) = $this->find_library($new_name);
if ($repo_id && $dst_repo_id) {
$path_src = explode('/', $src_name);
$path_dst = explode('/', $dst_name);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = '/' . ltrim(implode('/', $path_src), '/');
$dst_dir = '/' . ltrim(implode('/', $path_dst), '/');
$success = $this->api->file_copy($repo_id, $f_src, $src_dir, $dst_dir, $dst_repo_id);
// now rename the file if needed
if ($success && $f_src != $f_dst) {
$success = $this->api->file_rename($dst_repo_id, rtrim($dst_dir, '/') . '/' . $f_src, $f_dst);
}
}
if (!$success) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error copying file on SeaFile server"),
true, false);
throw new Exception("Storage error. File copying failed.", file_storage::ERROR);
}
}
/**
* Move (or rename) a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_move($file_name, $new_name)
{
list($src_name, $repo_id) = $this->find_library($file_name);
list($dst_name, $dst_repo_id) = $this->find_library($new_name);
if ($repo_id && $dst_repo_id) {
$path_src = explode('/', $src_name);
$path_dst = explode('/', $dst_name);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = '/' . ltrim(implode('/', $path_src), '/');
$dst_dir = '/' . ltrim(implode('/', $path_dst), '/');
if ($src_dir == $dst_dir && $repo_id == $dst_repo_id) {
$success = true;
}
else {
$success = $this->api->file_move($repo_id, $src_name, $dst_dir, $dst_repo_id);
}
// now rename the file if needed
if ($success && $f_src != $f_dst) {
$success = $this->api->file_rename($dst_repo_id, rtrim($dst_dir, '/') . '/' . $f_src, $f_dst);
}
}
if (!$success) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error moving file on SeaFile server"),
true, false);
throw new Exception("Storage error. File rename failed.", file_storage::ERROR);
}
}
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_create($folder_name)
{
list($folder, $repo_id) = $this->find_library($folder_name, true);
if (empty($repo_id)) {
$success = $this->api->library_create($folder_name);
}
else if ($folder != '/') {
$success = $this->api->directory_create($repo_id, $folder);
}
if (!$success) {
throw new Exception("Storage error. Unable to create folder", file_storage::ERROR);
}
// clear the cache
if (empty($repo_id)) {
$this->libraries = null;
}
}
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_delete($folder_name)
{
list($folder, $repo_id) = $this->find_library($folder_name, true);
if ($repo_id && $folder == '/') {
$success = $this->api->library_delete($repo_id);
}
else if ($repo_id) {
$success = $this->api->directory_delete($repo_id, $folder);
}
if (!$success) {
throw new Exception("Storage error. Unable to delete folder.", file_storage::ERROR);
}
}
/**
* Move/Rename a folder.
*
* @param string $folder_name Name of a folder with full path
* @param string $new_name New name of a folder with full path
*
* @throws Exception on error
*/
public function folder_move($folder_name, $new_name)
{
list($folder, $repo_id, $library) = $this->find_library($folder_name, true);
list($dest_folder, $dest_repo_id) = $this->find_library($new_name, true);
// folders rename/move is possible only in the same library and folder
// @TODO: support folder move between libraries and folders
// @TODO: support converting library into a folder and vice-versa
// library rename
if ($repo_id && !$dest_repo_id && $folder == '/' && strpos($new_name, '/') === false) {
$success = $this->api->library_rename($repo_id, $new_name, $library['desc']);
}
// folder rename
else if ($folder != '/' && $dest_folder != '/' && $repo_id && $repo_id == $dest_repo_id) {
$path_src = explode('/', $folder);
$path_dst = explode('/', $dest_folder);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = implode('/', $path_src);
$dst_dir = implode('/', $path_dst);
if ($src_dir == $dst_dir) {
$success = $this->api->directory_rename($repo_id, $folder, $f_dst);
}
}
if (!$success) {
throw new Exception("Storage error. Unable to rename/move folder", file_storage::ERROR);
}
}
/**
* Subscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_subscribe($folder_name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Unsubscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_unsubscribe($folder_name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns list of folders.
*
* @param array $params List parameters ('type', 'search')
*
* @return array List of folders
* @throws Exception
*/
public function folder_list($params = array())
{
$writable = ($params['type'] & file_storage::FILTER_WRITABLE) ? true : false;
$libraries = $this->libraries();
$folders = array();
if ($this->config['cache']) {
$cache = $this->rc->get_cache('seafile_' . $this->title,
$this->config['cache'], $this->config['cache_ttl'], true);
if ($cache) {
$cached = $cache->get('folders');
}
}
foreach ($libraries as $library) {
if ($library['virtual'] || $library['encrypted']) {
continue;
}
if (strpos($library['permission'], 'w') === false) {
$readonly_prefixes[] = $library['name'];
}
$folders[$library['name']] = array(
'mtime' => $library['mtime'],
'permission' => $library['permission'],
);
if ($folder_tree = $this->folders_tree($library, '', $library, $cached)) {
$folders = array_merge($folders, $folder_tree);
}
}
if (empty($folders)) {
throw new Exception("Storage error. Unable to get folders list.", file_storage::ERROR);
}
if ($cache && $cached != $folders) {
$cache->set('folders', $folders);
}
// remove read-only folders when requested
if ($writable) {
foreach ($folders as $folder_name => $folder) {
if (strpos($folder['permission'], 'w') === false) {
unset($folders[$folder_name]);
}
}
}
// In extended format we return array of arrays
if (!empty($params['extended'])) {
foreach ($folders as $folder_name => $folder) {
$item = array('folder' => $folder_name);
// check if folder is readonly
if (!$writable && $params['permissions']) {
if (strpos($folder['permission'], 'w') === false) {
$item['readonly'] = true;
}
}
$folders[$folder_name] = $item;
}
}
else {
$folders = array_keys($folders);
}
// sort folders
usort($folders, array('file_utils', 'sort_folder_comparator'));
return $folders;
}
/**
* Check folder rights.
*
* @param string $folder_name Name of a folder with full path
*
* @return int Folder rights (sum of file_storage::ACL_*)
*/
public function folder_rights($folder_name)
{
// It is not possible (yet) to assign a specified library/folder
// to the mount point. So, it is a "virtual" folder.
if (!strlen($folder_name)) {
return 0;
}
list($folder, $repo_id, $library) = $this->find_library($folder_name);
// @TODO: we should check directory permission not library
// However, there's no API for this, we'd need to get a list
// of directories of a parent folder/library
/*
if (strpos($folder, '/')) {
// @TODO
}
else {
$acl = $library['permission'];
}
*/
$acl = $library['permission'];
$rights = 0;
$map = array(
'r' => file_storage::ACL_READ,
'w' => file_storage::ACL_WRITE,
);
foreach ($map as $key => $value) {
if (strpos($acl, $key) !== false) {
$rights |= $value;
}
}
return $rights;
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $path File/folder path
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
* @throws Exception
*/
public function lock_list($path, $child_locks = false)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->path2uri($path);
// get locks list
$list = $this->lock_db->lock_list($uri, $child_locks);
// convert back resource string into URIs
foreach ($list as $idx => $lock) {
$list[$idx]['uri'] = $this->uri2path($lock['uri']);
}
return $list;
}
/**
* Locks a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
* - depth: 0/'infinite'
* - scope: 'shared'/'exclusive'
* - owner: string
* - token: string
* - timeout: int
*
* @throws Exception
*/
public function lock($path, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($path);
if (!$this->lock_db->lock($uri, $lock)) {
throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR);
}
}
/**
* Removes a lock from a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
*
* @throws Exception
*/
public function unlock($path, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->path2uri($path);
if (!$this->lock_db->unlock($uri, $lock)) {
throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR);
}
}
/**
* Return disk quota information for specified folder.
*
* @param string $folder_name Name of a folder with full path
*
* @return array Quota
* @throws Exception
*/
public function quota($folder)
{
if (!$this->init()) {
throw new Exception("Storage error. Unable to get SeaFile account info.", file_storage::ERROR);
}
$account_info = $this->api->account_info();
if (empty($account_info)) {
throw new Exception("Storage error. Unable to get SeaFile account info.", file_storage::ERROR);
}
$quota = array(
// expected values in kB
'total' => intval($account_info['total'] / 1024),
'used' => intval($account_info['usage'] / 1024),
);
return $quota;
}
/**
* Recursively builds folders list
*/
protected function folders_tree($library, $path, $folder, $cached)
{
$folders = array();
$fname = strlen($path) ? $path . $folder['name'] : '/';
$root = $library['name'] . ($fname != '/' ? $fname : '');
// nothing changed, use cached folders tree of this folder
if ($cached && is_array($cached[$root]) && $cached[$root]['mtime'] == $folder['mtime']) {
foreach ($cached as $folder_name => $f) {
if (strpos($folder_name, $root . '/') === 0) {
$folders[$folder_name] = array(
'mtime' => $f['mtime'],
'permission' => $f['permission'],
);
}
}
}
// get folder content (files and sub-folders)
// there's no API method to get only folders
else if ($content = $this->api->directory_entries($library['id'], $fname, 'dir')) {
if ($fname != '/') {
$fname .= '/';
}
foreach ($content as $item) {
if ($item['type'] == 'dir' && strlen($item['name'])) {
$folders[$root . '/' . $item['name']] = array(
'mtime' => $item['mtime'],
'permission' => $item['permission'],
);
// get subfolders recursively
$folders_tree = $this->folders_tree($library, $fname, $item, $cached);
if (!empty($folders_tree)) {
$folders = array_merge($folders, $folders_tree);
}
}
}
}
return $folders;
}
/**
* Get list of SeaFile libraries
*/
protected function libraries()
{
// get from memory, @TODO: cache in rcube_cache?
if ($this->libraries !== null) {
return $this->libraries;
}
if (!$this->init()) {
throw new Exception("Storage error. Unable to get list of SeaFile libraries.", file_storage::ERROR);
}
if ($list = $this->api->library_list()) {
$this->libraries = $list;
}
else {
$this->libraries = array();
}
return $this->libraries;
}
/**
* Find library ID from folder name
*/
protected function find_library($folder_name, $no_exception = false)
{
$libraries = $this->libraries();
foreach ($libraries as $lib) {
$path = $lib['name'] . '/';
if ($folder_name == $lib['name'] || strpos($folder_name, $path) === 0) {
if (empty($library) || strlen($library['name']) < strlen($lib['name'])) {
$library = $lib;
}
}
}
if (empty($library)) {
if (!$no_exception) {
throw new Exception("Storage error. Library not found.", file_storage::ERROR);
}
}
else {
$folder = substr($folder_name, strlen($library['name']) + 1);
}
return array(
'/' . ($folder ? $folder : ''),
$library['id'],
$library
);
}
/**
* Get file object.
*
* @param string $file_name Name of a file (with folder path)
* @param kolab_storage_folder $folder Reference to folder object
*
* @return array File data
* @throws Exception
*/
protected function get_file_object(&$file_name, &$folder = null)
{
// extract file path and file name
$path = explode(file_storage::SEPARATOR, $file_name);
$file_name = array_pop($path);
$folder_name = implode(file_storage::SEPARATOR, $path);
if ($folder_name === '') {
throw new Exception("Missing folder name", file_storage::ERROR);
}
// get folder object
$folder = $this->get_folder_object($folder_name);
$files = $folder->select(array(
array('type', '=', 'file'),
array('filename', '=', $file_name)
));
return $files[0];
}
/**
* Simplify internal structure of the file object
*/
protected function from_file_object($file)
{
if ($file['type'] != 'file') {
return null;
}
// file modification time
if ($file['mtime']) {
try {
$file['changed'] = new DateTime('@' . $file['mtime']);
}
catch (Exception $e) { }
}
// find file mimetype from extension
$file['type'] = file_utils::ext_to_type($file['name']);
unset($file['id']);
unset($file['mtime']);
return $file;
}
/**
* Save remote file into file pointer
*/
protected function save_file_content($location, $fp)
{
if (!$fp || !$location) {
return false;
}
$config = array_merge($this->config, array('store_bodies' => true));
$request = seafile_api::http_request($config);
if (!$request) {
return false;
}
$observer = new seafile_request_observer();
$observer->set_fp($fp);
try {
$request->setUrl($location);
$request->attach($observer);
$response = $request->send();
$status = $response->getStatus();
$response->getBody(); // returns nothing
$request->detach($observer);
if ($status != 200) {
throw new Exception("Unable to save file. Status $status.");
}
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
return false;
}
return true;
}
/**
* Convert file/folder path into a global URI.
*
* @param string $path File/folder path
*
* @return string URI
* @throws Exception
*/
public function path2uri($path)
{
list($file, $repo_id, $library) = $this->find_library($path);
$host = preg_replace('|https?://|i', '', $this->config['host']);
return 'seafile://' . rawurlencode($library['owner']) . '@' . $host . '/' . file_utils::encode_path($path);
}
/**
* Convert global URI into file/folder path.
*
* @param string $uri URI
*
* @return string File/folder path
* @throws Exception
*/
public function uri2path($uri)
{
if (!preg_match('|^seafile://([^@]+)@([^/]+)/(.*)$|', $uri, $matches)) {
throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
}
$user = rawurldecode($matches[1]);
$host = $matches[2];
$path = file_utils::decode_path($matches[3]);
$c_host = preg_replace('|https?://|i', '', $this->config['host']);
list($file, $repo_id, $library) = $this->find_library($path, true);
if (empty($library) || $host != $c_host || $user != $library['owner']) {
throw new Exception("Internal storage error. Unresolvable URI.", file_storage::ERROR);
}
return $path;
}
/**
* Initializes file_locks object
*/
protected function init_lock_db()
{
if (!$this->lock_db) {
$this->lock_db = new file_locks;
}
}
}
diff --git a/lib/drivers/webdav/webdav_file_storage.php b/lib/drivers/webdav/webdav_file_storage.php
index cd25d0f..c06fce7 100644
--- a/lib/drivers/webdav/webdav_file_storage.php
+++ b/lib/drivers/webdav/webdav_file_storage.php
@@ -1,1026 +1,1026 @@
<?php
/**
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2015, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
// try SabreDAV installed as described in README
if (file_exists(__DIR__ . '/../../ext/SabreDAV/vendor/autoload.php')) {
require __DIR__ . '/../../ext/SabreDAV/vendor/autoload.php';
}
// fallback to System-installed package
else {
require 'Sabre/autoload.php';
}
use Sabre\DAV\Client;
class webdav_file_storage implements file_storage
{
/**
* @var rcube
*/
protected $rc;
/**
* @var array
*/
protected $config = array();
/**
* @var string
*/
protected $title;
/**
* @var Sabre\DAV\Client
*/
protected $client;
/**
* Class constructor
*/
public function __construct()
{
$this->rc = rcube::get_instance();
}
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @param bool True on success, False on failure
*/
public function authenticate($username, $password)
{
$settings = array(
'baseUri' => $this->config['baseuri'],
'userName' => $username,
'password' => $password,
'authType' => Client::AUTH_BASIC,
);
$client = new Client($settings);
try {
$client->propfind('', array());
}
catch (Exception $e) {
return false;
}
if ($this->title) {
$_SESSION[$this->title . '_webdav_user'] = $username;
$_SESSION[$this->title . '_webdav_pass'] = $this->rc->encrypt($password);
$this->client = $client;
}
return true;
}
/**
* Get password and name of authenticated user
*
* @return array Authenticated user data
*/
public function auth_info()
{
return array(
'username' => $this->config['username'],
'password' => $this->config['password'],
);
}
/**
* Configures environment
*
* @param array $config Configuration
* @param string $title Source identifier
*/
public function configure($config, $title = null)
{
if (!empty($config['host'])) {
$config['baseuri'] = $config['host'];
}
$this->config = array_merge($this->config, $config);
$this->title = $title;
}
/**
* Initializes WebDAV client
*/
protected function init()
{
if ($this->client !== null) {
return true;
}
// Load configuration for main driver
$config['baseuri'] = $this->rc->config->get('fileapi_webdav_baseuri');
if (!empty($config['baseuri'])) {
$config['username'] = $_SESSION['username'];
$config['password'] = $this->rc->decrypt($_SESSION['password']);
}
$this->config = array_merge($config, $this->config);
// Use session username if not set in configuration
if (!isset($this->config['username'])) {
$this->config['username'] = $_SESSION[$this->title . '_webdav_user'];
}
if (!isset($this->config['password'])) {
$this->config['password'] = $this->rc->decrypt($_SESSION[$this->title . '_webdav_pass']);
}
if (empty($this->config['baseuri'])) {
throw new Exception("Missing base URI of WebDAV server", file_storage::ERROR_NOAUTH);
}
$this->client = new Client(array(
'baseUri' => $this->config['baseuri'],
'userName' => $this->config['username'],
'password' => $this->config['password'],
'authType' => Client::AUTH_BASIC,
));
}
/**
* Returns current instance title
*
* @return string Instance title (mount point)
*/
public function title()
{
return $this->title;
}
/**
* Storage driver capabilities
*
* @return array List of capabilities
*/
public function capabilities()
{
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize) {
$max_filesize = $max_postsize;
}
return array(
file_storage::CAPS_MAX_UPLOAD => $max_filesize,
file_storage::CAPS_QUOTA => true,
file_storage::CAPS_LOCKS => true, //TODO: Implement WebDAV locks
);
}
/**
* Save configuration of external driver (mount point)
*
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_create($driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Delete configuration of external driver (mount point)
*
* @param string $name Driver instance name
*
* @throws Exception
*/
public function driver_delete($name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Return list of registered drivers (mount points)
*
* @return array List of drivers data
* @throws Exception
*/
public function driver_list()
{
return array();
//TODO: Stub. Not implemented.
}
/**
* Update configuration of external driver (mount point)
*
* @param string $title Driver instance title
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_update($title, $driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns metadata of the driver
*
* @return array Driver meta data (image, name, form)
*/
public function driver_metadata()
{
$image_content = file_get_contents(__DIR__ . '/webdav.png');
$metadata = array(
'image' => 'data:image/png;base64,' . base64_encode($image_content),
'name' => 'WebDAV',
'ref' => 'http://www.webdav.org/',
'description' => 'WebDAV client',
'form' => array(
'baseuri' => 'baseuri',
'username' => 'username',
'password' => 'password',
),
);
// these are returned when authentication on folders list fails
if ($this->config['username']) {
$metadata['form_values'] = array(
'baseuri' => $this->config['baseuri'],
'username' => $this->config['username'],
);
}
return $metadata;
}
/**
* Validate metadata (config) of the driver
*
* @param array $metadata Driver metadata
*
* @return array Driver meta data to be stored in configuration
* @throws Exception
*/
public function driver_validate($metadata)
{
if (!is_string($metadata['username']) || !strlen($metadata['username'])) {
throw new Exception("Missing user name.", file_storage::ERROR);
}
if (!is_string($metadata['password']) || !strlen($metadata['password'])) {
throw new Exception("Missing user password.", file_storage::ERROR);
}
if (!is_string($metadata['baseuri']) || !strlen($metadata['baseuri'])) {
throw new Exception("Missing base URL.", file_storage::ERROR);
}
// Ensure baseUri ends with a slash
$base_uri = $metadata['baseuri'];
if (substr($base_uri, -1) != '/') {
$base_uri .= '/';
}
$this->config['baseuri'] = $base_uri;
if (!$this->authenticate($metadata['username'], $metadata['password'])) {
throw new Exception("Unable to authenticate user", file_storage::ERROR_NOAUTH);
}
return array(
'host' => $base_uri,
'port' => 0,
'username' => $metadata['username'],
'password' => $metadata['password'],
);
}
/**
* Create a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_create($file_name, $file)
{
$this->init();
if ($file['path']) {
$data = fopen($file['path'], 'r');
}
else {
// Resource or data
$data = $file['content'];
}
$file_name = $this->encode_path($file_name);
$response = $this->client->request('PUT', $file_name, $data);
if ($response['statusCode'] != 201) {
throw new Exception("Storage error. " . $response['body'], file_storage::ERROR);
}
}
/**
* Update a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_update($file_name, $file)
{
$this->init();
if ($file['path']) {
$data = fopen($file['path'], 'r');
}
else {
//Resource or data
$data = $file['content'];
}
$file_name = $this->encode_path($file_name);
$response = $this->client->request('PUT', $file_name, $data);
if ($response['statusCode'] != 204) {
throw new Exception("Storage error. " . $response['body'], file_storage::ERROR);
}
}
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name)
{
$this->init();
$file_name = $this->encode_path($file_name);
$response = $this->client->request('DELETE', $file_name);
if ($response['statusCode'] != 204) {
throw new Exception("Storage error: " . $response['body'], file_storage::ERROR);
}
}
/**
* Return file body.
*
* @param string $file_name Name of a file (with folder path)
* @param array $params Parameters (force-download)
* @param resource $fp Print to file pointer instead (send no headers)
*
* @throws Exception
*/
public function file_get($file_name, $params = array(), $fp = null)
{
$this->init();
// @TODO: Write directly to $fp
$file_name = $this->encode_path($file_name);
$response = $this->client->request($params['head'] ? 'HEAD' : 'GET', $file_name);
if ($response['statusCode'] != 200) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$size = $response['headers']['content-length'];
// write to file pointer, send no headers
if ($fp) {
if ($size) {
fwrite($fp, $response['body']);
}
return;
}
if (!empty($params['force-download'])) {
$disposition = 'attachment';
header("Content-Type: application/octet-stream");
// @TODO
// if ($browser->ie)
// header("Content-Type: application/force-download");
}
else {
$mimetype = file_utils::real_mimetype($params['force-type'] ? $params['force-type'] : $response['headers']['content-type']);
$disposition = 'inline';
header("Content-Transfer-Encoding: binary");
header("Content-Type: $mimetype");
}
$filename = addcslashes(end(explode('/', $file_name)), '"');
// Workaround for nasty IE bug (#1488844)
// If Content-Disposition header contains string "attachment" e.g. in filename
// IE handles data as attachment not inline
/*
@TODO
if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) {
$filename = str_ireplace('attachment', 'attach', $filename);
}
*/
header("Content-Length: " . $size);
header("Content-Disposition: $disposition; filename=\"$filename\"");
if ($size && empty($params['head'])) {
echo $response['body'];
}
}
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name)
{
$this->init();
try {
$props = $this->client->propfind($this->encode_path($file_name), array(
'{DAV:}resourcetype',
'{DAV:}getcontentlength',
'{DAV:}getcontenttype',
'{DAV:}getlastmodified',
'{DAV:}creationdate'
), 0);
}
catch (Exception $e) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$mtime = new DateTime($props['{DAV:}getlastmodified']);
$ctime = new DateTime($props['{DAV:}creationdate']);
return array (
'name' => end(explode('/', $file_name)),
'size' => (int) $props['{DAV:}getcontentlength'],
'type' => (string) $props['{DAV:}getcontenttype'],
- 'mtime' => $mtime ? $mtime->format($this->config['date_format']) : '',
- 'ctime' => $ctime ? $ctime->format($this->config['date_format']) : '',
+ 'mtime' => file_utils::date_format($mtime, $this->config['date_format'], $this->config['timezone']),
+ 'ctime' => file_utils::date_format($ctime, $this->config['date_format'], $this->config['timezone']),
'modified' => $mtime ? $mtime->format('U') : 0,
'created' => $ctime ? $ctime->format('U') : 0,
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search', 'prefix')
*
* @return array List of files (file properties array indexed by filename)
* @throws Exception
*/
public function file_list($folder_name, $params = array())
{
$this->init();
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
switch ($idx) {
case 'name':
$params['search']['name'] = mb_strtoupper($value);
break;
case 'class':
$params['search']['class'] = file_utils::class2mimetypes($params['search']['class']);
break;
}
}
}
try {
$items = $this->client->propfind($this->encode_path($folder_name), array(
'{DAV:}resourcetype',
'{DAV:}getcontentlength',
'{DAV:}getcontenttype',
'{DAV:}getlastmodified',
'{DAV:}creationdate'
), 1);
}
catch (Exception $e) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
$result = array();
foreach ($items as $file => $props) {
//Skip directories
$is_dir = in_array('{DAV:}collection', $props['{DAV:}resourcetype']->resourceType);
if ($is_dir) {
continue;
}
$mtime = new DateTime($props['{DAV:}getlastmodified']);
$ctime = new DateTime($props['{DAV:}creationdate']);
$ctype = (string) $props['{DAV:}getcontenttype'];
$path = $this->get_full_url($file);
$path = $this->decode_path($path);
$fname = end(explode('/', $path));
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
switch ($idx) {
case 'name':
if (stripos(mb_strtoupper($fname), $value) === false) {
continue 3; // skip the file
}
break;
case 'class':
foreach ($value as $type) {
if (stripos($ctype, $type) !== false) {
continue 3;
}
}
continue 3; // skip the file
break;
}
}
}
$result[$path] = array(
'name' => $fname,
'size' => (int) $props['{DAV:}getcontentlength'],
'type' => $ctype,
- 'mtime' => $mtime ? $mtime->format($this->config['date_format']) : '',
- 'ctime' => $ctime ? $ctime->format($this->config['date_format']) : '',
+ 'mtime' => file_utils::date_format($mtime, $this->config['date_format'], $this->config['timezone']),
+ 'ctime' => file_utils::date_format($ctime, $this->config['date_format'], $this->config['timezone']),
'modified' => $mtime ? $mtime->format('U') : 0,
'created' => $ctime ? $ctime->format('U') : 0,
);
}
// @TODO: pagination
// Sorting
$sort = !empty($params['sort']) ? $params['sort'] : 'name';
$index = array();
if ($sort == 'mtime') {
$sort = 'modified';
}
if (in_array($sort, array('name', 'size', 'modified'))) {
foreach ($result as $key => $val) {
$index[$key] = $val[$sort];
}
array_multisort($index, SORT_ASC, SORT_NUMERIC, $result);
}
if ($params['reverse']) {
$result = array_reverse($result, true);
}
return $result;
}
/**
* Copy a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_copy($file_name, $new_name)
{
$this->init();
$request = array('Destination' => $this->config['baseuri'] . '/' . rawurlencode($new_name));
$file_name = $this->encode_path($file_name);
$response = $this->client->request('COPY', $file_name, null, $request);
if ($response['statusCode'] != 201) {
throw new Exception("Storage error: " . $response['body'], file_storage::ERROR);
}
}
/**
* Move (or rename) a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_move($file_name, $new_name)
{
$this->init();
$request = array('Destination' => $this->config['baseuri'] . '/' . rawurlencode($new_name));
$file_name = $this->encode_path($file_name);
$response = $this->client->request('MOVE', $file_name, null, $request);
if ($response['statusCode'] != 201) {
throw new Exception("Storage error: " . $response['body'], file_storage::ERROR);
}
}
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_create($folder_name)
{
$this->init();
$folder_name = $this->encode_path($folder_name);
$response = $this->client->request('MKCOL', $folder_name);
if ($response['statusCode'] != 201) {
throw new Exception("Storage error: " . $response['body'], file_storage::ERROR);
}
}
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_delete($folder_name)
{
$this->init();
$folder_name = $this->encode_path($folder_name);
$response = $this->client->request('DELETE', $folder_name);
if ($response['statusCode'] != 204) {
throw new Exception("Storage error: " . $response['body'], file_storage::ERROR);
}
}
/**
* Move/Rename a folder.
*
* @param string $folder_name Name of a folder with full path
* @param string $new_name New name of a folder with full path
*
* @throws Exception on error
*/
public function folder_move($folder_name, $new_name)
{
$this->init();
$request = array('Destination' => $this->config['baseuri'] . '/' . rawurlencode($new_name));
$folder_name = $this->encode_path($folder_name);
$response = $this->client->request('MOVE', $folder_name, null, $request);
if ($response['statusCode'] != 201) {
throw new Exception("Storage error: " . $response['body'], file_storage::ERROR);
}
}
/**
* Subscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_subscribe($folder_name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Unsubscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_unsubscribe($folder_name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns list of folders.
*
* @param array $params List parameters ('type', 'search')
*
* @return array List of folders
* @throws Exception
*/
public function folder_list($params = array())
{
$this->init();
try {
$items = $this->client->propfind('', array(
'{DAV:}resourcetype',
), 'infinity');
// TODO: Replace infinity by recursion
// Many servers just do not support 'Depth: infinity' for security reasons
// E.g. SabreDAV has this optional and disabled by default
}
catch (Exception $e) {
throw new Exception("User credentials not provided", file_storage::ERROR_NOAUTH);
}
$result = array();
foreach ($items as $file => $props) {
// Skip files
$is_dir = in_array('{DAV:}collection', $props['{DAV:}resourcetype']->resourceType);
if (!$is_dir) {
continue;
}
$path = $this->get_relative_url($file);
$path = $this->decode_path($path);
if ($path !== '') {
$result[] = $path;
}
}
// ensure sorted folders
usort($result, array('file_utils', 'sort_folder_comparator'));
// In extended format we return array of arrays
if (!empty($params['extended'])) {
foreach ($result as $idx => $folder) {
$item = array('folder' => $folder);
$result[$idx] = $item;
}
}
return $result;
}
/**
* Check folder rights.
*
* @param string $folder_name Name of a folder with full path
*
* @return int Folder rights (sum of file_storage::ACL_*)
*/
public function folder_rights($folder_name)
{
// @TODO
return file_storage::ACL_READ | file_storage::ACL_WRITE;
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $path File/folder path
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
* @throws Exception
*/
public function lock_list($path, $child_locks = false)
{
$this->init_lock_db();
// convert path into global URI
$uri = $this->path2uri($path);
// get locks list
$list = $this->lock_db->lock_list($uri, $child_locks);
// convert back global URIs into paths
foreach ($list as $idx => $lock) {
$list[$idx]['uri'] = $this->uri2path($lock['uri']);
}
return $list;
}
/**
* Locks a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
* - depth: 0/'infinite'
* - scope: 'shared'/'exclusive'
* - owner: string
* - token: string
* - timeout: int
*
* @throws Exception
*/
public function lock($path, $lock)
{
$this->init_lock_db();
// convert path into global URI
$uri = $this->path2uri($path);
if (!$this->lock_db->lock($uri, $lock)) {
throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR);
}
}
/**
* Removes a lock from a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
*
* @throws Exception
*/
public function unlock($path, $lock)
{
$this->init_lock_db();
// convert path into global URI
$uri = $this->path2uri($path);
if (!$this->lock_db->unlock($uri, $lock)) {
throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR);
}
}
/**
* Return disk quota information for specified folder.
*
* @param string $folder_name Name of a folder with full path
*
* @return array Quota
* @throws Exception
*/
public function quota($folder)
{
$this->init();
$props = $this->client->propfind($this->encode_path($folder), array(
'{DAV:}quota-available-bytes',
'{DAV:}quota-used-bytes',
), 0);
$used = $props['{DAV:}quota-used-bytes'];
$available = $props['{DAV:}quota-available-bytes'];
return array(
// expected values in kB
'total' => ($used + $available) / 1024,
'used' => $used / 1024,
);
}
/**
* Gets the relative URL of a resource
*
* @param string $url WebDAV URL
* @return string Path relative to root (title/.)
*/
protected function get_relative_url($url)
{
$url = $this->client->getAbsoluteUrl($url);
return trim(str_replace($this->config['baseuri'], '', $url), '/');
}
/**
* Gets the full URL of a resource
*
* @param string $url WebDAV URL
* @return string Path relative to chwala root
*/
protected function get_full_url($url)
{
if (!empty($this->title)) {
return $this->title . '/' . $this->get_relative_url($url);
}
return $this->get_relative_url($url);
}
/**
* Encode folder/file names in the path
* so it can be used as URL
*
* @param string $path File/folder path
*
* @return string Encoded URL
*/
protected function encode_path($path)
{
$path = explode('/', $path);
$path = array_map('rawurlencode', $path);
return implode('/', $path);
}
/**
* Decode folder/file URL path
*
* @param string $path File/folder path
*
* @return string Decoded path
*/
protected function decode_path($path)
{
$path = explode('/', $path);
$path = array_map('rawurldecode', $path);
return implode('/', $path);
}
/**
* Convert file/folder path into a global URI.
*
* @param string $path File/folder path
*
* @return string URI
* @throws Exception
*/
public function path2uri($path)
{
$base = preg_replace('|^[a-zA-Z]+://|', '', $this->config['baseuri']);
return 'webdav://' . rawurlencode($this->config['username']) . '@' . $base
. '/' . file_utils::encode_path($path);
}
/**
* Convert global URI into file/folder path.
*
* @param string $uri URI
*
* @return string File/folder path
* @throws Exception
*/
public function uri2path($uri)
{
if (!preg_match('|^webdav://([^@]+)@(.*)$|', $uri, $matches)) {
throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
}
$user = rawurldecode($matches[1]);
$base = preg_replace('|^[a-zA-Z]+://|', '', $this->config['baseuri']);
$uri = $matches[2];
if ($user != $this->config['username'] || strpos($uri, $base) !== 0) {
throw new Exception("Internal storage error. Unresolvable URI.", file_storage::ERROR);
}
$uri = substr($matches[2], strlen($base) + 1);
return file_utils::decode_path($uri);
}
/**
* Initializes file_locks object
*/
protected function init_lock_db()
{
if (!$this->lock_db) {
$this->lock_db = new file_locks;
}
}
}
diff --git a/lib/file_api_core.php b/lib/file_api_core.php
index 1de6c84..06df742 100644
--- a/lib/file_api_core.php
+++ b/lib/file_api_core.php
@@ -1,411 +1,412 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2014, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_api_core extends file_locale
{
const API_VERSION = 3;
const ERROR_CODE = 500;
const ERROR_INVALID = 501;
const OUTPUT_JSON = 'application/json';
const OUTPUT_HTML = 'text/html';
public $env = array(
'date_format' => 'Y-m-d H:i',
'language' => 'en_US',
+ 'timezone' => 'UTC',
);
protected $app_name = 'Kolab File API';
protected $drivers = array();
protected $icache = array();
protected $backend;
/**
* Returns API version
*/
public function client_version()
{
return self::API_VERSION;
}
/**
* Initialise authentication/configuration backend class
*
* @return file_storage Main storage driver
*/
public function get_backend()
{
if ($this->backend) {
return $this->backend;
}
$rcube = rcube::get_instance();
$driver = $rcube->config->get('fileapi_backend', 'kolab');
$this->backend = $this->load_driver_object($driver);
// configure api
$this->backend->configure($this->env);
return $this->backend;
}
/**
* Return supported/enabled external storage instances
*
* @param bool $as_objects Return drivers as objects not config data
*
* @return array List of storage drivers
*/
public function get_drivers($as_objects = false)
{
$rcube = rcube::get_instance();
$enabled = $rcube->config->get('fileapi_drivers');
$preconf = $rcube->config->get('fileapi_sources');
$result = array();
$all = array();
$iRony = defined('KOLAB_DAV_ROOT');
if (!empty($enabled)) {
$backend = $this->get_backend();
$drivers = $backend->driver_list();
foreach ($drivers as $item) {
// Disable webdav sources/drivers in iRony that point to the
// same host to prevent infinite recursion
if ($iRony && $item['driver'] == 'webdav') {
$self_url = parse_url($_SERVER['SCRIPT_URI']);
$item_url = parse_url($item['host']);
if ($self_url['host'] == $item_url['host']) {
continue;
}
}
$all[] = $item['title'];
if ($item['enabled'] && in_array($item['driver'], (array) $enabled)) {
$result[] = $as_objects ? $this->get_driver_object($item) : $item;
}
}
}
if (empty($result) && !empty($preconf)) {
foreach ((array) $preconf as $title => $item) {
if (!in_array($title, $all)) {
$item['title'] = $title;
$item['admin'] = true;
$result[] = $as_objects ? $this->get_driver_object($item) : $item;
}
}
}
return $result;
}
/**
* Return driver for specified file/folder path
*
* @param string $path Folder/file path
*
* @return array Storage driver object, modified path, driver config
*/
public function get_driver($path)
{
$drivers = $this->get_drivers();
foreach ($drivers as $item) {
$prefix = $item['title'] . file_storage::SEPARATOR;
if ($path == $item['title'] || strpos($path, $prefix) === 0) {
$selected = $item;
break;
}
}
if (empty($selected)) {
return array($this->get_backend(), $path);
}
$path = substr($path, strlen($selected['title']) + 1);
return array($this->get_driver_object($selected), $path, $selected);
}
/**
* Initialize driver instance
*
* @param array $config Driver config
*
* @return file_storage Storage driver instance
*/
public function get_driver_object($config)
{
$key = $config['title'];
if (empty($this->drivers[$key])) {
$this->drivers[$key] = $driver = $this->load_driver_object($config['driver']);
if ($config['username'] == '%u') {
$backend = $this->get_backend();
$auth_info = $backend->auth_info();
$config['username'] = $auth_info['username'];
$config['password'] = $auth_info['password'];
}
else if (!empty($config['password']) && empty($config['admin']) && !empty($key)) {
$config['password'] = $this->decrypt($config['password']);
}
// configure api
$driver->configure(array_merge($config, $this->env), $key);
}
return $this->drivers[$key];
}
/**
* Loads a driver
*/
public function load_driver_object($name)
{
$class = $name . '_file_storage';
if (!class_exists($class, false)) {
$include_path = __DIR__ . "/drivers/$name" . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
}
return new $class;
}
/**
* Returns storage(s) capabilities
*
* @param bool $full Return all drivers' capabilities
*
* @return array Capabilities
*/
public function capabilities($full = true)
{
$rcube = rcube::get_instance();
$backend = $this->get_backend();
$caps = array();
// check support for upload progress
if (($progress_sec = $rcube->config->get('upload_progress'))
&& ini_get('apc.rfc1867') && function_exists('apc_fetch')
) {
$caps[file_storage::CAPS_PROGRESS_NAME] = ini_get('apc.rfc1867_name');
$caps[file_storage::CAPS_PROGRESS_TIME] = $progress_sec;
}
// get capabilities of main storage module
foreach ($backend->capabilities() as $name => $value) {
// skip disabled capabilities
if ($value !== false) {
$caps[$name] = $value;
}
}
// Manticore support
if ($rcube->config->get('fileapi_manticore')) {
$caps['MANTICORE'] = true;
}
// WOPI support
if ($rcube->config->get('fileapi_wopi_office')) {
$caps['WOPI'] = true;
}
if (!$full) {
return $caps;
}
if ($caps['MANTICORE']) {
$manticore = new file_manticore($this);
$caps['MANTICORE_EDITABLE'] = $manticore->supported_filetypes(true);
}
if ($caps['WOPI']) {
$wopi = new file_wopi($this);
$caps['WOPI_EDITABLE'] = $wopi->supported_filetypes(true);
}
// get capabilities of other drivers
$drivers = $this->get_drivers(true);
foreach ($drivers as $driver) {
if ($driver != $backend) {
$title = $driver->title();
foreach ($driver->capabilities() as $name => $value) {
// skip disabled capabilities
if ($value !== false) {
$caps['MOUNTPOINTS'][$title][$name] = $value;
}
}
}
}
return $caps;
}
/**
* Get user name from user identifier (email address) using LDAP lookup
*
* @param string $email User identifier
*
* @return string User name
*/
public function resolve_user($email)
{
$key = "user:$email";
// make sure Kolab backend is initialized so kolab_storage can be found
$this->get_backend();
// @todo: Move this into drivers
if ($this->icache[$key] === null
&& class_exists('kolab_storage')
&& ($ldap = kolab_storage::ldap())
) {
$user = $ldap->get_user_record($email, $_SESSION['imap_host']);
$this->icache[$key] = $user ?: false;
}
if ($this->icache[$key]) {
return $this->icache[$key]['displayname'] ?: $this->icache[$key]['name'];
}
}
/**
* Return mimetypes list supported by built-in viewers
*
* @return array List of mimetypes
*/
protected function supported_mimetypes()
{
$rcube = rcube::get_instance();
$mimetypes = array();
$mimetypes_c = array();
$dir = __DIR__ . '/viewers';
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if (preg_match('/^([a-z0-9_]+)\.php$/i', $file, $matches)) {
include_once $dir . '/' . $file;
$class = 'file_viewer_' . $matches[1];
$viewer = new $class($this);
if ($supported = $viewer->supported_mimetypes()) {
$mimetypes = array_merge($mimetypes, $supported);
}
}
}
closedir($handle);
}
// Here we return mimetypes supported for editing and creation of files
// @TODO: maybe move this to viewers
if ($rcube->config->get('fileapi_wopi_office')) {
$mimetypes_c['application/vnd.oasis.opendocument.text'] = array('ext' => 'odt');
$mimetypes_c['application/vnd.oasis.opendocument.presentation'] = array('ext' => 'odp');
$mimetypes_c['application/vnd.oasis.opendocument.spreadsheet'] = array('ext' => 'ods');
}
else if ($rcube->config->get('fileapi_manticore')) {
$mimetypes_c['application/vnd.oasis.opendocument.text'] = array('ext' => 'odt');
}
$mimetypes_c['text/plain'] = array('ext' => 'txt');
$mimetypes_c['text/html'] = array('ext' => 'html');
foreach (array_keys($mimetypes_c) as $type) {
list ($app, $label) = explode('/', $type);
$label = preg_replace('/[^a-z]/', '', $label);
$mimetypes_c[$type]['label'] = $this->translate('type.' . $label);
}
return array(
'view' => $mimetypes,
'edit' => $mimetypes_c,
);
}
/**
* Encrypts data with current user password
*
* @param string $str A string to encrypt
*
* @return string Encrypted string (and base64-encoded)
*/
public function encrypt($str)
{
$rcube = rcube::get_instance();
$key = $this->get_crypto_key();
return $rcube->encrypt($str, $key, true);
}
/**
* Decrypts data encrypted with encrypt() method
*
* @param string $str Encrypted string (base64-encoded)
*
* @return string Decrypted string
*/
public function decrypt($str)
{
$rcube = rcube::get_instance();
$key = $this->get_crypto_key();
return $rcube->decrypt($str, $key, true);
}
/**
* Set encryption password
*/
protected function get_crypto_key()
{
$key = 'chwala_crypto_key';
$rcube = rcube::get_instance();
$backend = $this->get_backend();
$user = $backend->auth_info();
$password = $user['password'] . $user['username'];
// encryption password must be 24 characters, no less, no more
if (($len = strlen($password)) > 24) {
$password = substr($password, 0, 24);
}
else {
$password = $password . substr($rcube->config->get('des_key'), 0, 24 - $len);
}
$rcube->config->set($key, $password);
return $key;
}
}
diff --git a/lib/file_utils.php b/lib/file_utils.php
index 596629e..52ed686 100644
--- a/lib/file_utils.php
+++ b/lib/file_utils.php
@@ -1,242 +1,266 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_utils
{
static $class_map = array(
'document' => array(
// text
'text/',
'application/rtf',
'application/x-rtf',
'application/xml',
// office
'application/wordperfect',
'application/excel',
'application/msword',
'application/msexcel',
'application/mspowerpoint',
'application/vnd.ms-word',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument',
'application/vnd.oasis.opendocument',
'application/vnd.sun.xml.calc',
'application/vnd.sun.xml.writer',
'application/vnd.stardivision.calc',
'application/vnd.stardivision.writer',
// pdf
'application/pdf',
'application/x-pdf',
'application/acrobat',
'application/vnd.pdf',
),
'audio' => array(
'audio/',
),
'video' => array(
'video/',
),
'image' => array(
'image/',
'application/dxf',
'application/acad',
),
'empty' => array(
'application/x-empty',
),
);
// list of known file extensions, more in Roundcube config
static $ext_map = array(
'doc' => 'application/msword',
'eml' => 'message/rfc822',
'gz' => 'application/gzip',
'htm' => 'text/html',
'html' => 'text/html',
'mp3' => 'audio/mpeg',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odt' => 'application/vnd.oasis.opendocument.text',
'ogg' => 'application/ogg',
'pdf' => 'application/pdf',
'ppt' => 'application/vnd.ms-powerpoint',
'rar' => 'application/x-rar-compressed',
'tgz' => 'application/gzip',
'txt' => 'text/plain',
'zip' => 'application/zip',
);
/**
* Return list of mimetype prefixes for specified file class
*
* @param string $class Class name
*
* @return array List of mimetype prefixes
*/
static function class2mimetypes($class)
{
return isset(self::$class_map[$class]) ? self::$class_map[$class] : self::$class_map['empty'];
}
/**
* Finds class of specified mimetype
*
* @param string $mimetype File mimetype
*
* @return string Class name
*/
static function mimetype2class($mimetype)
{
$mimetype = strtolower($mimetype);
foreach (self::$class_map as $class => $prefixes) {
foreach ($prefixes as $prefix) {
if (strpos($mimetype, $prefix) === 0) {
return $class;
}
}
}
}
/**
* Apply some fixes on file mimetype string
*
* @param string $mimetype File type
*
* @return string File type
*/
static function real_mimetype($mimetype)
{
if (preg_match('/^text\/(.+)/i', $mimetype, $m)) {
// fix pdf mimetype
if (preg_match('/^(pdf|x-pdf)$/i', $m[1])) {
$mimetype = 'application/pdf';
}
}
return $mimetype;
}
/**
* Find mimetype from file name (extension)
*
* @param string $filename File name
* @param string $fallback Follback mimetype
*
* @return string File mimetype
*/
static function ext_to_type($filename, $fallback = 'application/octet-stream')
{
static $mime_ext = array();
$config = rcube::get_instance()->config;
$ext = substr($filename, strrpos($filename, '.') + 1);
if (empty($mime_ext)) {
$mime_ext = self::$ext_map;
foreach ($config->resolve_paths('mimetypes.php') as $fpath) {
$mime_ext = array_merge($mime_ext, (array) @include($fpath));
}
}
if (is_array($mime_ext) && $ext) {
$mimetype = $mime_ext[strtolower($ext)];
}
return $mimetype ?: $fallback;
}
/**
* Returns script URI
*
* @return string Script URI
*/
static function script_uri()
{
if (!empty($_SERVER['SCRIPT_URI'])) {
return $_SERVER['SCRIPT_URI'];
}
$uri = $_SERVER['SERVER_PORT'] == 443 ? 'https://' : 'http://';
$uri .= $_SERVER['HTTP_HOST'];
$uri .= preg_replace('/\?.*$/', '', $_SERVER['REQUEST_URI']);
return $uri;
}
/**
* Callback for uasort() that implements correct
* locale-aware case-sensitive sorting
*/
public static function sort_folder_comparator($p1, $p2)
{
$ext = is_array($p1); // folder can be a string or an array with 'folder' key
$path1 = explode(file_storage::SEPARATOR, $ext ? $p1['folder'] : $p1);
$path2 = explode(file_storage::SEPARATOR, $ext ? $p2['folder'] : $p2);
foreach ($path1 as $idx => $folder1) {
$folder2 = $path2[$idx];
if ($folder1 === $folder2) {
continue;
}
return strcoll($folder1, $folder2);
}
return 0;
}
/**
* Encode folder path for use in an URI
*
* @param string $path Folder path
*
* @return string Encoded path
*/
public static function encode_path($path)
{
$items = explode(file_storage::SEPARATOR, $path);
$items = array_map('rawurlencode', $items);
return implode(file_storage::SEPARATOR, $items);
}
/**
* Decode an URI into folder path
*
* @param string $path Encoded folder path
*
* @return string Decoded path
*/
public static function decode_path($path)
{
$items = explode(file_storage::SEPARATOR, $path);
$items = array_map('rawurldecode', $items);
return implode(file_storage::SEPARATOR, $items);
}
+
+ /**
+ *
+ * @return string Date time string in specified format and timezone
+ */
+ public static function date_format($datetime, $format = 'Y-m-d H:i', $timezone = null)
+ {
+ if (!$datetime instanceof DateTime) {
+ return '';
+ }
+
+ if ($timezone && $timezone != $datetime->getTimezone()) {
+ try {
+ $dt = clone $datetime;
+ $dt->setTimezone(new DateTimeZone($timezone));
+ $datetime = $dt;
+ }
+ catch (Exception $e) {
+ // ignore, return original timezone
+ }
+ }
+
+ return $datetime->format($format);
+ }
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jun 9, 2:53 PM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
196819
Default Alt Text
(144 KB)

Event Timeline