Projects
Kolab:16:Testing
chwala
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 15
View file
chwala.spec
Changed
@@ -38,7 +38,7 @@ %global upstream_version 0.5.14 Name: chwala -Version: 0.5.14.3 +Version: 0.5.14.5 Release: 1%{?dist} Summary: Glorified WebDAV, done right
View file
chwala-0.5.14.tar.gz/lib/api/document.php
Changed
@@ -262,7 +262,12 @@ $result'owner' = $session'owner'; $result'owner_name' = $session'owner_name'; - $result'user' = $rcube->user->get_username(); + if ($rcube && $rcube->user) { + $result'user' = $rcube->user->get_username(); + } else { + //Fall back to the owner if we don't have the rcube user initialized (wopi with kolabfiles) + $result'user' = $session'owner'; + } $result'readonly' = !empty($session'readonly'); $result'origin' = $session'origin';
View file
chwala-0.5.14.tar.gz/lib/drivers/kolab/kolab_file_storage.php
Changed
@@ -54,6 +54,11 @@ */ protected $lock_db; + /** + * @var ?object + */ + protected $driver_list = null; + /** * Class constructor @@ -186,7 +191,8 @@ // parse $host $a_host = parse_url($host); $port = null; - if ($a_host'host') { + $ssl = null; + if (!empty($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')) {
View file
chwala-0.5.14.tar.gz/lib/drivers/kolabfiles
Added
+(directory)
View file
chwala-0.5.14.tar.gz/lib/drivers/kolabfiles/kolabfiles_file_storage.php
Added
@@ -0,0 +1,1173 @@ +<?php +/* + +--------------------------------------------------------------------------+ + | This file is part of the Kolab File API | + | | + | Copyright (C) 2024, Apheleia It | + | | + | 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: Christian Mollekopf <mollekopf@apheleia-it.ch> | + +--------------------------------------------------------------------------+ +*/ + +// FIXME +// * Support non-toplevel folders for collection creation/moves/... +// * Paging (we don't display beyond the first page (100 items)) +// * Searching is not implemented +class kolabfiles_file_storage implements file_storage +{ + /** + * @var rcube + */ + protected $rc; + + protected $client = null; + + /** + * @var array + */ + protected $config = array(); + + /** + * List of Kolabfiles collections + * + * @var array + */ + protected $collections; + + /** + * Instance title (mount point) + * + * @var string + */ + protected $title; + + + /** + * Class constructor + */ + public function __construct() + { + $this->rc = rcube::get_instance(); + $this->config = + 'baseuri' => $this->rc->config->get('fileapi_kolabfiles_baseuri', 'https://' . $_SERVER"HTTP_HOST" . '/api/'), + 'host' => $this->rc->config->get('fileapi_kolabfiles_host', $_SERVER"HTTP_HOST"), + ; + } + + + protected function init() + { + if ($this->client !== null) { + return true; + } + + $stack = new \GuzzleHttp\HandlerStack(); + $stack->setHandler(\GuzzleHttp\choose_handler()); + $stack->push(\GuzzleHttp\Middleware::retry( + function ( + $retries, + \GuzzleHttp\Psr7\Request $request, + \GuzzleHttp\Psr7\Response $response = null, + \GuzzleHttp\Exception\RequestException $exception = null + ) { + $maxRetries = 2; + + if ($retries >= $maxRetries) { + return false; + } + + if ($response && $response->getStatusCode() === 401) { + $this->refreshClientAccessToken(); + return true; + } + + return false; + } + )); + + $stack->push(\GuzzleHttp\Middleware::mapRequest(function (\GuzzleHttp\Psr7\Request $request) { + //TODO Just forward the bearer token, once we manage to make sure it get's sent to roundcube + // 'Authorization' => rcube_utils::request_header('Authorization') + if ($accessToken = $this->getClientAccessToken()) { + return $request->withHeader('Authorization', 'Bearer ' . $accessToken); + } + return $request; + })); + + $this->client = new \GuzzleHttp\Client( + + 'http_errors' => false, // No exceptions from Guzzle + 'base_uri' => rtrim($this->config'baseuri', '/') . '/', + 'handler' => $stack, + 'verify' => false, + 'connect_timeout' => 10, + 'timeout' => 10, + // 'on_stats' => function (\GuzzleHttp\TransferStats $stats) { + // $threshold = \config('logging.slow_log'); + // if ($threshold && ($sec = $stats->getTransferTime()) > $threshold) { + // $url = $stats->getEffectiveUri(); + // $method = $stats->getRequest()->getMethod(); + // \Log::warning(sprintf("STATS %s %s: %.4f sec.", $method, $url, $sec)); + // } + // }, + + ); + } + + private function getClientAccessToken() + { + if (!empty($_SESSION'access_token')) { + return $this->rc->decrypt($_SESSION'access_token'); + } + return null; + } + + private function refreshClientAccessToken() + { + //TODO use the refresh token if available instead of refreshing from scratch always. + rcube::write_log('kolabfiles', "Refreshing the access token"); + $username = $_SESSION'username'; + $password = $this->rc->decrypt($_SESSION'password'); + + $client = new \GuzzleHttp\Client( + 'http_errors' => false, // No exceptions from Guzzle + 'base_uri' => rtrim($this->config'baseuri', '/') . '/', + 'verify' => false, + 'connect_timeout' => 10, + 'timeout' => 10, + ); + + $response = $client->request('POST', "auth/login?email=$username&password=$password"); + if ($response->getStatusCode() != 200) { + throw new Exception("Failed to authenticate $username:$password"); + } + $json = json_decode($response->getBody(), true); + $accessToken = $json'access_token'; + $_SESSION'access_token' = $this->rc->encrypt($accessToken); + } + + protected function client() + { + $this->init(); + return $this->client; + } + + /** + * 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) + { + $_SESSION'username' = $username; + $_SESSION'password' = $this->rc->encrypt($password); + + + $this->init(); + + return true; + } + + /** + * Get password and name of authenticated user + * + * @return array Authenticated user data + */ + public function auth_info() + { + return array( + 'username' => $_SESSION'username', + 'password' => $this->rc->decrypt($_SESSION'password'), + ); + } + + /** + * 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 => false, + file_storage::CAPS_LOCKS => true, + file_storage::CAPS_ACL => 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() + { + return ; + } + + /** + * 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__ . '/kolab.png'); + + $metadata = array( + 'image' => 'data:image/png;base64,' . base64_encode($image_content), + 'name' => 'Kolab Files', + 'ref' => 'http://kolab.org', + 'description' => 'Kolab File Storage', + '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) + { + list($fn, $repo_id) = $this->find_collection($file_name); + + if (empty($repo_id)) { + throw new Exception("Storage error. Folder not found.", file_storage::ERROR); + } + + $data = null; + $fp = null; + if ($file'path') { + $fp = fopen($file'path', 'r'); + $data = $fp; + } else { + $data = $file'content'; + } + + $response = $this->client->request("POST", "v4/fs?name=$fn&parent=$repo_id", 'body' => $data); + $created = $response->getStatusCode() == 200; + + if (!$created) { + rcube::raise_error(array( + 'code' => 600, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Error saving file 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) + { + list($fn, $repo_id) = $this->find_collection($file_name); + $file_id = $this->find_file_id($fn, $repo_id); + + if (empty($repo_id)) { + throw new Exception("Storage error. Folder not found.", file_storage::ERROR); + } + + $data = null; + $fp = null; + if ($file'path') { + $fp = fopen($file'path', 'r'); + $data = $fp; + } else { + $data = $file'content'; + } + + $response = $this->client->request("PATCH", "v4/fs/$file_id?media=content", 'body' => $data); + + if ($response->getStatusCode() != 200) { + rcube::raise_error(array( + 'code' => 600, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Error saving file 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) + { + list($file_name, $repo_id) = $this->find_collection($file_name); + + if ($repo_id && $file_name != '/') { + $file_id = $this->find_file_id($file_name, $repo_id); + $response = $this->client->request("DELETE", "v4/fs/$file_id"); + $deleted = $response->getStatusCode() == 200; + } + + 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, 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_collection($file_name); + $file_id = $this->find_file_id($fn, $repo_id); + + $response = $this->client->request("GET", "v4/fs/{$file_id}"); + $file = json_decode($response->getBody(), true); + + // write to file pointer, send no headers + if ($fp) { + $response = $this->client->request("GET", "v4/fs/{$file_id}?download=1"); + fwrite(fp, $request->getBody()); + + 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'mimetype'); + $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 Kolab server + if ($file'size' && empty($params'head')) { + $allow_redirects = $this->rc->config->get('fileapi_kolabfiles_allow_redirects'); + // In view-mode we can't redirect to Kolab 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 ($allow_redirects && !empty($params'force-download')) { + $response = $this->client->request("GET", "v4/fs/{$file_id}?downloadUrl=1"); + $json = json_decode($response->getBody(), true); + $link = $json'downloadUrl'; + header("Location: $link"); + } + else if ($fp = fopen('php://output', 'wb')) { + $response = $this->client->request("GET", "v4/fs/{$file_id}?download=1"); + fwrite($fp, $response->getBody()); + fclose($fp); + } + } + } + + /** + * 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_collection($file_name); + $file_id = $this->find_file_id($file, $repo_id); + + $response = $this->client->request("GET", "v4/fs/{$file_id}"); + $json = json_decode($response->getBody(), true); + + if (empty($json)) { + throw new Exception("Storage error. File not found.", file_storage::ERROR); + } + + $file = $this->from_file_object($json); + + return array( + 'name' => $file'name', + 'size' => (int) $file'size', + 'type' => (string) $file'type', + '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()) + { + // mount point contains only folders + if (!is_string($folder_name) || $folder_name === '') { + return array(); + } + + list($folder, $repo_id) = $this->find_collection($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); + } + } + } + + $response = $this->client()->request('GET', "v4/fs?parent={$repo_id}&type=file"); + $json = json_decode($response->getBody(), true); + $entries = $json'list'; + + $result = array(); + + foreach ((array) $entries as $idx => $file) { + //TODO file'name' + //TODO file'type' = 'file' + //TODO file'type' + + if ($file'type' != 'file') { + continue; + } + + $file_id = $file'id'; + + //FIXME slightly wasteful to just get the size and file modification timestamps + $response = $this->client->request("GET", "v4/fs/{$file_id}"); + $json = json_decode($response->getBody(), true); + + $file = $this->from_file_object($json); + // 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_utils::date_format($file'changed', null, $this->config'timezone'), + 'ctime' => file_utils::date_format($file'created', null, $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) + { + throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); + } + + /** + * 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_collection($file_name); + list($dst_name, $dst_repo_id) = $this->find_collection($new_name); + $file_id = $this->find_file_id($src_name, $repo_id); + + $response = $this->client->request("PUT", "v4/fs/$file_id?name=$dst_name", 'headers' => "X-Kolab-Parents" => $dst_repo_id); + $success = $response->getStatusCode() == 200; + + if (!$success) { + rcube::raise_error(array( + 'code' => 600, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Error moving file 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) + { + list($folder, $repo_id) = $this->find_collection($folder_name, true); + + if (empty($repo_id)) { + $response = $this->client->request("POST", "v4/fs?name=$folder_name&type=collection"); + } + else { + $response = $this->client->request("POST", "v4/fs?name=$folder&type=collection&parent={$repo_id}"); + } + + $success = $response->getStatusCode() == 200; + + if (!$success) { + throw new Exception("Storage error. Unable to create folder", file_storage::ERROR); + } + + // clear the cache + if (empty($repo_id)) { + $this->collections = 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_collection($folder_name, true); + + $response = $this->client->request("DELETE", "v4/fs/$repo_id"); + $success = $response->getStatusCode() == 200; + + 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_name, $repo_id, $collection) = $this->find_collection($folder_name, true); + list($dest_folder_name, $dest_repo_id) = $this->find_collection($new_name, true); + + $response = $this->client->request("PUT", "v4/fs/$repo_id?name=$dest_folder_name", 'headers' => "X-Kolab-Parents" => $dest_repo_id); + $success = $response->getStatusCode() == 200; + + 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', 'path', 'level') + * + * @return array List of folders + * @throws Exception + */ + public function folder_list($params = array()) + { + // $collections = $this->collections(); + // $writable = ($params'type' & file_storage::FILTER_WRITABLE) ? true : false; + // $prefix = (string) $params'path'; + // $prefix_len = strlen($prefix); + $folders = array(); + $collections = $this->collections(); + foreach ($collections as $folder) { + $item = array('folder' => $folder'name'); + $item'readonly' = false; + + //TODO? + // 'mtime' => $collection'mtime', + // 'permission' => $collection'permission', + + $folders$folder'name' = $item; + } + + + if (empty($params'extended')) { + $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) + { + list($folder, $repo_id, $collection) = $this->find_collection($folder_name); + + $response = $this->client()->request('GET', "v4/fs/$repo_id"); + $json = json_decode($response->getBody(), true); + if ($json'canUpdate') { + return file_storage::ACL_READ | file_storage::ACL_WRITE; + } + return file_storage::ACL_READ; + } + + /** + * 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 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) + { + // throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); + return ; + } + + /** + * Sharing interface + * + * @param string $folder_name Name of a folder with full path + * @param int $mode Sharing action mode + * @param array $args POST/GET parameters + * + * @return mixed Sharing response + * @throws Exception + */ + public function sharing($folder, $mode, $args = array()) + { + throw new Exception("Search not implemented", file_storage::ERROR_UNSUPPORTED); + } + + /** + * User/group search (autocompletion) + * + * @param string $search Search string + * @param int $mode Search mode + * + * @return array Users/Groups list + * @throws Exception + */ + public function autocomplete($search, $mode) + { + throw new Exception("Autocomplete not implemented", file_storage::ERROR_UNSUPPORTED); + } + + /** + * Convert file/folder path into a global URI. + * + * @param string $path File/folder path + * + * @return string URI + * @throws Exception + */ + public function path2uri($path) + { + // Remove protocol prefix and path, we work with host only + $host = preg_replace('#(^https?://|/.*$)#i', '', $this->config'host'); + + if (!is_string($path) || !strlen($path)) { + $user = $_SESSION$this->title . 'user'; + return 'kolabfiles://' . rawurlencode($user) . '@' . $host . '/'; + } + + list($file, $repo_id, $collection) = $this->find_collection($path); + + //FIXME we don't currently have an owner + return 'kolabfiles://' . rawurlencode($collection'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('|^kolabfiles://(^@+)@(^/+)/(.*)$|', $uri, $matches)) { + throw new Exception("Internal storage error. Unexpected data format. $uri", file_storage::ERROR); + } + + $user = rawurldecode($matches1); + $host = $matches2; + $path = file_utils::decode_path($matches3); + $c_host = preg_replace('#(^https?://|/.*$)#i', '', $this->config'host'); + + if (strlen($path)) { + list($file, $repo_id, $collection) = $this->find_collection($path, true); + + if (empty($collection) || $host != $c_host || $user != $collection'owner') { + throw new Exception("Internal storage error. Unresolvable URI.", file_storage::ERROR); + } + } + + return $path; + } + + /** + * Get list of collections + */ + private function collections($parent = null, $path = null) + { + if ($this->collections) { + return $this->collections; + } + $folders = array(); + + //FIXME If we could just fetch all collections, we could assemble the tree after a single fetch. + if ($parent) { + $parentId = $parent'id'; + $response = $this->client()->request('GET', "v4/fs?parent=$parentId&type=collection"); + } else { + $response = $this->client()->request('GET', "v4/fs?type=collection"); + } + //FIXME should we just always throw on request errors? Probably? + if ($response->getStatusCode() != 200) { + rcube::write_log('kolabfiles', "The request failed: " . $response->getStatusCode()); + throw new Exception("Get request was unsuccessful"); + } + $json = json_decode($response->getBody(), true); + // rcube::write_log('kolabfiles', var_export($json, true)); + $collections = $json'list'; + + if (!$collections) { + return ; + } + + $collections = array_map(function ($entry) use ($path) { + //FIXME: retrieve the actual owner from the api. (Do we need the owner though?), not sure it matters + $entry'owner' = $_SESSION$this->title . 'user'; + if ($path) { + $entry'name' = $path . "/" . $entry'name'; + } + return $entry; + }, $collections); + + $tmp = $collections; + foreach ($tmp as $lib) { + $subfolders = $this->collections($lib, $lib'name'); + $collections = array_merge($collections, $subfolders); + } + if (!$parent) { + $this->collections = $collections; + } + return $collections; + } + + + protected function find_file_id($file_name, $repo_id) + { + $response = $this->client()->request('GET', "v4/fs?parent={$repo_id}&type=file"); + $json = json_decode($response->getBody(), true); + foreach ($json'list' as $idx => $file) { + if ($file'name' == $file_name) { + return $file'id'; + } + } + rcube::write_log('console', "Failed to find the file $file_name in $repo_id"); + throw new Exception("Failed to find the file $file_name in $repo_id"); + } + + /** + * Find collection ID from folder name + */ + protected function find_collection($folder_name, $no_exception = false) + { + $collections = $this->collections(); + + foreach ($collections as $lib) { + $path = $lib'name' . '/'; + + if ($folder_name == $lib'name' || strpos($folder_name, $path) === 0) { + if (empty($collection) || strlen($collection'name') < strlen($lib'name')) { + $collection = $lib; + } + } + } + + if (empty($collection)) { + if (!$no_exception) { + throw new Exception("Storage error. Collection not found.", file_storage::ERROR); + } + return array(null, null); + } + else { + $folder = substr($folder_name, strlen($collection'name') + 1); + } + + return array( + // '/' . ($folder ? $folder : ''), + ($folder ? $folder : ''), + $collection'id', + $collection + ); + } + + /** + * Simplify internal structure of the file object + */ + protected function from_file_object($file) + { + if ($file'created_at') { + try { + $file'created' = new DateTime($file'created_at'); + } + catch (Exception $e) {} + } + unset($file'created_at'); + + if ($file'updated_at') { + try { + $file'changed' = new DateTime($file'updated_at'); + } + catch (Exception $e) { } + } + unset($file'updated_at'); + + // We're not taking the servers filetype. The server might have octet/stream stored for a file, + // which will break the editor which needs some odt mimetype (it will silently fall back to downloading the file instead). + $file'type' = file_utils::ext_to_type($file'name'); + + unset($file'id'); + + return $file; + } + + /** + * Initializes file_locks object + */ + protected function init_lock_db() + { + if (!$this->lock_db) { + $this->lock_db = new file_locks; + } + } + + /** + * Create display-username-with-email string + */ + protected function user_label($name, $email) + { + if ($name && $name != $email) { + $label = "{$name} ({$email})"; + } + else { + $label = $email; + } + + return $label; + } +}
View file
chwala-0.5.14.tar.gz/lib/drivers/seafile/seafile_api.php
Changed
@@ -59,11 +59,11 @@ protected $config = array(); /** - * HTTP request handle + * HTTP client * - * @var HTTP_Request + * @var GuzzleHttp\Client */ - protected $request; + protected $client = null; /** * Web API URI prefix @@ -107,39 +107,6 @@ } /** - * - * @param array Configuration for this Request instance, that will be merged - * with default configuration - * - * @return HTTP_Request2 Request object - */ - public static function http_request($config = array()) - { - // remove unknown config, otherwise HTTP_Request will throw an error - $config = array_intersect_key($config, array_flip(array( - 'connect_timeout', 'timeout', 'use_brackets', 'protocol_version', - 'buffer_size', 'store_body', 'follow_redirects', 'max_redirects', - 'strict_redirects', 'ssl_verify_peer', 'ssl_verify_host', - 'ssl_cafile', 'ssl_capath', 'ssl_local_cert', 'ssl_passphrase' - ))); - - // force CURL adapter, this allows to handle correctly - // compressed responses with simple SplObserver registered - $config'adapter' = 'HTTP_Request2_Adapter_Curl'; - - try { - $request = new HTTP_Request2(); - $request->setConfig($config); - } - catch (Exception $e) { - rcube::raise_error($e, true, false); - return; - } - - return $request; - } - - /** * Send HTTP request * * @param string $method Request method ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT') @@ -166,29 +133,15 @@ $url = str_replace('/api2/', "/api/v$version/", $url); } - if (!$this->request) { - $this->config'store_body' = true; - // some methods respond with 301 redirect, we'll not follow them - // also because of https://github.com/haiwen/seahub/issues/288 - $this->config'follow_redirects' = false; - - $this->request = self::http_request($this->config); - - if (!$this->request) { - $this->status = self::CONNECTION_ERROR; - return; - } - } - - // cleanup - try { - $this->request->setBody(''); - $this->request->setUrl($url); - } - catch (Exception $e) { - rcube::raise_error($e, true, false); - $this->status = self::CONNECTION_ERROR; - return; + if (!$this->client) { + $this->client = new \GuzzleHttp\Client( + + 'http_errors' => true, + 'verify' => false, + 'connect_timeout' => 10, + 'timeout' => 300, + + ); } if ($this->config'debug') { @@ -210,49 +163,73 @@ rcube::write_log('console', $log_line); } - $this->request->setMethod($method ?: HTTP_Request2::METHOD_GET); - - if (!empty($get)) { - $_url = $this->request->getUrl(); - $_url->setQueryVariables($get); - $this->request->setUrl($_url); - } + // send request to the SeaFile API server + try { + $headers = + 'Accept' => "application/json,text/javascript,*/*", + 'User-Agent' => $_SERVER'HTTP_USER_AGENT' + ; - // HTTP_Request2 does not support POST params on PUT requests - if (!empty($post) && $method == 'PUT') { - $body = http_build_query($post, '', '&'); - $body = str_replace('%7E', '~', $body); // support RFC 3986 by not encoding '~' symbol - $post = null; + if ($this->token) { + $headers'Authorization' = "Token " . $this->token; + } - $this->request->setBody($body); - $this->request->setHeader('content-type', 'application/x-www-form-urlencoded'); - } + $options = ; - if (!empty($post)) { - $this->request->addPostParameter($post); - } - if (!empty($upload)) { - foreach ($upload as $field_name => $file) { - $this->request->addUpload($field_name, $file'data', $file'name', $file'type'); + if (!empty($get)) { + $options'query' = $get; } - } - - if ($this->token) { - $this->request->setHeader('Authorization', "Token " . $this->token); - } - // some HTTP server configurations require this header - $this->request->setHeader('Accept', "application/json,text/javascript,*/*"); + if (!empty($upload)) { + if (!empty($post'target_file')) { + $filename = basename($post'target_file'); + } else { + $filename = $upload'file''name'; + } + $multipart = + + 'headers' => 'Content-Type' => 'application/octet-stream', + 'name' => 'file', + 'filename' => $filename, + 'contents' => $upload'file''data' + , + + 'name' => 'name', + 'contents' => $upload'file''name', + , + + 'name' => 'type', + 'contents' => $upload'file''type', + , + ; + + if (array_key_exists('parent_dir', $post)) { + $multipart = + 'name' => 'parent_dir', + 'contents' => $post'parent_dir' + ; + } + if (array_key_exists('target_file', $post)) { + $multipart = + 'name' => 'target_file', + 'contents' => $post'target_file' + ; + } + $options'multipart' = $multipart; + } else { + if (!empty($post)) { + $options'form_params' = $post; + } + } - // proxy User-Agent string - $this->request->setHeader('User-Agent', $_SERVER'HTTP_USER_AGENT'); + if (!empty($headers)) { + $options'headers' = $headers; + } + $response = $this->client->request($method ?: "GET", $url, $options); - // send request to the SeaFile API server - try { - $response = $this->request->send(); - $this->status = $response->getStatus(); - $body = $response->getBody(); + $this->status = $response->getStatusCode(); + $body = $response->getBody(); } catch (Exception $e) { rcube::raise_error($e, true, false);
View file
chwala-0.5.14.tar.gz/lib/drivers/seafile/seafile_file_storage.php
Changed
@@ -379,9 +379,6 @@ $created = $this->api->file_upload($repo_id, $fn, $file); - if ($fp) { - fclose($fp); - } if (!$created) { rcube::raise_error(array( @@ -425,9 +422,6 @@ $saved = $this->api->file_update($repo_id, $fn, $file); - if ($fp) { - fclose($fp); - } if (!$saved) { rcube::raise_error(array( @@ -542,7 +536,6 @@ } else if ($fp = fopen('php://output', 'wb')) { $this->save_file_content($link, $fp); - fclose($fp); } } } @@ -1645,9 +1638,24 @@ } $config = array_merge($this->config, array('store_bodies' => true)); - $request = seafile_api::http_request($config); - if (!$request) { + $config = array_intersect_key($config, array_flip(array( + 'connect_timeout', 'timeout', 'use_brackets', 'protocol_version', + 'buffer_size', 'store_body', 'follow_redirects', 'max_redirects', + 'strict_redirects', 'ssl_verify_peer', 'ssl_verify_host', + 'ssl_cafile', 'ssl_capath', 'ssl_local_cert', 'ssl_passphrase' + ))); + + // force CURL adapter, this allows to handle correctly + // compressed responses with simple SplObserver registered + $config'adapter' = 'HTTP_Request2_Adapter_Curl'; + + try { + $request = new HTTP_Request2(); + $request->setConfig($config); + } + catch (Exception $e) { + rcube::raise_error($e, true, false); return false; }
View file
chwala-0.5.14.tar.gz/lib/drivers/webdav/webdav_file_storage.php
Changed
@@ -269,7 +269,7 @@ ); // these are returned when authentication on folders list fails - if ($this->config'username') { + if (!empty($this->config'username')) { $metadata'form_values' = array( 'baseuri' => $this->config'baseuri', 'username' => $this->config'username',
View file
chwala-0.5.14.tar.gz/lib/file_api.php
Changed
@@ -215,6 +215,7 @@ */ protected function authenticate() { + $username = null; if (isset($_POST'username')) { $username = $_POST'username'; $password = $_POST'password'; @@ -226,9 +227,9 @@ // when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule else if (!isset($_SERVER'PHP_AUTH_USER')) { $tokens = array( - $_SERVER'REMOTE_USER', - $_SERVER'REDIRECT_REMOTE_USER', - $_SERVER'HTTP_AUTHORIZATION', + $_SERVER'REMOTE_USER' ?? null, + $_SERVER'REDIRECT_REMOTE_USER' ?? null, + $_SERVER'HTTP_AUTHORIZATION' ?? null, rcube_utils::request_header('Authorization'), ); @@ -369,7 +370,7 @@ */ public function file_url($file) { - return $this->api_url() . '?method=file_get' + return $this->api_url() . '/?method=file_get' . '&file=' . urlencode($file) . '&token=' . urlencode(session_id()); }
View file
chwala-0.5.14.tar.gz/lib/wopi/files.php
Changed
@@ -95,8 +95,7 @@ $info = parent::document_info($id); // Convert file metadata to Wopi format - // TODO: support more properties from - // https://wopirest.readthedocs.io/en/latest/files/CheckFileInfo.html + // TODO: support more properties from the WOPI and Collabora spec. $result = array( 'BaseFileName' => $info'name', @@ -113,6 +112,7 @@ 'HideSaveOption' => true, 'HideExportOption' => true, 'HidePrintOption' => true, + // 'HideRepairOption' => true, 'EnableOwnerTermination' => true, 'WatermarkText' => '', // ?? 'DisablePrint' => false, @@ -121,9 +121,18 @@ 'DisableInactiveMessages' => true, 'DisableChangeTrackingRecord' => true, 'DisableChangeTrackingShow' => true, + // 'DownloadAsPostMessage' + // 'EnableInsertRemoteImage' + // 'EnableRemoteLinkPicker' + // 'EnableShare' 'HideChangeTrackingControls' => true, + // 'HideUserList' => true, + 'SupportsRename' => false, + 'UserCanRename' => false, + // 'UserCanNotWriteRelative' // TODO: 'UserExtraInfo' => 'avatar' => 'http://url/to/user/avatar', 'mail' => $info'user' 'UserExtraInfo' => array(), + // 'UserPrivateInfo' ); if ($info'modified') {
View file
chwala.dsc
Changed
@@ -2,7 +2,7 @@ Source: chwala Binary: chwala Architecture: all -Version: 0.5.14.3-1~kolab1 +Version: 0.5.14.5-1~kolab1 Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Homepage: http://kolab.org/about/chwala/
View file
debian.changelog
Changed
@@ -1,4 +1,4 @@ -chwala (0.5.14.3-1~kolab1) unsable; urgency=low +chwala (0.5.14.5-1~kolab1) unsable; urgency=low * Release version 0.5.14
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.