Page MenuHomePhorge

No OneTemporary

diff --git a/lib/kolab/kolab_file_plugin_api.php b/lib/drivers/kolab/kolab_file_plugin_api.php
similarity index 100%
rename from lib/kolab/kolab_file_plugin_api.php
rename to lib/drivers/kolab/kolab_file_plugin_api.php
diff --git a/lib/kolab/kolab_file_storage.php b/lib/drivers/kolab/kolab_file_storage.php
similarity index 100%
rename from lib/kolab/kolab_file_storage.php
rename to lib/drivers/kolab/kolab_file_storage.php
diff --git a/lib/kolab/plugins/kolab_auth/LICENSE b/lib/drivers/kolab/plugins/kolab_auth/LICENSE
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/LICENSE
rename to lib/drivers/kolab/plugins/kolab_auth/LICENSE
diff --git a/lib/kolab/plugins/kolab_auth/config.inc.php.dist b/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/config.inc.php.dist
rename to lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist
diff --git a/lib/kolab/plugins/kolab_auth/kolab_auth.php b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/kolab_auth.php
rename to lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php
diff --git a/lib/kolab/plugins/kolab_auth/kolab_auth_ldap.php b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/kolab_auth_ldap.php
rename to lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php
diff --git a/lib/kolab/plugins/kolab_auth/localization/bg_BG.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/bg_BG.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/de_CH.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/de_CH.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/de_DE.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/de_DE.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/en_US.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/en_US.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/es_ES.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/es_ES.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/et_EE.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/et_EE.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/fr_FR.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/fr_FR.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/ja_JP.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/ja_JP.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/nl_NL.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/nl_NL.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/pl_PL.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/pl_PL.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/pt_BR.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/pt_BR.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc
diff --git a/lib/kolab/plugins/kolab_auth/localization/ru_RU.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/localization/ru_RU.inc
rename to lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc
diff --git a/lib/kolab/plugins/kolab_auth/package.xml b/lib/drivers/kolab/plugins/kolab_auth/package.xml
similarity index 100%
rename from lib/kolab/plugins/kolab_auth/package.xml
rename to lib/drivers/kolab/plugins/kolab_auth/package.xml
diff --git a/lib/kolab/plugins/libkolab/LICENSE b/lib/drivers/kolab/plugins/libkolab/LICENSE
similarity index 100%
rename from lib/kolab/plugins/libkolab/LICENSE
rename to lib/drivers/kolab/plugins/libkolab/LICENSE
diff --git a/lib/kolab/plugins/libkolab/README b/lib/drivers/kolab/plugins/libkolab/README
similarity index 100%
rename from lib/kolab/plugins/libkolab/README
rename to lib/drivers/kolab/plugins/libkolab/README
diff --git a/lib/kolab/plugins/libkolab/SQL/mysql.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql
similarity index 100%
rename from lib/kolab/plugins/libkolab/SQL/mysql.initial.sql
rename to lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql
diff --git a/lib/kolab/plugins/libkolab/SQL/mysql/2013011000.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013011000.sql
similarity index 100%
rename from lib/kolab/plugins/libkolab/SQL/mysql/2013011000.sql
rename to lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013011000.sql
diff --git a/lib/kolab/plugins/libkolab/SQL/mysql/2013041900.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013041900.sql
similarity index 100%
rename from lib/kolab/plugins/libkolab/SQL/mysql/2013041900.sql
rename to lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013041900.sql
diff --git a/lib/kolab/plugins/libkolab/SQL/mysql/2013100400.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013100400.sql
similarity index 100%
rename from lib/kolab/plugins/libkolab/SQL/mysql/2013100400.sql
rename to lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013100400.sql
diff --git a/lib/kolab/plugins/libkolab/SQL/postgres.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/postgres.initial.sql
similarity index 100%
rename from lib/kolab/plugins/libkolab/SQL/postgres.initial.sql
rename to lib/drivers/kolab/plugins/libkolab/SQL/postgres.initial.sql
diff --git a/lib/kolab/plugins/libkolab/UPGRADING b/lib/drivers/kolab/plugins/libkolab/UPGRADING
similarity index 100%
rename from lib/kolab/plugins/libkolab/UPGRADING
rename to lib/drivers/kolab/plugins/libkolab/UPGRADING
diff --git a/lib/kolab/plugins/libkolab/bin/modcache.sh b/lib/drivers/kolab/plugins/libkolab/bin/modcache.sh
similarity index 100%
rename from lib/kolab/plugins/libkolab/bin/modcache.sh
rename to lib/drivers/kolab/plugins/libkolab/bin/modcache.sh
diff --git a/lib/kolab/plugins/libkolab/config.inc.php.dist b/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist
similarity index 100%
rename from lib/kolab/plugins/libkolab/config.inc.php.dist
rename to lib/drivers/kolab/plugins/libkolab/config.inc.php.dist
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_date_recurrence.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_date_recurrence.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_date_recurrence.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_date_recurrence.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_configuration.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_configuration.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_contact.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_contact.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_contact.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_contact.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_event.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_event.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_file.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_file.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_file.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_file.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_journal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_journal.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_journal.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_journal.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_note.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_note.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_note.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_note.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_task.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_task.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_task.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_task.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_format_xcal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_format_xcal.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php
diff --git a/lib/kolab/plugins/libkolab/lib/kolab_storage_folder.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/lib/kolab_storage_folder.php
rename to lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php
diff --git a/lib/kolab/plugins/libkolab/libkolab.php b/lib/drivers/kolab/plugins/libkolab/libkolab.php
similarity index 100%
rename from lib/kolab/plugins/libkolab/libkolab.php
rename to lib/drivers/kolab/plugins/libkolab/libkolab.php
diff --git a/lib/kolab/plugins/libkolab/package.xml b/lib/drivers/kolab/plugins/libkolab/package.xml
similarity index 100%
rename from lib/kolab/plugins/libkolab/package.xml
rename to lib/drivers/kolab/plugins/libkolab/package.xml
diff --git a/lib/drivers/seafile/seafile_api.php b/lib/drivers/seafile/seafile_api.php
new file mode 100644
index 0000000..dcc5fe1
--- /dev/null
+++ b/lib/drivers/seafile/seafile_api.php
@@ -0,0 +1,841 @@
+<?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 implementing access via SeaFile Web API v2
+ */
+class seafile_api
+{
+ const STATUS_OK = 200;
+ const CREATED = 201;
+ const ACCEPTED = 202;
+ const MOVED_PERMANENTLY = 301;
+ const BAD_REQUEST = 400;
+ const FORBIDDEN = 403;
+ const NOT_FOUND = 404;
+ const CONFLICT = 409;
+ const TOO_MANY_REQUESTS = 429;
+ const REPO_PASSWD_REQUIRED = 440;
+ const REPO_PASSWD_MAGIC_REQUIRED = 441;
+ const INTERNAL_SERVER_ERROR = 500;
+ const OPERATION_FAILED = 520;
+
+ const CONNECTION_ERROR = 550;
+
+ /**
+ * Specifies how long max. we'll wait and renew throttled request (in seconds)
+ */
+ const WAIT_LIMIT = 30;
+
+
+ /**
+ * Configuration
+ *
+ * @var array
+ */
+ protected $config = array();
+
+ /**
+ * HTTP request handle
+ *
+ * @var HTTP_Request
+ */
+ protected $request;
+
+ /**
+ * Web API URI prefix
+ *
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * Session token
+ *
+ * @var string
+ */
+ protected $token;
+
+
+ public function __construct($config = array())
+ {
+ $this->config = $config;
+
+ // set Web API URI
+ $this->url = rtrim('https://' . ($config['host'] ?: 'localhost'), '/');
+ if (!preg_match('|/api2$|', $this->url)) {
+ $this->url .= '/api2/';
+ }
+ }
+
+ /**
+ *
+ * @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())
+ {
+ // load HTTP_Request2
+ require_once 'HTTP/Request2.php';
+
+ // 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'
+ )));
+
+ 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')
+ * @param string $url Request API URL
+ * @param array $get GET parameters
+ * @param array $post POST parameters
+ * @param array $upload Uploaded files data
+ *
+ * @return string|array Server response
+ */
+ protected function request($method, $url, $get = null, $post = null, $upload = null)
+ {
+ if (!preg_match('/^https?:\/\//', $url)) {
+ $url = $this->url . $url;
+ // Note: It didn't work for me without the last backslash
+ $url = rtrim($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->config['debug']) {
+ $log_line = "SeaFile $method: $url";
+ $json_opt = PHP_VERSION_ID >= 50400 ? JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0;
+
+ if (!empty($get)) {
+ $log_line .= ", GET: " . @json_encode($get, $json_opt);
+ }
+
+ if (!empty($post)) {
+ $log_line .= ", POST: " . preg_replace('/("password":)[^\},]+/', '\\1"*"', @json_encode($post, $json_opt));
+ }
+
+ if (!empty($upload)) {
+ $log_line .= ", Files: " . @json_encode(array_keys($upload), $json_opt);
+ }
+
+ 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);
+ }
+
+ 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 ($this->token) {
+ $this->request->setHeader('Authorization', "Token " . $this->token);
+ }
+
+ // some HTTP server configurations require this header
+ $this->request->setHeader('Accept', "application/json,text/javascript,*/*");
+
+ // proxy User-Agent string
+ $this->request->setHeader('User-Agent', $_SERVER['HTTP_USER_AGENT']);
+
+ // send request to the SeaFile API server
+ try {
+ $response = $this->request->send();
+ $this->status = $response->getStatus();
+ $body = $response->getBody();
+ }
+ catch (Exception $e) {
+ rcube::raise_error($e, true, false);
+ $this->status = self::CONNECTION_ERROR;
+ }
+
+ if ($this->config['debug']) {
+ rcube::write_log('console', "SeaFile Response [$this->status]: " . trim($body));
+ }
+
+ // request throttled, try again?
+ if ($this->status == self::TOO_MANY_REQUESTS) {
+ if (preg_match('/([0-9]+) second/', $body['detail'], $m) && ($seconds = $m[1]) < self::WAIT_LIMIT) {
+ sleep($seconds/2); // try to be smart and wait only a half of it
+ return $this->request($url, $method, $get, $post, $upload);
+ }
+ }
+
+ // decode response
+ return $this->status >= 400 ? false : @json_decode($body, true);
+ }
+
+ /**
+ * Return error code of last operation
+ */
+ public function is_error()
+ {
+ return $this->status >= 400 ? $this->status : false;
+ }
+
+ /**
+ * Authenticate to SeaFile API and get auth token
+ *
+ * @param string $username User name (email)
+ * @param string $password User password
+ *
+ * @return string Authentication token
+ */
+ public function authenticate($username, $password)
+ {
+ $result = $this->request('POST', 'auth-token', null, array(
+ 'username' => $username,
+ 'password' => $password,
+ ));
+
+ if ($result['token']) {
+ return $this->token = $result['token'];
+ }
+ }
+
+ /**
+ * Get account information
+ *
+ * @return array Account info (usage, total, email)
+ */
+ public function account_info()
+ {
+ return $this->request('GET', "account/info");
+ }
+
+ /**
+ * Delete a directory
+ *
+ * @param string $repo_id Library identifier
+ * @param string $dir Directory name (with path)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function directory_delete($repo_id, $dir)
+ {
+ // sanity checks
+ if ($dir === '' || $dir === '/' || !is_string($dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $this->request('DELETE', "repos/$repo_id/dir", array('p' => $dir));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Rename a directory
+ *
+ * @param string $repo_id Library identifier
+ * @param string $src_dir Directory name (with path)
+ * @param string $dest_dir New directory name (with path)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function directory_rename($repo_id, $src_dir, $dest_dir)
+ {
+ // sanity checks
+ if ($src_dir === '' || $src_dir === '/' || !is_string($src_dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($dest_dir === '' || $dest_dir === '/' || !is_string($dest_dir) || $dest_dir === $src_dir) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $result = $this->request('POST', "repos/$repo_id/dir", array('p' => $src_dir), array(
+ 'operation' => 'rename',
+ 'newname' => $dest_dir,
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Rename a directory
+ *
+ * @param string $repo_id Library identifier
+ * @param string $dir Directory name (with path)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function directory_create($repo_id, $dir)
+ {
+ // sanity checks
+ if ($dir === '' || $dir === '/' || !is_string($dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $result = $this->request('POST', "repos/$repo_id/dir", array('p' => $dir), array(
+ 'operation' => 'mkdir',
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * List directory entries (files and directories)
+ *
+ * @param string $repo_id Library identifier
+ * @param string $dir Directory name (with path)
+ *
+ * @return bool|array List of directories/files on success, False on failure
+ */
+ public function directory_entries($repo_id, $dir)
+ {
+ // sanity checks
+ if (!is_string($dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($dir === '') {
+ $dir = '/';
+ }
+
+ // args: p=<$name> ('/' is a root, default), oid=?
+ // sample result
+ // [{
+ // "id": "0000000000000000000000000000000000000000",
+ // "type": "file",
+ // "name": "test1.c",
+ // "size": 0
+ // },{
+ // "id": "e4fe14c8cda2206bb9606907cf4fca6b30221cf9",
+ // "type": "dir",
+ // "name": "test_dir"
+ // }]
+
+ return $this->request('GET', "repos/$repo_id/dir", array('p' => $dir));
+ }
+
+ /**
+ * Update a file
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ * @param array $file File data (data, type, name)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_update($repo_id, $filename, $file)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ // first get the update link
+ $result = $this->request('GET', "repos/$repo_id/update-link");
+
+ if ($this->is_error() || empty($result)) {
+ return false;
+ }
+
+ $path = explode('/', $filename);
+ $fn = array_pop($path);
+
+ // then update file
+ $result = $this->request('POST', $result, null, array(
+ 'filename' => $fn,
+ 'target_file' => $filename,
+ ),
+ array('file' => $file)
+ );
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Upload a file
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ * @param array $file File data (data, type, name)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_upload($repo_id, $filename, $file)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ // first get upload link
+ $result = $this->request('GET', "repos/$repo_id/upload-link");
+
+ if ($this->is_error() || empty($result)) {
+ return false;
+ }
+
+ $path = explode('/', $filename);
+ $filename = array_pop($path);
+ $dir = '/' . ltrim(implode('/', $path), '/');
+
+ // then update file
+ $result = $this->request('POST', $result, null, array(
+ 'parent_dir' => $dir
+ ),
+ array('file' => $file)
+ );
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Delete a file
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_delete($repo_id, $filename)
+ {
+ // sanity check
+ if ($filename === '' || $filename === '/' || !is_string($filename)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $this->request('DELETE', "repos/$repo_id/file", array('p' => $filename));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Copy file(s) (no rename here)
+ *
+ * @param string $repo_id Library identifier
+ * @param string|array $files List of files (without path)
+ * @param string $src_dir Source directory
+ * @param string $dest_dir Destination directory
+ * @param string $dest_repo Destination library (optional)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_copy($repo_id, $files, $src_dir, $dest_dir, $dest_repo)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($src_dir === '' || !is_string($src_dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($dest_dir === '' || !is_string($dest_dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ((!is_array($files) && !strlen($files)) || (is_array($files) && empty($files))) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if (empty($dest_repo)) {
+ $dest_repo = $repo_id;
+ }
+
+ $result = $this->request('POST', "repos/$repo_id/fileops/copy", array('p' => $src_dir), array(
+ 'file_names' => implode(':', (array) $files),
+ 'dst_dir' => $dest_dir,
+ 'dst_repo' => $dest_repo,
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Move a file (no rename here)
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ * @param string $dst_dir Destination directory
+ * @param string $dst_repo Destination library (optional)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_move($repo_id, $filename, $dst_dir, $dst_repo = null)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($filename === '' || !is_string($filename)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($dst_dir === '' || !is_string($dst_dir)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if (empty($dst_repo)) {
+ $dst_repo = $repo_id;
+ }
+
+ $result = $this->request('POST', "repos/$repo_id/file", array('p' => $filename), array(
+ 'operation' => 'move',
+ 'dst_dir' => $dst_dir,
+ 'dst_repo' => $dst_repo,
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Rename a file
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ * @param string $new_name New file name (without path)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_rename($repo_id, $filename, $new_name)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($filename === '' || !is_string($filename)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($new_name === '' || !is_string($new_name)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $result = $this->request('POST', "repos/$repo_id/file", array('p' => $filename), array(
+ 'operation' => 'rename',
+ 'newname' => $new_name,
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Create an empty file
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ *
+ * @return bool True on success, False on failure
+ */
+ public function file_create($repo_id, $filename)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($filename === '' || !is_string($filename)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $result = $this->request('POST', "repos/$repo_id/file", array('p' => $filename), array(
+ 'operation' => 'create',
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Get file info
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ *
+ * @return bool|array File info on success, False on failure
+ */
+ public function file_info($repo_id, $filename)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($filename === '' || !is_string($filename)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ // sample result:
+ // "id": "013d3d38fed38b3e8e26b21bb3463eab6831194f",
+ // "mtime": 1398148877,
+ // "type": "file",
+ // "name": "foo.py",
+ // "size": 22
+
+ return $this->request('GET', "repos/$repo_id/file/detail", array('p' => $filename));
+ }
+
+ /**
+ * Get file content
+ *
+ * @param string $repo_id Library identifier
+ * @param string $filename File name (with path)
+ *
+ * @return bool|string File download URI on success, False on failure
+ */
+ public function file_get($repo_id, $filename)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($filename === '' || !is_string($filename)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ return $this->request('GET', "repos/$repo_id/file", array('p' => $filename));
+ }
+
+ /**
+ * List libraries (repositories)
+ *
+ * @return array|bool List of libraries on success, False on failure
+ */
+ public function library_list()
+ {
+ $result = $this->request('GET', "repos");
+
+ // sample result
+ // [{
+ // "permission": "rw",
+ // "encrypted": false,
+ // "mtime": 1400054900,
+ // "owner": "user@mail.com",
+ // "id": "f158d1dd-cc19-412c-b143-2ac83f352290",
+ // "size": 0,
+ // "name": "foo",
+ // "type": "repo",
+ // "virtual": false,
+ // "desc": "new library",
+ // "root": "0000000000000000000000000000000000000000"
+ // }]
+
+ return $result;
+ }
+
+ /**
+ * Get library info
+ *
+ * @param string $repo_id Library identifier
+ *
+ * @return array|bool Library info on success, False on failure
+ */
+ public function library_info($repo_id)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ return $this->request('GET', "repos/$repo_id");
+ }
+
+ /**
+ * Create library
+ *
+ * @param string $name Library name
+ * @param string $description Library description
+ *
+ * @return bool|array Library info on success, False on failure
+ */
+ public function library_create($name, $description = '')
+ {
+ if ($name === '' || !is_string($name)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ return $this->request('POST', "repos", null, array(
+ 'name' => $name,
+ 'desc' => $description,
+ ));
+ }
+
+ /**
+ * Rename library
+ *
+ * @param string $repo_id Library identifier
+ * @param string $new_name Library description
+ *
+ * @return bool True on success, False on failure
+ */
+ public function library_rename($repo_id, $name, $description = '')
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ if ($name === '' || !is_string($name)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ // Note: probably by mistake the 'op' is a GET parameter
+ // maybe changed in future to be consistent with other methods
+ $this->request('POST', "repos/$repo_id", array('op' => 'rename'), array(
+ 'repo_name' => $name,
+ 'repo_desc' => $description,
+ ));
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Delete library
+ *
+ * @param string $repo_id Library identifier
+ *
+ * @return bool True on success, False on failure
+ */
+ public function library_delete($repo_id)
+ {
+ if ($repo_id === '' || !is_string($repo_id)) {
+ $this->status = self::BAD_REQUEST;
+ return false;
+ }
+
+ $this->request('DELETE', "repos/$repo_id");
+
+ return $this->is_error() === false;
+ }
+
+ /**
+ * Ping the API server
+ *
+ * @param string $token If set, auth token will be used
+ *
+ * @param bool True on success, False on failure
+ */
+ public function ping($token = null)
+ {
+ // can be used to check if token is still valid
+ if ($token) {
+ $this->token = $token;
+
+ $result = $this->request('GET', 'auth/ping', null, null);
+ }
+ // or if api works
+ else {
+ $result = $this->request('GET', 'ping', null, null);
+ }
+
+ return $this->is_error() === false;
+ }
+}
diff --git a/lib/drivers/seafile/seafile_file_storage.php b/lib/drivers/seafile/seafile_file_storage.php
new file mode 100644
index 0000000..524cf78
--- /dev/null
+++ b/lib/drivers/seafile/seafile_file_storage.php
@@ -0,0 +1,994 @@
+<?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;
+
+
+ /**
+ * 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['seafile_user'] = $username;
+ $_SESSION['seafile_token'] = $this->rc->encrypt($token);
+ $_SESSION['seafile_pass'] = $this->rc->encrypt($password);
+
+ return true;
+ }
+
+ $this->api = false;
+
+ return false;
+ }
+
+ /**
+ * 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('seafile_host', 'localhost'),
+ 'ssl_verify_peer' => $this->rc->config->get('seafile_ssl_verify_peer', true),
+ 'ssl_verify_host' => $this->rc->config->get('seafile_ssl_verify_host', true),
+ 'debug' => $this->rc->config->get('seafile_debug', false),
+ );
+
+ $this->config = array_merge($this->config, $config);
+
+ // initialize Web API
+ $this->api = new seafile_api($this->config);
+
+ if ($skip_auth) {
+ return true;
+ }
+
+ // try session token
+ if ($_SESSION['seafile_token'] && ($token = $this->rc->decrypt($_SESSION['seafile_token']))) {
+ $valid = $this->api->ping($token);
+ }
+
+ if (!$valid && $_SESSION['seafile_password'] && $_SESSION['seafile_user']) {
+ $pass = $this->rc->decrypt($_SESSION['seafile_pass']);
+ $valid = $this->authenticate($_SESSION['seafile_user'], $pass);
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Configures environment
+ *
+ * @param array $config Configuration
+ */
+ public function configure($config)
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 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,
+ );
+ }
+
+ /**
+ * 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);
+ }
+
+ $file['data'] = $file['path'];
+
+ $created = $this->api->file_upload($repo_id, $fn, $file);
+
+ 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 {
+ $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)
+ * @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']) {
+ $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']) {
+ header("Location: $link");
+ }
+ die;
+ }
+
+ /**
+ * 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']) : '',
+ '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')
+ *
+ * @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);
+ $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) === 0) {
+ break 2;
+ }
+ }
+
+ continue 2;
+ }
+ }
+ }
+
+ $filename = $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']) : '',
+ '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)
+ {
+ 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_old, $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 (!$saved) {
+ 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);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Returns list of folders.
+ *
+ * @return array List of folders
+ * @throws Exception
+ */
+ public function folder_list()
+ {
+ $libraries = $this->libraries();
+ $folders = array();
+
+ foreach ($this->libraries as $library) {
+ if ($library['virtual'] || $library['encrypted']) {
+ continue;
+ }
+
+ $folders[] = $library['name'];
+
+ if ($folder_tree = $this->folders_tree($library, '')) {
+ $folders = array_merge($folders, $folder_tree);
+ }
+ }
+
+ if (empty($folders)) {
+ throw new Exception("Storage error. Unable to get folders list.", file_storage::ERROR);
+ }
+
+ // sort folders
+ usort($folders, array($this, 'sort_folder_comparator'));
+
+ return $folders;
+ }
+
+ /**
+ * Recursively builds folders list
+ */
+ protected function folders_tree($library, $folder)
+ {
+ $folders = array();
+ $length = strlen($folder);
+
+ if ($content = $this->api->directory_entries($library['id'], '/' . $folder)) {
+ foreach ($content as $item) {
+ if ($item['type'] == 'dir' && strlen($item['name'])) {
+ $f = ($length ? $folder . '/' : '') . $item['name'];
+ $folders[] = $library['name'] . '/' . $f;
+
+ $folders_tree = $this->folders_tree($library, $f);
+ if (!empty($folders_tree)) {
+ $folders = array_merge($folders, $folders_tree);
+ }
+ }
+ }
+ }
+
+ return $folders;
+ }
+
+ /**
+ * Callback for uasort() that implements correct
+ * locale-aware case-sensitive sorting
+ */
+ protected function sort_folder_comparator($str1, $str2)
+ {
+ $path1 = explode('/', $str1);
+ $path2 = explode('/', $str2);
+
+ foreach ($path1 as $idx => $folder1) {
+ $folder2 = $path2[$idx];
+
+ if ($folder1 === $folder2) {
+ continue;
+ }
+
+ return strcoll($folder1, $folder2);
+ }
+ }
+
+ /**
+ * 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
+ );
+ }
+
+ /**
+ * 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 $uri URI
+ * @param bool $child_locks Enables subtree checks
+ *
+ * @return array List of locks
+ * @throws Exception
+ */
+ public function lock_list($uri, $child_locks = false)
+ {
+ $this->init_lock_db();
+
+ // convert URI to global resource string
+ $uri = $this->uri2resource($uri);
+
+ // 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->resource2uri($lock['uri']);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Locks a URI
+ *
+ * @param string $uri URI
+ * @param array $lock Lock data
+ * - depth: 0/'infinite'
+ * - scope: 'shared'/'exclusive'
+ * - owner: string
+ * - token: string
+ * - timeout: int
+ *
+ * @throws Exception
+ */
+ public function lock($uri, $lock)
+ {
+ $this->init_lock_db();
+
+ // convert URI to global resource string
+ $uri = $this->uri2resource($uri);
+
+ 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 URI
+ * @param array $lock Lock data
+ *
+ * @throws Exception
+ */
+ public function unlock($uri, $lock)
+ {
+ $this->init_lock_db();
+
+ // convert URI to global resource string
+ $uri = $this->uri2resource($uri);
+
+ 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(
+ 'total' => $account_info['total'],
+ 'usage' => $account_info['usage'],
+ );
+
+ return $quota;
+ }
+
+ /**
+ * 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;
+ }
+
+ protected function uri2resource($uri)
+ {
+ list($file, $repo_id, $library) = $this->find_library($uri);
+
+ // convert to imap charset (to be safe to store in DB)
+ $uri = rcube_charset::convert($uri, RCUBE_CHARSET, 'UTF7-IMAP');
+
+ return 'seafile://' . urlencode($library['owner']) . '@' . $this->config['host'] . '/' . $uri;
+ }
+
+ protected function resource2uri($resource)
+ {
+ if (!preg_match('|^seafile://([^@]+)@([^/]+)/(.*)$|', $resource, $matches)) {
+ throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
+ }
+
+ $user = urldecode($matches[1]);
+ $uri = $matches[3];
+
+ // convert from imap charset (to be safe to store in DB)
+ $uri = rcube_charset::convert($uri, 'UTF7-IMAP', RCUBE_CHARSET);
+
+ return $uri;
+ }
+
+ /**
+ * 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_request_observer.php b/lib/drivers/seafile/seafile_request_observer.php
new file mode 100644
index 0000000..ec09c1f
--- /dev/null
+++ b/lib/drivers/seafile/seafile_request_observer.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Observer for HTTP_Request2 implementing saving response body into a file
+ */
+class seafile_request_observer implements SplObserver
+{
+ protected $file;
+ protected $fp;
+
+ public function set_file($file)
+ {
+ $this->file = $file;
+ }
+
+ public function set_fp($fp)
+ {
+ $this->fp = $fp;
+ }
+
+ public function update(SplSubject $subject)
+ {
+ $event = $subject->getLastEvent();
+
+ switch ($event['name']) {
+ case 'receivedHeaders':
+ if ($this->file) {
+ $target = $this->dir . DIRECTORY_SEPARATOR . $this->file;
+ if (!($this->fp = @fopen($target, 'wb'))) {
+ throw new Exception("Cannot open target file '{$target}'");
+ }
+ }
+ else if (!$this->fp) {
+ throw new Exception("Cannot open target file '{$target}'");
+ }
+
+ break;
+
+ case 'receivedBodyPart':
+ case 'receivedEncodedBodyPart':
+ fwrite($this->fp, $event['data']);
+ break;
+
+ case 'receivedBody':
+ fclose($this->fp);
+ break;
+ }
+ }
+}
diff --git a/lib/file_api.php b/lib/file_api.php
index c9631c6..3464f1c 100644
--- a/lib/file_api.php
+++ b/lib/file_api.php
@@ -1,850 +1,850 @@
<?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_api
{
const ERROR_CODE = 500;
const OUTPUT_JSON = 'application/json';
const OUTPUT_HTML = 'text/html';
public $session;
public $api;
private $app_name = 'Kolab File API';
private $conf;
private $browser;
private $output_type = self::OUTPUT_JSON;
private $config = array(
'date_format' => 'Y-m-d H:i',
'language' => 'en_US',
);
public function __construct()
{
$rcube = rcube::get_instance();
$rcube->add_shutdown_function(array($this, 'shutdown'));
$this->conf = $rcube->config;
$this->session_init();
}
/**
* Initialise backend class
*/
protected function api_init()
{
if ($this->api) {
return;
}
$driver = $this->conf->get('fileapi_backend', 'kolab');
$class = $driver . '_file_storage';
- $include_path = RCUBE_INSTALL_PATH . '/lib/' . $driver . PATH_SEPARATOR;
+ $include_path = RCUBE_INSTALL_PATH . "/lib/drivers/$driver" . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
$this->api = new $class;
// configure api
$this->api->configure(!empty($_SESSION['config']) ? $_SESSION['config'] : $this->config);
}
/**
* Process the request and dispatch it to the requested service
*/
public function run()
{
$this->request = strtolower($_GET['method']);
// Check the session, authenticate the user
if (!$this->session_validate()) {
$this->session->destroy(session_id());
if ($this->request == 'authenticate') {
$this->session->regenerate_id(false);
if ($username = $this->authenticate()) {
$_SESSION['user'] = $username;
$_SESSION['time'] = time();
$_SESSION['config'] = $this->config;
$this->output_success(array(
'token' => session_id(),
'capabilities' => $this->capabilities(),
));
}
}
throw new Exception("Invalid session", 403);
}
// Call service method
$result = $this->request_handler($this->request);
// Send success response, errors should be handled by driver class
// by throwing exceptions or sending output by itself
$this->output_success($result);
}
/**
* Session validation check and session start
*/
private function session_validate()
{
$sess_id = rcube_utils::request_header('X-Session-Token') ?: $_REQUEST['token'];
if (empty($sess_id)) {
session_start();
return false;
}
session_id($sess_id);
session_start();
if (empty($_SESSION['user'])) {
return false;
}
$timeout = $this->conf->get('session_lifetime', 0) * 60;
if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
return false;
}
// update session time
$_SESSION['time'] = time();
return true;
}
/**
* Initializes session
*/
private function session_init()
{
$rcube = rcube::get_instance();
$sess_name = $this->conf->get('session_name');
$lifetime = $this->conf->get('session_lifetime', 0) * 60;
if ($lifetime) {
ini_set('session.gc_maxlifetime', $lifetime * 2);
}
ini_set('session.name', $sess_name ? $sess_name : 'file_api_sessid');
ini_set('session.use_cookies', 0);
ini_set('session.serialize_handler', 'php');
// use database for storing session data
$this->session = new rcube_session($rcube->get_dbh(), $this->conf);
$this->session->register_gc_handler(array($rcube, 'gc'));
$this->session->set_secret($this->conf->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
$this->session->set_ip_check($this->conf->get('ip_check'));
// this is needed to correctly close session in shutdown function
$rcube->session = $this->session;
}
/**
* Script shutdown handler
*/
public function shutdown()
{
// write performance stats to logs/console
if ($this->conf->get('devel_mode')) {
if (function_exists('memory_get_peak_usage'))
$mem = memory_get_peak_usage();
else if (function_exists('memory_get_usage'))
$mem = memory_get_usage();
$log = trim($this->request . ($mem ? sprintf(' [%.1f MB]', $mem/1024/1024) : ''));
if (defined('FILE_API_START')) {
rcube::print_timer(FILE_API_START, $log);
}
else {
rcube::console($log);
}
}
}
/**
* Authentication request handler (HTTP Auth)
*/
private function authenticate()
{
if (isset($_POST['username'])) {
$username = $_POST['username'];
$password = $_POST['password'];
}
else if (!empty($_SERVER['PHP_AUTH_USER'])) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
}
// when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule
else if (!isset($_SERVER['PHP_AUTH_USER'])) {
// "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..."
if (isset($_SERVER["REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REMOTE_USER"], 6));
}
else if (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
}
else if (isset($_SERVER["Authorization"])) {
$basicAuthData = base64_decode(substr($_SERVER["Authorization"], 6));
}
else if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
$basicAuthData = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6));
}
if (isset($basicAuthData) && !empty($basicAuthData)) {
list($username, $password) = explode(":", $basicAuthData);
}
}
if (!empty($username)) {
$this->api_init();
$result = $this->api->authenticate($username, $password);
}
if (empty($result)) {
/*
header('WWW-Authenticate: Basic realm="' . $this->app_name .'"');
header('HTTP/1.1 401 Unauthorized');
exit;
*/
throw new Exception("Invalid password or username", file_api::ERROR_CODE);
}
return $username;
}
/**
* Storage/System method handler
*/
private function request_handler($request)
{
// handle "global" requests that don't require api driver
switch ($request) {
case 'ping':
return array();
case 'quit':
$this->session->destroy(session_id());
return array();
case 'configure':
foreach (array_keys($this->config) as $name) {
if (isset($_GET[$name])) {
$this->config[$name] = $_GET[$name];
}
}
$_SESSION['config'] = $this->config;
return $this->config;
case 'upload_progress':
return $this->upload_progress();
case 'mimetypes':
return $this->supported_mimetypes();
case 'capabilities':
// this one actually uses api driver, but we put it here
// because we'd need session for the api driver
return $this->capabilities();
}
// init API driver
$this->api_init();
// GET arguments
$args = &$_GET;
// POST arguments (JSON)
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$post = file_get_contents('php://input');
$args += (array) json_decode($post, true);
unset($post);
}
// disable script execution time limit, so we can handle big files
@set_time_limit(0);
// handle request
switch ($request) {
case 'file_list':
$params = array('reverse' => !empty($args['reverse']) && rcube_utils::get_boolean($args['reverse']));
if (!empty($args['sort'])) {
$params['sort'] = strtolower($args['sort']);
}
if (!empty($args['search'])) {
$params['search'] = $args['search'];
if (!is_array($params['search'])) {
$params['search'] = array('name' => $params['search']);
}
}
return $this->api->file_list($args['folder'], $params);
case 'file_upload':
// for Opera upload frame response cannot be application/json
$this->output_type = self::OUTPUT_HTML;
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
$uploads = $this->upload();
$result = array();
foreach ($uploads as $file) {
$this->api->file_create($args['folder'] . file_storage::SEPARATOR . $file['name'], $file);
unset($file['path']);
$result[$file['name']] = array(
'type' => $file['type'],
'size' => $file['size'],
);
}
return $result;
case 'file_create':
case 'file_update':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
if (!isset($args['content'])) {
throw new Exception("Missing file content", file_api::ERROR_CODE);
}
$file = array(
'content' => $args['content'],
'type' => rcube_mime::file_content_type($args['content'], $args['file'], $args['content-type'], true),
);
$this->api->$request($args['file'], $file);
if (!empty($args['info']) && rcube_utils::get_boolean($args['info'])) {
return $this->api->file_info($args['file']);
}
return;
case 'file_delete':
$files = (array) $args['file'];
if (empty($files)) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
foreach ($files as $file) {
$this->api->file_delete($file);
}
return;
case 'file_info':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
$info = $this->api->file_info($args['file']);
if (!empty($args['viewer']) && rcube_utils::get_boolean($args['viewer'])) {
$this->file_viewer_info($args['file'], $info);
}
return $info;
case 'file_get':
$this->output_type = self::OUTPUT_HTML;
if (!isset($args['file']) || $args['file'] === '') {
header("HTTP/1.0 ".file_api::ERROR_CODE." Missing file name");
}
$params = array(
'force-download' => !empty($args['force-download']) && rcube_utils::get_boolean($args['force-download']),
'force-type' => $args['force-type'],
);
if (!empty($args['viewer'])) {
$this->file_view($args['file'], $args['viewer'], $args, $params);
}
try {
$this->api->file_get($args['file'], $params);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
}
exit;
case 'file_move':
case 'file_copy':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
if (is_array($args['file'])) {
if (empty($args['file'])) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
}
else {
if (!isset($args['new']) || $args['new'] === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
$args['file'] = array($args['file'] => $args['new']);
}
$overwrite = !empty($args['overwrite']) && rcube_utils::get_boolean($args['overwrite']);
$files = (array) $args['file'];
$errors = array();
foreach ($files as $file => $new_file) {
if ($new_file === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
if ($new_file === $file) {
throw new Exception("Old and new file name is the same", file_api::ERROR_CODE);
}
try {
$this->api->{$request}($file, $new_file);
}
catch (Exception $e) {
if ($e->getCode() == file_storage::ERROR_FILE_EXISTS) {
// delete existing file and do copy/move again
if ($overwrite) {
$this->api->file_delete($new_file);
$this->api->{$request}($file, $new_file);
}
// collect file-exists errors, so the client can ask a user
// what to do and skip or replace file(s)
else {
$errors[] = array(
'src' => $file,
'dst' => $new_file,
);
}
}
else {
throw $e;
}
}
}
if (!empty($errors)) {
return array('already_exist' => $errors);
}
return;
case 'folder_create':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_create($args['folder']);
case 'folder_delete':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_delete($args['folder']);
case 'folder_rename':
case 'folder_move':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing source folder name", file_api::ERROR_CODE);
}
if (!isset($args['new']) || $args['new'] === '') {
throw new Exception("Missing destination folder name", file_api::ERROR_CODE);
}
if ($args['new'] === $args['folder']) {
return;
}
return $this->api->folder_move($args['folder'], $args['new']);
case 'folder_list':
return $this->api->folder_list();
case 'quota':
$quota = $this->api->quota($args['folder']);
if (!$quota['total']) {
$quota_result['percent'] = 0;
}
else if ($quota['total']) {
if (!isset($quota['percent'])) {
$quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
}
}
return $quota;
case 'lock':
// arguments: uri, owner, timeout, scope, depth, token
foreach (array('uri', 'token') as $arg) {
if (!isset($args[$arg]) || $args[$arg] === '') {
throw new Exception("Missing lock $arg", file_api::ERROR_CODE);
}
}
$this->api->lock($args['uri'], $args);
return;
case 'unlock':
foreach (array('uri', 'token') as $arg) {
if (!isset($args[$arg]) || $args[$arg] === '') {
throw new Exception("Missing lock $arg", file_api::ERROR_CODE);
}
}
$this->api->unlock($args['uri'], $args);
return;
case 'lock_list':
$child_locks = !empty($args['child_locks']) && rcube_utils::get_boolean($args['child_locks']);
return $this->api->lock_list($args['uri'], $child_locks);
}
if ($request) {
throw new Exception("Unknown method", 501);
}
}
/**
* File uploads handler
*/
protected function upload()
{
$files = array();
if (is_array($_FILES['file']['tmp_name'])) {
foreach ($_FILES['file']['tmp_name'] as $i => $filepath) {
if ($err = $_FILES['file']['error'][$i]) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$maxsize = ini_get('upload_max_filesize');
$maxsize = $this->show_bytes(parse_bytes($maxsize));
throw new Exception("Maximum file size ($maxsize) exceeded", file_api::ERROR_CODE);
}
throw new Exception("File upload failed", file_api::ERROR_CODE);
}
$files[] = array(
'path' => $filepath,
'name' => $_FILES['file']['name'][$i],
'size' => filesize($filepath),
'type' => rcube_mime::file_content_type($filepath, $_FILES['file']['name'][$i], $_FILES['file']['type']),
);
}
}
else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// if filesize exceeds post_max_size then $_FILES array is empty,
if ($maxsize = ini_get('post_max_size')) {
$maxsize = $this->show_bytes(parse_bytes($maxsize));
throw new Exception("Maximum file size ($maxsize) exceeded", file_api::ERROR_CODE);
}
throw new Exception("File upload failed", file_api::ERROR_CODE);
}
return $files;
}
/**
* File upload progress handler
*/
protected function upload_progress()
{
if (function_exists('apc_fetch')) {
$prefix = ini_get('apc.rfc1867_prefix');
$uploadid = rcube_utils::get_input_value('id', rcube_utils::INPUT_GET);
$status = apc_fetch($prefix . $uploadid);
if (!empty($status)) {
$status['percent'] = round($status['current']/$status['total']*100);
if ($status['percent'] < 100) {
$diff = time() - intval($status['start_time']);
// calculate time to end of uploading (in seconds)
$status['eta'] = intval($diff * (100 - $status['percent']) / $status['percent']);
// average speed (bytes per second)
$status['rate'] = intval($status['current'] / $diff);
}
}
$status['id'] = $uploadid;
return $status; // id, done, total, current, percent, start_time, eta, rate
}
throw new Exception("Not supported", file_api::ERROR_CODE);
}
/*
* Returns API capabilities
*/
protected function capabilities()
{
$this->api_init();
$caps = array();
// check support for upload progress
if (($progress_sec = $this->conf->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;
}
foreach ($this->api->capabilities() as $name => $value) {
// skip disabled capabilities
if ($value !== false) {
$caps[$name] = $value;
}
}
return $caps;
}
/**
* Return mimetypes list supported by built-in viewers
*
* @return array List of mimetypes
*/
protected function supported_mimetypes()
{
$mimetypes = array();
$dir = RCUBE_INSTALL_PATH . 'lib/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);
$mimetypes = array_merge($mimetypes, $viewer->supported_mimetypes());
}
}
closedir($handle);
}
return $mimetypes;
}
/**
* Merge file viewer data into file info
*/
protected function file_viewer_info($file, &$info)
{
if ($viewer = $this->find_viewer($info['type'])) {
$info['viewer'] = array();
if ($frame = $viewer->frame($file, $info['type'])) {
$info['viewer']['frame'] = $frame;
}
else if ($href = $viewer->href($file, $info['type'])) {
$info['viewer']['href'] = $href;
}
}
}
/**
* File vieweing request handler
*/
protected function file_view($file, $viewer, &$args, &$params)
{
$path = RCUBE_INSTALL_PATH . "lib/viewers/$viewer.php";
$class = "file_viewer_$viewer";
if (!file_exists($path)) {
return;
}
// get file info
try {
$info = $this->api->file_info($file);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
exit;
}
include_once $path;
$viewer = new $class($this);
// check if specified viewer supports file type
// otherwise return (fallback to file_get action)
if (!$viewer->supports($info['type'])) {
return;
}
$viewer->output($file, $info['type']);
exit;
}
/**
* Return built-in viewer opbject for specified mimetype
*
* @return object Viewer object
*/
protected function find_viewer($mimetype)
{
$dir = RCUBE_INSTALL_PATH . 'lib/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 ($viewer->supports($mimetype)) {
return $viewer;
}
}
}
closedir($handle);
}
}
/**
* Returns complete File URL
*
* @param string $file File name (with path)
*
* @return string File URL
*/
public function file_url($file)
{
return file_utils::script_uri(). '?method=file_get'
. '&file=' . urlencode($file)
. '&token=' . urlencode(session_id());
}
/**
* Returns web browser object
*
* @return rcube_browser Web browser object
*/
public function get_browser()
{
if ($this->browser === null) {
$this->browser = new rcube_browser;
}
return $this->browser;
}
/**
* Send success response
*
* @param mixed $data Data
*/
public function output_success($data)
{
if (!is_array($data)) {
$data = array();
}
$response = array('status' => 'OK', 'result' => $data);
if (!empty($_REQUEST['req_id'])) {
$response['req_id'] = $_REQUEST['req_id'];
}
$this->output_send($response);
}
/**
* Send error response
*
* @param mixed $response Response data
* @param int $code Error code
*/
public function output_error($response, $code = null)
{
if (is_string($response)) {
$response = array('reason' => $response);
}
$response['status'] = 'ERROR';
if ($code) {
$response['code'] = $code;
}
if (!empty($_REQUEST['req_id'])) {
$response['req_id'] = $_REQUEST['req_id'];
}
if (empty($response['code'])) {
$response['code'] = file_api::ERROR_CODE;
}
$this->output_send($response);
}
/**
* Send response
*
* @param mixed $data Data
*/
protected function output_send($data)
{
// Send response
header("Content-Type: {$this->output_type}; charset=utf-8");
echo json_encode($data);
exit;
}
/**
* Create a human readable string for a number of bytes
*
* @param int Number of bytes
*
* @return string Byte string
*/
public function show_bytes($bytes)
{
if ($bytes >= 1073741824) {
$gb = $bytes/1073741824;
$str = sprintf($gb >= 10 ? "%d " : "%.1f ", $gb) . 'GB';
}
else if ($bytes >= 1048576) {
$mb = $bytes/1048576;
$str = sprintf($mb >= 10 ? "%d " : "%.1f ", $mb) . 'MB';
}
else if ($bytes >= 1024) {
$str = sprintf("%d ", round($bytes/1024)) . 'KB';
}
else {
$str = sprintf('%d ', $bytes) . 'B';
}
return $str;
}
}
diff --git a/lib/file_utils.php b/lib/file_utils.php
index 8ad36a9..416d151 100644
--- a/lib/file_utils.php
+++ b/lib/file_utils.php
@@ -1,140 +1,188 @@
<?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',
+ '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;
}
}
diff --git a/lib/init.php b/lib/init.php
index 6b8e86a..e57abd9 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -1,40 +1,40 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab File API |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| 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> |
+--------------------------------------------------------------------------+
*/
// Roundcube Framework constants
define('FILE_API_START', microtime(true));
define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__)) . '/../');
define('RCUBE_CONFIG_DIR', RCUBE_INSTALL_PATH . 'config/');
-define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'lib/kolab/plugins');
+define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'lib/drivers/kolab/plugins');
// Define include path
$include_path = RCUBE_INSTALL_PATH . '/lib' . PATH_SEPARATOR;
$include_path .= RCUBE_INSTALL_PATH . '/lib/ext' . PATH_SEPARATOR;
$include_path .= RCUBE_INSTALL_PATH . '/lib/client' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
// include global functions from Roundcube Framework
require_once 'Roundcube/bootstrap.php';

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jun 10, 5:14 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
196957
Default Alt Text
(110 KB)

Event Timeline