diff --git a/lib/file_ui.php b/lib/file_ui.php index e8f5032..f440f8f 100644 --- a/lib/file_ui.php +++ b/lib/file_ui.php @@ -1,573 +1,580 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ class file_ui extends file_locale { /** * @var kolab_client_output */ protected $output; /** * @var kolab_client_api */ public $api; /** * @var Conf */ protected $config; protected $ajax_only = false; protected $page_title = 'Kolab File API'; protected $menu = array(); protected $cache = array(); protected $devel_mode = false; protected $object_types = array(); const API_VERSION = 2; /** * Class constructor. * * @param file_ui_output $output Optional output object */ public function __construct($output = null) { $rcube = rcube::get_instance(); - $rcube->add_shutdown_function(array($this, 'shutdown')); + register_shutdown_function(array($this, 'shutdown')); $this->config_init(); $this->devel_mode = $this->config->get('devel_mode', false); $this->output_init($output); $this->api_init(); ini_set('session.use_cookies', 'On'); session_start(); // Initialize locales $this->locale_init(); $this->auth(); } /** * Configuration initialization. */ private function config_init() { $this->config = rcube::get_instance()->config; } /** * Output initialization. */ private function output_init($output = null) { if ($output) { $this->output = $output; return; } $skin = $this->config->get('file_api_skin', 'default'); $this->output = new file_ui_output($skin); // Assign self to template variable $this->output->assign('engine', $this); } /** * API initialization */ private function api_init() { $url = $this->config->get('file_api_url', ''); if (!$url) { $schema = rcube_utils::https_check() ? 'https' : 'http'; $port = $schema == 'http' ? 80 : 443; $url = $schema . '://' . $_SERVER['SERVER_NAME']; $url .= $_SERVER['SERVER_PORT'] != $port ? ':' . $_SERVER['SERVER_PORT'] : ''; $url .= preg_replace('/\/?\?.*$/', '', $_SERVER['REQUEST_URI']); $url .= '/api/'; } $this->api = new file_ui_api($url); } /** * Initializes User Interface */ protected function ui_init() { // assign token $this->output->set_env('token', $_SESSION['user']['token']); // assign capabilities $this->output->set_env('capabilities', $_SESSION['caps']); // add watermark content $this->output->set_env('watermark', $this->output->get_template('watermark')); // $this->watermark('taskcontent'); // assign default set of translations $this->output->add_translation('loading', 'servererror'); // $this->output->assign('tasks', $this->menu); // $this->output->assign('main_menu', $this->menu()); $this->output->assign('user', $_SESSION['user']); if ($_SESSION['caps']['MAX_UPLOAD']) { $this->output->assign('max_upload', $this->show_bytes($_SESSION['caps']['MAX_UPLOAD'])); } } /** * User authentication (and authorization). */ private function auth() { if (isset($_POST['login'])) { $login = $this->get_input('login', 'POST'); if ($login['username']) { $result = $this->api->login($login['username'], $login['password'], array('version' => self::API_VERSION)); if ($token = $result->get('token')) { $user = array( 'token' => $token, 'username' => $login['username'], ); $this->api->set_session_token($user['token']); /* // Find user settings // Don't call API user.info for non-existing users (#1025) if (preg_match('/^cn=([a-z ]+)/i', $login['username'], $m)) { $user['fullname'] = ucwords($m[1]); } else { $res = $this->api->get('user.info', array('user' => $user['id'])); $res = $res->get(); if (is_array($res) && !empty($res)) { $user['language'] = $res['preferredlanguage']; $user['fullname'] = $res['cn']; } } */ // Save capabilities $_SESSION['caps'] = $result->get('capabilities'); // Save user data $_SESSION['user'] = $user; if (($language = $this->get_language()) && $language != 'en_US') { $_SESSION['user']['language'] = $language; // $session_config['language'] = $language; } /* // Configure API session if (!empty($session_config)) { $this->api->post('system.configure', null, $session_config); } */ header('Location: ?'); die; } else { $code = $result->get_error_code(); $str = $result->get_error_str(); $label = 'loginerror'; if ($code == file_ui_api::ERROR_INTERNAL || $code == file_ui_api::ERROR_CONNECTION ) { $label = 'internalerror'; $this->raise_error(500, 'Login failed. ' . $str); } $this->output->command('display_message', $label, 'error'); } } } else if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token'])) { // Validate session $timeout = $this->config->get('session_timeout', 3600); if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) { $this->action_logout(true); } // update session time $_SESSION['time'] = time(); // Set API session key $this->api->set_session_token($_SESSION['user']['token']); } } /** * Main execution. */ public function run() { // Session check if (empty($_SESSION['user']) || empty($_SESSION['user']['token'])) { $this->action_logout(); } // Run security checks $this->input_checks(); $this->action = $this->get_input('action', 'GET'); if ($this->action) { $method = 'action_' . $this->action; if (method_exists($this, $method)) { $this->$method(); } } else if (method_exists($this, 'action_default')) { $this->action_default(); } } /** * Security checks and input validation. */ public function input_checks() { $ajax = $this->output->is_ajax(); // Check AJAX-only tasks if ($this->ajax_only && !$ajax) { $this->raise_error(500, 'Invalid request type!', null, true); } // CSRF prevention $token = $ajax ? rcube_utils::request_header('X-Session-Token') : $this->get_input('token'); $task = $this->get_task(); if ($task != 'main' && $token != $_SESSION['user']['token']) { $this->raise_error(403, 'Invalid request data!', null, true); } } /** * Logout action. */ private function action_logout($sess_expired = false, $stop_sess = true) { if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token']) && $stop_sess) { $this->api->logout(); } $_SESSION = array(); if ($this->output->is_ajax()) { if ($sess_expired) { $args = array('error' => 'session.expired'); } $this->output->command('main_logout', $args); if ($sess_expired) { $this->output->send(); exit; } } else { $this->output->add_translation('loginerror', 'internalerror', 'session.expired'); } if ($sess_expired) { $error = 'session.expired'; } else { $error = $this->get_input('error', 'GET'); } if ($error) { $this->output->command('display_message', $error, 'error', 60000); } $this->output->send('login'); exit; } /** * Error action (with error logging). * * @param int $code Error code * @param string $msg Error message * @param array $args Optional arguments (type, file, line) * @param bool $output Enable to send output and finish */ public function raise_error($code, $msg, $args = array(), $output = false) { $log_line = sprintf("%s Error: %s (%s)", isset($args['type']) ? $args['type'] : 'PHP', $msg . (isset($args['file']) ? sprintf(' in %s on line %d', $args['file'], $args['line']) : ''), $_SERVER['REQUEST_METHOD']); rcube::write_log('errors', $log_line); if (!$output) { return; } if ($this->output->is_ajax()) { header("HTTP/1.0 $code $msg"); die; } $this->output->assign('error_code', $code); $this->output->assign('error_message', $msg); $this->output->send('error'); exit; } /** * Script shutdown handler */ public function shutdown() { // write performance stats to logs/console - if ($this->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 = 'ui:' . $this->get_task() . ($this->action ? '/' . $this->action : ''); - $log .= ($mem ? sprintf(' [%.1f MB]', $mem/1024/1024) : ''); + if ($this->config->get('devel_mode') || $this->config->get('performance_stats')) { + // we have to disable per_user_logging to make sure stats end up in the main console log + $this->config->set('per_user_logging', false); + + // make sure logged numbers use unified format + setlocale(LC_NUMERIC, 'en_US.utf8', 'en_US.UTF-8', 'en_US', 'C'); + + if (function_exists('memory_get_usage')) { + $mem = round(memory_get_usage() / 1024 /1024, 1); + } + if (function_exists('memory_get_peak_usage')) { + $mem .= '/'. round(memory_get_peak_usage() / 1024 / 1024, 1); + } + + $log = 'ui:' . $this->get_task() . ($this->action ? '/' . $this->action : '') . " [$mem]"; if (defined('FILE_API_START')) { rcube::print_timer(FILE_API_START, $log); } else { rcube::console($log); } } } /** * Output sending. */ public function send() { $task = $this->get_task(); if ($this->page_title) { $this->output->assign('pagetitle', $this->page_title); } $this->output->set_env('task', $task); $this->output->send($this->task_template ? $this->task_template : $task); exit; } /** * Returns name of the current task. * * @return string Task name */ public function get_task() { $class_name = get_class($this); if (preg_match('/^file_ui_client_([a-z]+)$/', $class_name, $m)) { return $m[1]; } } /** * Returns input parameter value. * * @param string $name Parameter name * @param string $type Parameter type (GET|POST|NULL) * @param bool $allow_html Disables stripping of insecure content (HTML tags) * * @see rcube_utils::get_input_value * @return mixed Input value. */ public static function get_input($name, $type = null, $allow_html = false) { if ($type == 'GET') { $type = rcube_utils::INPUT_GET; } else if ($type == 'POST') { $type = rcube_utils::INPUT_POST; } else { $type = rcube_utils::INPUT_GPC; } $result = rcube_utils::get_input_value($name, $type, $allow_html); return $result; } /** * Returns task menu output. * * @return string HTML output */ protected function menu() { } /** * Adds watermark page definition into main page. */ protected function watermark($name) { $this->output->command('set_watermark', $name); } /** * API GET request wrapper */ protected function api_get($action, $get = array()) { return $this->api_call('get', $action, $get); } /** * API POST request wrapper */ protected function api_post($action, $get = array(), $post = array()) { return $this->api_call('post', $action, $get, $post); } /** * API request wrapper with error handling */ protected function api_call($type, $action, $get = array(), $post = array()) { if ($type == 'post') { $result = $this->api->post($action, $get, $post); } else { $result = $this->api->get($action, $get); } // error handling if ($code = $result->get_error_code()) { // Invalid session, do logout if ($code == 403) { $this->action_logout(true, false); } // Log communication errors, other should be logged on API side if ($code < 400) { $this->raise_error($code, 'API Error: ' . $result->get_error_str()); } } return $result; } /** * Returns execution time in seconds * * @param string Execution time */ public function gentime() { return sprintf('%.4f', microtime(true) - FILE_API_START); } /** * Returns HTML output of login form * * @param string HTML output */ public function login_form() { $post = $this->get_input('login', 'POST'); $user_input = new html_inputfield(array( 'type' => 'text', 'id' => 'login_name', 'name' => 'login[username]', 'autofocus' => true, )); $pass_input = new html_inputfield(array( 'type' => 'password', 'id' => 'login_pass', 'name' => 'login[password]', )); $button = new html_inputfield(array( 'type' => 'submit', 'id' => 'login_submit', 'value' => $this->translate('login.login'), )); $username = html::label(array('for' => 'login_name'), $this->translate('login.username')) . $user_input->show($post['username']); $password = html::label(array('for' => 'login_pass'), $this->translate('login.password')) . $pass_input->show(''); $form = html::tag('form', array( 'id' => 'login_form', 'name' => 'login', 'method' => 'post', 'action' => '?'), html::span(null, $username) . html::span(null, $password) . $button->show()); return $form; } /** * Create a human readable string for a number of bytes * * @param int Number of bytes * * @return string Byte string */ protected function show_bytes($bytes) { if ($bytes >= 1073741824) { $gb = $bytes/1073741824; $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . $this->translate('size.GB'); } else if ($bytes >= 1048576) { $mb = $bytes/1048576; $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . $this->translate('size.MB'); } else if ($bytes >= 1024) { $str = sprintf("%d ", round($bytes/1024)) . $this->translate('size.KB'); } else { $str = sprintf("%d ", $bytes) . $this->translate('size.B'); } return $str; } }