Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2533613
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
48 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/doc/SQL/mysql.initial.sql b/doc/SQL/mysql.initial.sql
index 87c03aa..0f8364f 100644
--- a/doc/SQL/mysql.initial.sql
+++ b/doc/SQL/mysql.initial.sql
@@ -1,23 +1,24 @@
CREATE TABLE IF NOT EXISTS `chwala_locks` (
`uri` varchar(512) BINARY NOT NULL,
`owner` varchar(256),
`timeout` integer unsigned,
`expires` datetime DEFAULT NULL,
`token` varchar(256),
`scope` tinyint,
`depth` tinyint,
INDEX `uri_index` (`uri`, `depth`),
INDEX `expires_index` (`expires`),
INDEX `token_index` (`token`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE IF NOT EXISTS `chwala_sessions` (
`id` varchar(40) BINARY NOT NULL,
`uri` varchar(1024) BINARY NOT NULL,
+ `owner` varchar(255) BINARY NOT NULL,
`data` mediumtext,
PRIMARY KEY (`id`),
- UNIQUE INDEX `uri_index` (`uri`(255)),
- INDEX `expires_index` (`expires`)
+ INDEX `uri_index` (`uri`(255)),
+ INDEX `owner` (`owner`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
INSERT INTO `system` (`name`, `value`) VALUES ('chwala-version', '2015110400');
diff --git a/lib/api/document.php b/lib/api/document.php
index a057b67..aef2a3b 100644
--- a/lib/api/document.php
+++ b/lib/api/document.php
@@ -1,117 +1,143 @@
<?php
/**
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2015, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_api_document extends file_api_common
{
/**
* Request handler
*/
public function handle()
{
- if (empty($_GET['id'])) {
- throw new Exception("Missing document ID.", file_api_core::ERROR_CODE);
- }
-
- $method = $_SERVER['REQUEST_METHOD'];
+ $method = $_SERVER['REQUEST_METHOD'];
+ $this->args = $_GET;
if ($method == 'POST' && !empty($_SERVER['HTTP_X_HTTP_METHOD'])) {
$method = $_SERVER['HTTP_X_HTTP_METHOD'];
}
- $file = $this->get_file_path($_GET['id']);
-
+ // Document content actions for Manticore
if ($method == 'PUT' || $method == 'GET') {
+ if (empty($this->args['id'])) {
+ throw new Exception("Missing document ID.", file_api_core::ERROR_CODE);
+ }
+
+ $file = $this->get_file_path($this->args['id']);
+
return $this->{'document_' . strtolower($method)}($file);
}
+ // Sessions and invitations management
+ else if ($method == 'POST' && $_GET['method'] == 'document_delete') {
+ $post = file_get_contents('php://input');
+ $this->args += (array) json_decode($post, true);
+ unset($post);
+
+ if (empty($this->args['id'])) {
+ throw new Exception("Missing document ID.", file_api_core::ERROR_CODE);
+ }
+
+ return $this->document_delete($this->args['id']);
+ }
}
/**
* Get file path from manticore session identifier
*/
protected function get_file_path($id)
{
$manticore = new file_manticore($this->api);
return $manticore->session_file($id);
}
+ /**
+ * Close (delete) manticore session
+ */
+ protected function document_delete($id)
+ {
+ $manticore = new file_manticore($this->api);
+
+ if (!$manticore->session_delete($id)) {
+ throw new Exception("Failed deleting the document session.", file_api_core::ERROR_CODE);
+ }
+ }
+
/**
* Update document file content
*/
protected function document_put($file)
{
list($driver, $path) = $this->api->get_driver($file);
$length = rcube_utils::request_header('Content-Length');
$tmp_dir = unslashify($this->api->config->get('temp_dir'));
$tmp_path = tempnam($tmp_dir, 'chwalaUpload');
// Create stream to copy input into a temp file
$input = fopen('php://input', 'r');
$tmp_file = fopen($tmp_path, 'w');
if (!$input || !$tmp_file) {
throw new Exception("Failed opening input or temp file stream.", file_api_core::ERROR_CODE);
}
// Create temp file from the input
$copied = stream_copy_to_stream($input, $tmp_file);
fclose($input);
fclose($tmp_file);
if ($copied < $length) {
throw new Exception("Failed writing to temp file.", file_api_core::ERROR_CODE);
}
$file = array(
'path' => $tmp_path,
'type' => rcube_mime::file_content_type($tmp_path, $file),
);
$driver->file_update($path, $file);
// remove the temp file
unlink($tmp_path);
}
/**
* Return document file content
*/
protected function document_get($file)
{
list($driver, $path) = $this->api->get_driver($file);
try {
$params = array('force-type' => 'application/vnd.oasis.opendocument.text');
$driver->file_get($path, $params);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api_core::ERROR_CODE . " " . $e->getMessage());
}
exit;
}
}
diff --git a/lib/api/file_info.php b/lib/api/file_info.php
index 47c55a8..5f7ba62 100644
--- a/lib/api/file_info.php
+++ b/lib/api/file_info.php
@@ -1,97 +1,98 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2015, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_api_file_info extends file_api_common
{
/**
* Request handler
*/
public function handle()
{
parent::handle();
if (!isset($this->args['file']) || $this->args['file'] === '') {
throw new Exception("Missing file name", file_api_core::ERROR_CODE);
}
list($driver, $path) = $this->api->get_driver($this->args['file']);
$info = $driver->file_info($path);
// Possible 'viewer' types are defined in files_api.js:file_type_supported()
// 1 - Native browser support
// 2 - Chwala viewer exists
// 4 - Manticore (WebODF collaborative editor)
if (rcube_utils::get_boolean((string) $this->args['viewer'])) {
$this->file_viewer_info($info);
// check if file type is supported by webodf editor?
if ($this->rc->config->get('fileapi_manticore')) {
if (strtolower($info['type']) == 'application/vnd.oasis.opendocument.text') {
$info['viewer']['manticore'] = true;
}
}
if ((intval($this->args['viewer']) & 4) && $info['viewer']['manticore']) {
$this->file_manticore_handler($info);
}
}
return $info;
}
/**
* Merge file viewer data into file info
*/
protected function file_viewer_info(&$info)
{
$file = $this->args['file'];
$viewer = $this->find_viewer($info['type']);
if ($viewer) {
$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;
}
}
}
/**
* Merge manticore session data into file info
*/
protected function file_manticore_handler(&$info)
{
$manticore = new file_manticore($this->api);
$file = $this->args['file'];
$session = $this->args['session'];
- if ($uri = $manticore->viewer_uri($file, $session)) {
+ if ($uri = $manticore->session_start($file, $session)) {
$info['viewer']['href'] = $uri;
+ $info['session'] = $manticore->session_info($session);
}
}
}
diff --git a/lib/file_api.php b/lib/file_api.php
index 365aaa6..e40f377 100644
--- a/lib/file_api.php
+++ b/lib/file_api.php
@@ -1,437 +1,439 @@
<?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 extends file_api_core
{
public $session;
public $config;
public $browser;
public $output_type = file_api_core::OUTPUT_JSON;
public function __construct()
{
$rcube = rcube::get_instance();
$rcube->add_shutdown_function(array($this, 'shutdown'));
$this->config = $rcube->config;
$this->session_init();
if ($_SESSION['env']) {
$this->env = $_SESSION['env'];
}
$this->locale_init();
}
/**
* 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());
$this->session->regenerate_id(false);
if ($username = $this->authenticate()) {
$_SESSION['user'] = $username;
$_SESSION['time'] = time();
$_SESSION['env'] = $this->env;
// remember client API version
if (is_numeric($_GET['version'])) {
$_SESSION['version'] = $_GET['version'];
}
if ($this->request == 'authenticate') {
$this->output_success(array(
'token' => session_id(),
'capabilities' => $this->capabilities(),
));
}
}
else {
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->config->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->config->get('session_name');
$lifetime = $this->config->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');
// Roundcube Framework >= 1.2
if (in_array('factory', get_class_methods('rcube_session'))) {
$this->session = rcube_session::factory($this->config);
}
// Rouncube Framework < 1.2
else {
$this->session = new rcube_session($rcube->get_dbh(), $this->config);
$this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
$this->session->set_ip_check($this->config->get('ip_check'));
}
$this->session->register_gc_handler(array($rcube, 'gc'));
// 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->config->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)) {
$backend = $this->get_backend();
$result = $backend->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_core::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->env) as $name) {
if (isset($_GET[$name])) {
$this->env[$name] = $_GET[$name];
}
}
$_SESSION['env'] = $this->env;
return $this->env;
case 'upload_progress':
return $this->upload_progress();
case 'mimetypes':
return $this->supported_mimetypes();
case 'capabilities':
return $this->capabilities();
}
// handle request
if ($request && preg_match('/^[a-z0-9_-]+$/', $request)) {
- // request name aliases for backward compatibility
$aliases = array(
+ // request name aliases for backward compatibility
'lock' => 'lock_create',
'unlock' => 'lock_delete',
'folder_rename' => 'folder_move',
+ // document actions
+ 'document_delete' => 'document',
);
$request = $aliases[$request] ?: $request;
require_once __DIR__ . "/api/common.php";
include_once __DIR__ . "/api/$request.php";
$class_name = "file_api_$request";
if (class_exists($class_name, false)) {
$handler = new $class_name($this);
return $handler->handle();
}
}
throw new Exception("Unknown method", file_api_core::ERROR_INVALID);
}
/**
* 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 = max(1, 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_core::ERROR_CODE);
}
/**
* 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_core::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;
}
/**
* Returns API version supported by the client
*/
public function client_version()
{
return $_SESSION['version'];
}
/**
* 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_manticore.php b/lib/file_manticore.php
index 868c796..502caad 100644
--- a/lib/file_manticore.php
+++ b/lib/file_manticore.php
@@ -1,311 +1,348 @@
<?php
/**
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2015, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Document editing sessions handling
*/
class file_manticore
{
protected $api;
protected $rc;
protected $request;
protected $table = 'chwala_sessions';
/**
* Class constructor
*
* @param file_api Chwala API app instance
*/
public function __construct($api)
{
$this->rc = rcube::get_instance();
$this->api = $api;
}
/**
* Return viewer URI for specified file/session. This creates
* a new collaborative editing session when needed.
*
- * @param string $file File path
- * @param string $session_id Optional session ID to join to
+ * @param string $file File path
+ * @param string &$session_id Optional session ID to join to
*
* @return string Manticore URI
* @throws Exception
*/
- public function viewer_uri($file, $session_id = null)
+ public function session_start($file, &$session_id = null)
{
list($driver, $path) = $this->api->get_driver($file);
$backend = $this->api->get_backend();
$uri = $driver->path2uri($path);
if ($session_id) {
$session = $this->session_info($session_id);
if (empty($session)) {
throw new Exception("Document session ID not found.", file_api_core::ERROR_CODE);
}
// check session membership
- if ($session['data']['user'] != $_SESSION['user']) {
+ if ($session['owner'] != $_SESSION['user']) {
throw new Exception("No permission to join the editing session.", file_api_core::ERROR_CODE);
}
// @TODO: check if session exists in Manticore?
// @TOOD: joining sessions of other users
}
else {
$session_id = rcube_utils::bin2ascii(md5(time() . $uri, true));
- $data = array(
- 'user' => $_SESSION['user'],
- );
+ $data = array();
+ $owner = $_SESSION['user'];
// we'll store user credentials if the file comes from
// an external source that requires authentication
if ($backend != $driver) {
$auth = $driver->auth_info();
$auth['password'] = $this->rc->encrypt($auth['password']);
$data['auth_info'] = $auth;
}
- // Do this before starting the session in Manticore,
- // it will immediately call api/document to get the file body
- $res = $this->session_create($session_id, $uri, $data);
+ $res = $this->session_create($session_id, $uri, $owner, $data);
if (!$res) {
throw new Exception("Failed creating document editing session", file_api_core::ERROR_CODE);
}
-
- // get filename
- $path = explode(file_storage::SEPARATOR, $path);
- $filename = $path[count($path)-1];
-
- // create the session in Manticore
- $req = $this->get_request();
- $res = $req->session_create(array(
- 'id' => $session_id,
- 'title' => '', // @TODO: maybe set to a file path without extension?
- 'access' => array(
- array(
- 'identity' => $data['user'],
- 'permission' => 'write',
- ),
- ),
- ));
-
- if (!$res) {
- $this->session_delete($session_id);
- throw new Exception("Failed creating document editing session", file_api_core::ERROR_CODE);
- }
}
return $this->frame_uri($session_id);
}
/**
* Get file path (not URI) from session.
*
* @param string $id Session ID
*
* @return string File path
* @throws Exception
*/
public function session_file($id)
{
$session = $this->session_info($id);
if (empty($session)) {
throw new Exception("Document session ID not found.", file_api_core::ERROR_CODE);
}
$path = $this->uri2path($session['uri']);
if (empty($path)) {
throw new Exception("Document session ID not found.", file_api_core::ERROR_CODE);
}
+ // @TODO: check permissions to the session
+
return $path;
}
/**
* Get editing session info
*/
public function session_info($id)
{
$db = $this->rc->get_dbh();
$result = $db->query("SELECT * FROM `{$this->table}`"
. " WHERE `id` = ?", $id);
if ($row = $db->fetch_assoc($result)) {
- $row['data'] = json_decode($row['data'], true);
- return $row;
+ return $this->session_info_parse($row);
}
}
/**
* Find editing sessions for specified path
*/
public function session_find($path)
{
// create an URI for specified path
list($driver, $path) = $this->api->get_driver($path);
$uri = trim($driver->path2uri($path), '/') . '/';
// get existing sessions
- $db = $this->rc->get_dbh();
$sessions = array();
+ $filter = array('file', 'owner', 'is_owner');
+ $db = $this->rc->get_dbh();
$result = $db->query("SELECT * FROM `{$this->table}`"
. " WHERE `uri` LIKE '" . $db->escape($uri) . "%'");
if ($row = $db->fetch_assoc($result)) {
if ($path = $this->uri2path($row['uri'])) {
- $data = json_decode($row['data'], true);
+ $sessions[$row['id']] = $this->session_info_parse($row, $path, $filter);
+ }
+ }
- $session = array(
- 'file' => $path,
- 'owner' => $data['user'],
- // @TODO: invitated?, last_modified?
- );
+ return $sessions;
+ }
- if ($data['user'] == $_SESSION['user']) {
- $session['is_owner'] = true;
- }
+ /**
+ * Delete editing session (only owner can do that)
+ *
+ * @param string $id Session identifier
+ * @param bool $local Remove session only from local database
+ */
+ public function session_delete($id, $local = false)
+ {
+ $db = $this->rc->get_dbh();
+ $result = $db->query("DELETE FROM `{$this->table}`"
+ . " WHERE `id` = ? AND `owner` = ?",
+ $id, $_SESSION['user']);
- $sessions[$row['id']] = $session;
- }
+ $success = $db->affected_rows($result) > 0;
+
+ // Send document delete to Manticore
+ if ($success && !$local) {
+ $req = $this->get_request();
+ $res = $req->document_delete($id);
}
- return $sessions;
+ return $success;
}
/**
* Create editing session
*/
- protected function session_create($id, $uri, $data)
+ protected function session_create($id, $uri, $owner, $data)
{
+ // Do this before starting the session in Manticore,
+ // it will immediately call api/document to get the file body
$db = $this->rc->get_dbh();
$result = $db->query("INSERT INTO `{$this->table}`"
- . " (`id`, `uri`, `data`) VALUES (?, ?, ?)",
- $id, $uri, json_encode($data));
+ . " (`id`, `uri`, `owner`, `data`) VALUES (?, ?, ?, ?)",
+ $id, $uri, $owner, json_encode($data));
+
+ $success = $db->affected_rows($result) > 0;
+
+ // create the session in Manticore
+ if ($success) {
+ $req = $this->get_request();
+ $res = $req->document_create(array(
+ 'id' => $id,
+ 'title' => '', // @TODO: maybe set to a file path without extension?
+ 'access' => array(
+ array(
+ 'identity' => $owner,
+ 'permission' => 'write',
+ ),
+ ),
+ ));
+
+ if (!$res) {
+ $this->session_delete($id, true);
+ return false;
+ }
+ }
- return $db->affected_rows($result) > 0;
+ return $success;
}
/**
- * Delete editing session
+ * Parse session info data
*/
- protected function session_delete($id)
+ protected function session_info_parse($record, $path = null, $filter = array())
{
- $db = $this->rc->get_dbh();
- $result = $db->query("DELETE FROM `{$this->table}`"
- . " WHERE `id` = ?", $id);
+/*
+ if (is_string($data) && !empty($data)) {
+ $data = json_decode($data, true);
+ }
+*/
+ $session = array();
+ $fields = array('id', 'uri', 'owner');
+
+ foreach ($fields as $field) {
+ if (isset($record[$field])) {
+ $session[$field] = $record[$field];
+ }
+ }
+
+ if ($path) {
+ $session['file'] = $path;
+ }
+
+ // @TODO: is_invited?, last_modified?
+
+ if ($session['owner'] == $_SESSION['user']) {
+ $session['is_owner'] = true;
+ }
+
+ if (!empty($filter)) {
+ $session = array_intersect_key($session, array_flip($filter));
+ }
- return $db->affected_rows($result) > 0;
+ return $session;
}
/**
* Generate URI of Manticore editing session
*/
protected function frame_uri($id)
{
$base_url = rtrim($this->rc->config->get('fileapi_manticore'), ' /');
return $base_url . '/document/' . $id . '/' . $_SESSION['manticore_token'];
}
/**
* Get file path from the URI
*/
protected function uri2path($uri)
{
$backend = $this->api->get_backend();
try {
return $backend->uri2path($uri);
}
catch (Exception $e) {
// do nothing
}
foreach ($this->api->get_drivers(true) as $driver) {
try {
$path = $driver->uri2path($uri);
$title = $driver->title();
if ($title) {
$path = $title . file_storage::SEPARATOR . $path;
}
return $path;
}
catch (Exception $e) {
// do nothing
}
}
}
/**
* Return Manticore user/session info
*/
public function user_info()
{
$req = $this->get_request();
$res = $req->get('api/users/me');
return $res->get();
}
/**
* Initialize Manticore API request handler
*/
protected function get_request()
{
if (!$this->request) {
$uri = rcube_utils::resolve_url($this->rc->config->get('fileapi_manticore'));
$this->request = new file_manticore_api($uri);
// Use stored session token, check if it's still valid
if ($_SESSION['manticore_token']) {
$is_valid = $this->request->set_session_token($_SESSION['manticore_token'], true);
if ($is_valid) {
return $this->request;
}
}
$backend = $this->api->get_backend();
$auth = $backend->auth_info();
$_SESSION['manticore_token'] = $this->request->login($auth['username'], $auth['password']);
if (empty($_SESSION['manticore_token'])) {
throw new Exception("Unable to login to Manticore server.", file_api_core::ERROR_CODE);
}
}
return $this->request;
}
}
diff --git a/lib/file_manticore_api.php b/lib/file_manticore_api.php
index 6553c8d..991dce4 100644
--- a/lib/file_manticore_api.php
+++ b/lib/file_manticore_api.php
@@ -1,342 +1,342 @@
<?php
/**
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2011-2015, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Helper class to connect to the Manticore API
*/
class file_manticore_api
{
/**
* @var HTTP_Request2
*/
private $request;
/**
* @var string
*/
private $base_url;
/**
* @var bool
*/
private $debug = false;
const ERROR_INTERNAL = 100;
const ERROR_CONNECTION = 500;
const ACCEPT_HEADER = "application/json,text/javascript,*/*";
/**
* Class constructor.
*
* @param string $base_url Base URL of the Kolab API
*/
public function __construct($base_url)
{
require_once 'HTTP/Request2.php';
$config = rcube::get_instance()->config;
$this->debug = rcube_utils::get_boolean($config->get('fileapi_manticore_debug'));
$this->base_url = rtrim($base_url, '/') . '/';
$this->request = new HTTP_Request2();
self::configure($this->request);
}
/**
* Configure HTTP_Request2 object
*
* @param HTTP_Request2 $request Request object
*/
public static function configure($request)
{
// Configure connection options
$config = rcube::get_instance()->config;
$http_config = (array) $config->get('http_request', $config->get('kolab_http_request'));
// Deprecated config, all options are separated variables
if (empty($http_config)) {
$options = array(
'ssl_verify_peer',
'ssl_verify_host',
'ssl_cafile',
'ssl_capath',
'ssl_local_cert',
'ssl_passphrase',
'follow_redirects',
);
foreach ($options as $optname) {
if (($optvalue = $config->get($optname)) !== null
|| ($optvalue = $config->get('kolab_' . $optname)) !== null
) {
$http_config[$optname] = $optvalue;
}
}
}
if (!empty($http_config)) {
try {
$request->setConfig($http_config);
}
catch (Exception $e) {
rcube::log_error("HTTP: " . $e->getMessage());
}
}
// proxy User-Agent
$request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']);
// some HTTP server configurations require this header
$request->setHeader('accept', self::ACCEPT_HEADER);
$request->setHeader('Content-Type', 'application/json; charset=UTF-8');
}
/**
* Return API's base URL
*
* @return string Base URL
*/
public function base_url()
{
return $this->base_url;
}
/**
* Return HTTP_Request2 object
*
* @return HTTP_Request2 Request object
*/
public function request()
{
return $this->request;
}
/**
* Logs specified user into the API
*
* @param string $username User name
* @param string $password User password
*
* @return string Session token (on success)
*/
public function login($username, $password)
{
$query = array(
'email' => $username,
'password' => $password,
);
// remove current token if any
$this->request->setHeader('Authorization');
// authenticate the user
$response = $this->post('auth/local', $query);
if ($token = $response->get('token')) {
$this->set_session_token($token);
}
return $token;
}
/**
* Sets request session token.
*
* @param string $token Session token.
* @param bool $validate Enables token validatity check
*
* @return bool Token validity status
*/
public function set_session_token($token, $validate = false)
{
$this->request->setHeader('Authorization', "Bearer $token");
if ($validate) {
$result = $this->get('api/user/me');
return $result->get_error_code() == 200;
}
return true;
}
/**
* Delete document editing session
*
* @param array $id Session identifier
*
* @return bool True on success, False on failure
*/
- public function session_delete($id)
+ public function document_delete($id)
{
$res = $this->delete('api/documents/' . $id);
- return $res->get_error_code() == 200;
+ return $res->get_error_code() == 204;
}
/**
* Create document editing session
*
* @param array $params Session parameters
*
* @return bool True on success, False on failure
*/
- public function session_create($params)
+ public function document_create($params)
{
$res = $this->post('api/documents', $params);
// @TODO: 422?
return $res->get_error_code() == 201 || $res->get_error_code() == 422;
}
/**
* API's GET request.
*
* @param string $action Action name
* @param array $get Request arguments
*
* @return file_ui_api_result Response
*/
public function get($action, $get = array())
{
$url = $this->build_url($action, $get);
if ($this->debug) {
rcube::write_log('manticore', "GET: $url " . json_encode($get));
}
$this->request->setMethod(HTTP_Request2::METHOD_GET);
$this->request->setBody('');
return $this->get_response($url);
}
/**
* API's POST request.
*
* @param string $action Action name
* @param array $post POST arguments
*
* @return kolab_client_api_result Response
*/
public function post($action, $post = array())
{
$url = $this->build_url($action);
if ($this->debug) {
rcube::write_log('manticore', "POST: $url " . json_encode($post));
}
$this->request->setMethod(HTTP_Request2::METHOD_POST);
$this->request->setBody(json_encode($post));
return $this->get_response($url);
}
/**
* API's DELETE request.
*
* @param string $action Action name
* @param array $get Request arguments
*
* @return file_ui_api_result Response
*/
public function delete($action, $get = array())
{
$url = $this->build_url($action, $get);
if ($this->debug) {
rcube::write_log('manticore', "DELETE: $url " . json_encode($get));
}
$this->request->setMethod(HTTP_Request2::METHOD_DELETE);
$this->request->setBody('');
return $this->get_response($url);
}
/**
* @param string $action Action GET parameter
* @param array $args GET parameters (hash array: name => value)
*
* @return Net_URL2 URL object
*/
private function build_url($action, $args = array())
{
$url = new Net_URL2($this->base_url . $action);
$url->setQueryVariables((array) $args);
return $url;
}
/**
* HTTP Response handler.
*
* @param Net_URL2 $url URL object
*
* @return kolab_client_api_result Response object
*/
private function get_response($url)
{
try {
$this->request->setUrl($url);
$response = $this->request->send();
}
catch (Exception $e) {
return new file_ui_api_result(null,
self::ERROR_CONNECTION, $e->getMessage());
}
try {
$body = $response->getBody();
}
catch (Exception $e) {
return new file_ui_api_result(null,
self::ERROR_INTERNAL, $e->getMessage());
}
$code = $response->getStatus();
if ($this->debug) {
rcube::write_log('manticore', "Response [$code]: $body");
}
if ($code < 300) {
$result = $body ? json_decode($body, true) : array();
}
else {
if ($code != 401) {
rcube::raise_error("Error $code on $url", true, false);
}
$error = $body;
}
return new file_ui_api_result($result, $code, $error);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Feb 5, 7:12 PM (7 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
427990
Default Alt Text
(48 KB)
Attached To
Mode
R26 chwala
Attached
Detach File
Event Timeline
Log In to Comment