Page MenuHomePhorge

No OneTemporary

Size
30 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/file_api.php b/lib/file_api.php
index 3764d23..70f0dd9 100644
--- a/lib/file_api.php
+++ b/lib/file_api.php
@@ -1,462 +1,468 @@
<?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 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->request == 'authenticate')) {
$this->session->destroy(session_id());
$this->session->regenerate_id(false);
if ($username = $this->authenticate()) {
+ // Init locale after the session started
+ $this->locale_init();
+ $this->env['language'] = $this->language;
+
$_SESSION['user'] = $username;
$_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);
}
}
+ else {
+ // Init locale after the session started
+ $this->locale_init();
+ }
// 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($new_session = false)
{
if (!$new_session) {
$sess_id = rcube_utils::request_header('X-Session-Token') ?: $_REQUEST['token'];
}
if (empty($sess_id)) {
$this->session->start();
return false;
}
session_id($sess_id);
$this->session->start();
if (empty($_SESSION['user'])) {
return false;
}
// Document-only session
if (($doc_id = $_SESSION['document_session'])
&& (strpos($this->request, 'document') !== 0 || $doc_id != $_GET['id'])
) {
throw new Exception("Access denied", 403);
}
+ if ($_SESSION['env']) {
+ $this->env = $_SESSION['env'];
+ }
+
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)) {
$aliases = array(
// request name aliases for backward compatibility
'lock' => 'lock_create',
'unlock' => 'lock_delete',
'folder_rename' => 'folder_move',
);
// Redirect all document_* actions into 'document' action
if (preg_match('/^(sessions|invitations|document_[a-z]+)$/', $request)) {
$request = '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'];
header("X-Chwala-Request-ID: " . $_REQUEST['req_id']);
}
if (empty($response['code'])) {
$response['code'] = file_api_core::ERROR_CODE;
}
header("X-Chwala-Error: " . $response['code']);
// When binary response is expected return real
// HTTP error instaead of JSON response with code 200
if ($this->is_binary_request()) {
header(sprintf("HTTP/1.0 %d %s", $response['code'], $response ?: "Server error"));
exit;
}
$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;
}
/**
* Find out if current request expects binary output
*/
protected function is_binary_request()
{
return preg_match('/^(file_get|document)$/', $this->request)
&& $_SERVER['REQUEST_METHOD'] == 'GET';
}
/**
* 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_locale.php b/lib/file_locale.php
index 7db48a0..41b7f65 100644
--- a/lib/file_locale.php
+++ b/lib/file_locale.php
@@ -1,122 +1,127 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2011-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> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_locale
{
protected static $translation = array();
-
+ protected $language;
/**
* Localization initialization.
*/
protected function locale_init()
{
$language = $this->get_language();
$LANG = array();
if (!$language) {
$language = 'en_US';
}
+ $this->language = $language;
+
@include __DIR__ . "/locale/en_US.php";
if ($language != 'en_US') {
@include __DIR__ . "/locale/$language.php";
}
setlocale(LC_ALL, $language . '.utf8', $language . 'UTF-8', 'en_US.utf8', 'en_US.UTF-8');
self::$translation = $LANG;
}
/**
* Returns system language (locale) setting.
*
* @return string Language code
*/
protected function get_language()
{
$aliases = array(
'de' => 'de_DE',
'en' => 'en_US',
'pl' => 'pl_PL',
);
// UI language
$langs = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
$langs = explode(',', $langs);
if (!empty($_SESSION['user']) && !empty($_SESSION['user']['language'])) {
array_unshift($langs, $_SESSION['user']['language']);
}
+ if (!empty($_SESSION['env']) && !empty($_SESSION['env']['language'])) {
+ array_unshift($langs, $_SESSION['env']['language']);
+ }
- while ($lang = array_shift($langs)) {
+ foreach (array_unique($langs) as $lang) {
$lang = explode(';', $lang);
$lang = $lang[0];
$lang = str_replace('-', '_', $lang);
if (file_exists(__DIR__ . "/locale/$lang.php")) {
return $lang;
}
if (isset($aliases[$lang]) && ($alias = $aliases[$lang])
&& file_exists(__DIR__ . "/locale/$alias.php")
) {
return $alias;
}
}
return null;
}
/**
* Returns translation of defined label/message.
*
* @return string Translated string.
*/
public static function translate()
{
$args = func_get_args();
if (is_array($args[0])) {
$args = $args[0];
}
$label = $args[0];
if (isset(self::$translation[$label])) {
$content = trim(self::$translation[$label]);
}
else {
$content = $label;
}
for ($i = 1, $len = count($args); $i < $len; $i++) {
$content = str_replace('$'.$i, $args[$i], $content);
}
return $content;
}
}
diff --git a/lib/file_wopi.php b/lib/file_wopi.php
index 7382aed..af74b1b 100644
--- a/lib/file_wopi.php
+++ b/lib/file_wopi.php
@@ -1,307 +1,313 @@
<?php
/**
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2016, 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 (WOPI)
*/
class file_wopi extends file_document
{
protected $cache;
// Mimetypes supported by CODE, but not advertised by all possible names
protected $aliases = array(
'application/vnd.corel-draw' => 'image/x-coreldraw',
);
/**
* Return viewer URI for specified file/session. This creates
* a new collaborative editing session when needed.
*
* @param string $file File path
* @param array &$file_info File metadata (e.g. type)
* @param string &$session_id Optional session ID to join to
* @param string $readonly Create readonly (one-time) session
*
* @return string WOPI URI for specified document
* @throws Exception
*/
public function session_start($file, &$file_info, &$session_id = null, $readonly = false)
{
parent::session_start($file, $file_info, $session_id, $readonly);
if ($session_id) {
// Create Chwala session for use as WOPI access_token
// This session will have access to this one document session only
- $keys = array('language', 'user_id', 'user', 'username', 'password',
+ $keys = array('env', 'user_id', 'user', 'username', 'password',
'storage_host', 'storage_port', 'storage_ssl');
$data = array_intersect_key($_SESSION, array_flip($keys));
$data['document_session'] = $session_id;
$this->token = $this->api->session->create($data);
}
return $this->frame_uri($session_id, $file_info['type']);
}
/**
* Generate URI of WOPI editing session (WOPIsrc)
*/
protected function frame_uri($id, $mimetype)
{
$capabilities = $this->capabilities();
if (empty($capabilities) || empty($mimetype)) {
return;
}
$metadata = $capabilities[strtolower($mimetype)];
if (empty($metadata)) {
return;
}
$office_url = rtrim($metadata['urlsrc'], ' /?'); // collabora
$service_url = rtrim($this->rc->config->get('fileapi_wopi_service'), ' /'); // kolab-wopi
$service_url .= '/wopi/files/' . $id;
// @TODO: Parsing and replacing placeholder values
// https://wopi.readthedocs.io/en/latest/discovery.html#action-urls
- return $office_url . '?WOPISrc=' . urlencode($service_url);
+ $args = array('WOPISrc' => $service_url);
+
+ // We could also set: title, closebutton, revisionhistory
+ // @TODO: do it in editor_post_params() when supported by the editor
+ if ($lang = $this->api->env['language']) {
+ $args['lang'] = str_replace('_', '-', $lang);
+ }
+
+ return $office_url . '?' . http_build_query($args, '', '&', PHP_QUERY_RFC3986);
}
/**
* Retern extra viewer parameters to post to the viewer iframe
*
* @param array $info File info
*
* @return array POST parameters
*/
public function editor_post_params($info)
{
// Access token TTL (number of milliseconds since January 1, 1970 UTC)
if ($ttl = $this->rc->config->get('session_lifetime', 0) * 60) {
$now = new DateTime('now', new DateTimeZone('UTC'));
$ttl = ($ttl + $now->format('U')) . '000';
}
$params = array(
'access_token' => $this->token,
'access_token_ttl' => $ttl ?: 0,
);
- // @TODO: we should/could also add:
- // lang, title, timestamp, closebutton, revisionhistory
return $params;
}
/**
* List supported mimetypes
*
* @param bool $editable Return only editable mimetypes
*
* @return array List of supported mimetypes
*/
public function supported_filetypes($editable = false)
{
$caps = $this->capabilities();
if ($editable) {
$editable = array();
foreach ($caps as $mimetype => $c) {
if ($c['name'] == 'edit') {
$editable[] = $mimetype;
}
}
return $editable;
}
return array_keys($caps);
}
/**
* Uses WOPI discovery to get Office capabilities
* https://wopi.readthedocs.io/en/latest/discovery.html
*/
protected function capabilities()
{
$cache_key = 'wopi.capabilities';
if ($result = $this->get_from_cache($cache_key)) {
return $this->apply_aliases($result);
}
$office_url = rtrim($this->rc->config->get('fileapi_wopi_office'), ' /');
$office_url .= '/hosting/discovery';
try {
$request = $this->http_request();
$request->setMethod(HTTP_Request2::METHOD_GET);
$request->setBody('');
$request->setUrl($office_url);
$response = $request->send();
$body = $response->getBody();
$code = $response->getStatus();
if (empty($body) || $code != 200) {
throw new Exception("Unexpected WOPI discovery response");
}
}
catch (Exception $e) {
rcube::raise_error($e, true, true);
}
// parse XML output
// <wopi-discovery>
// <net-zone name="external-http">
// <app name="application/vnd.lotus-wordpro">
// <action ext="lwp" name="edit" urlsrc="https://office.example.org/loleaflet/1.8.3/loleaflet.html?"/>
// </app>
// ...
$node = new DOMDocument('1.0', 'UTF-8');
$node->loadXML($body);
$result = array();
foreach ($node->getElementsByTagName('app') as $app) {
if ($mimetype = $app->getAttribute('name')) {
if ($action = $app->getElementsByTagName('action')->item(0)) {
foreach ($action->attributes as $attr) {
$result[$mimetype][$attr->name] = $attr->value;
}
}
}
}
if (empty($result)) {
rcube::raise_error("Failed to parse WOPI discovery response: $body", true, true);
}
$this->save_in_cache($cache_key, $result);
return $this->apply_aliases($result);
}
/**
* Initializes HTTP request object
*/
protected function http_request()
{
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
// Configure connection options
$config = $this->rc->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', "application/json,text/javascript,*/*");
return $request;
}
/**
* Get cached data
*/
protected function get_from_cache($key)
{
if ($cache = $this->get_cache) {
return $cache->get($key);
}
}
/**
* Store data in cache
*/
protected function save_in_cache($key, $value)
{
if ($cache = $this->get_cache) {
$cache->set($key, $value);
}
}
/**
* Getter for the shared cache engine object
*/
protected function get_cache()
{
if ($this->cache === null) {
$cache = $this->rc->get_cache_shared('chwala');
$this->cache = $cache ?: false;
}
return $this->cache;
}
/**
* Support more mimetypes in CODE capabilities
*/
protected function apply_aliases($caps)
{
foreach ($this->aliases as $type => $alias) {
if (isset($caps[$type]) && !isset($caps[$alias])) {
$caps[$alias] = $caps[$type];
}
}
return $caps;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Feb 3, 5:13 PM (15 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
427390
Default Alt Text
(30 KB)

Event Timeline