Thomas Bruederli
2014-06-04 3412e50b54e3daac8745234e21ab6e72be0ed165
program/lib/Roundcube/rcube.php
@@ -3,8 +3,8 @@
/*
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
 | Copyright (C) 2008-2014, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2014, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -94,25 +94,32 @@
     */
    public $plugins;
    /**
     * Instance of rcube_user class.
     *
     * @var rcube_user
     */
    public $user;
    /* private/protected vars */
    protected $texts;
    protected $caches = array();
    protected $shutdown_functions = array();
    protected $expunge_cache = false;
    /**
     * This implements the 'singleton' design pattern
     *
     * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
     * @param string Environment name to run (e.g. live, dev, test)
     *
     * @return rcube The one and only instance
     */
    static function get_instance($mode = 0)
    static function get_instance($mode = 0, $env = '')
    {
        if (!self::$instance) {
            self::$instance = new rcube();
            self::$instance = new rcube($env);
            self::$instance->init($mode);
        }
@@ -123,10 +130,10 @@
    /**
     * Private constructor
     */
    protected function __construct()
    protected function __construct($env = '')
    {
        // load configuration
        $this->config  = new rcube_config;
        $this->config  = new rcube_config($env);
        $this->plugins = new rcube_dummy_plugin_api;
        register_shutdown_function(array($this, 'shutdown'));
@@ -348,29 +355,6 @@
        // for backward compat. (deprecated, will be removed)
        $this->imap = $this->storage;
        // enable caching of mail data
        $storage_cache  = $this->config->get("{$driver}_cache");
        $messages_cache = $this->config->get('messages_cache');
        // for backward compatybility
        if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
            $storage_cache  = 'db';
            $messages_cache = true;
        }
        if ($storage_cache) {
            $this->storage->set_caching($storage_cache);
        }
        if ($messages_cache) {
            $this->storage->set_messages_caching(true);
        }
        // set pagesize from config
        $pagesize = $this->config->get('mail_pagesize');
        if (!$pagesize) {
            $pagesize = $this->config->get('pagesize', 50);
        }
        $this->storage->set_pagesize($pagesize);
        // set class options
        $options = array(
            'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
@@ -378,6 +362,7 @@
            'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
            'debug'       => (bool) $this->config->get("{$driver}_debug"),
            'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
            'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
            'timeout'     => (int) $this->config->get("{$driver}_timeout"),
            'skip_deleted' => (bool) $this->config->get('skip_deleted'),
            'driver'      => $driver,
@@ -404,22 +389,65 @@
    /**
     * Set storage parameters.
     * This must be done AFTER connecting to the server!
     */
    protected function set_storage_prop()
    {
        $storage = $this->get_storage();
        // set pagesize from config
        $pagesize = $this->config->get('mail_pagesize');
        if (!$pagesize) {
            $pagesize = $this->config->get('pagesize', 50);
        }
        $storage->set_pagesize($pagesize);
        $storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET));
        if ($default_folders = $this->config->get('default_folders')) {
            $storage->set_default_folders($default_folders);
        // enable caching of mail data
        $driver         = $this->config->get('storage_driver', 'imap');
        $storage_cache  = $this->config->get("{$driver}_cache");
        $messages_cache = $this->config->get('messages_cache');
        // for backward compatybility
        if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
            $storage_cache  = 'db';
            $messages_cache = true;
        }
        if (isset($_SESSION['mbox'])) {
            $storage->set_folder($_SESSION['mbox']);
        if ($storage_cache) {
            $storage->set_caching($storage_cache);
        }
        if (isset($_SESSION['page'])) {
            $storage->set_page($_SESSION['page']);
        if ($messages_cache) {
            $storage->set_messages_caching(true);
        }
    }
    /**
     * Set special folders type association.
     * This must be done AFTER connecting to the server!
     */
    protected function set_special_folders()
    {
        $storage = $this->get_storage();
        $folders = $storage->get_special_folders(true);
        $prefs   = array();
        // check SPECIAL-USE flags on IMAP folders
        foreach ($folders as $type => $folder) {
            $idx = $type . '_mbox';
            if ($folder !== $this->config->get($idx)) {
                $prefs[$idx] = $folder;
            }
        }
        // Some special folders differ, update user preferences
        if (!empty($prefs) && $this->user) {
            $this->user->save_prefs($prefs);
        }
        // create default folders (on login)
        if ($this->config->get('create_default_folders')) {
            $storage->create_default_folders();
        }
    }
@@ -462,11 +490,13 @@
        // use database for storing session data
        $this->session = new rcube_session($this->get_dbh(), $this->config);
        $this->session->register_gc_handler(array($this, 'temp_gc'));
        $this->session->register_gc_handler(array($this, 'cache_gc'));
        $this->session->register_gc_handler(array($this, 'gc'));
        $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
        $this->session->set_ip_check($this->config->get('ip_check'));
        if ($this->config->get('session_auth_name')) {
            $this->session->set_cookiename($this->config->get('session_auth_name'));
        }
        // start PHP session (if not in CLI mode)
        if ($_SERVER['REMOTE_ADDR']) {
@@ -476,21 +506,41 @@
    /**
     * Garbage collector - cache/temp cleaner
     */
    public function gc()
    {
        rcube_cache::gc();
        rcube_cache_shared::gc();
        $this->get_storage()->cache_gc();
        $this->gc_temp();
    }
    /**
     * Garbage collector function for temp files.
     * Remove temp files older than two days
     */
    public function temp_gc()
    public function gc_temp()
    {
        $tmp = unslashify($this->config->get('temp_dir'));
        $expire = time() - 172800;  // expire in 48 hours
        // expire in 48 hours by default
        $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h');
        $temp_dir_ttl = get_offset_sec($temp_dir_ttl);
        if ($temp_dir_ttl < 6*3600)
            $temp_dir_ttl = 6*3600;   // 6 hours sensible lower bound.
        $expire = time() - $temp_dir_ttl;
        if ($tmp && ($dir = opendir($tmp))) {
            while (($fname = readdir($dir)) !== false) {
                if ($fname{0} == '.') {
                if ($fname[0] == '.') {
                    continue;
                }
                if (filemtime($tmp.'/'.$fname) < $expire) {
                if (@filemtime($tmp.'/'.$fname) < $expire) {
                    @unlink($tmp.'/'.$fname);
                }
            }
@@ -501,14 +551,21 @@
    /**
     * Garbage collector for cache entries.
     * Set flag to expunge caches on shutdown
     * Runs garbage collector with probability based on
     * session settings. This is intended for environments
     * without a session.
     */
    public function cache_gc()
    public function gc_run()
    {
        // because this gc function is called before storage is initialized,
        // we just set a flag to expunge storage cache on shutdown.
        $this->expunge_cache = true;
        $probability = (int) ini_get('session.gc_probability');
        $divisor     = (int) ini_get('session.gc_divisor');
        if ($divisor > 0 && $probability > 0) {
            $random = mt_rand(1, $divisor);
            if ($random <= $probability) {
                $this->gc();
            }
        }
    }
@@ -612,10 +669,11 @@
    /**
     * Load a localization package
     *
     * @param string Language ID
     * @param array  Additional text labels/messages
     * @param string $lang  Language ID
     * @param array  $add   Additional text labels/messages
     * @param array  $merge Additional text labels/messages to merge
     */
    public function load_language($lang = null, $add = array())
    public function load_language($lang = null, $add = array(), $merge = array())
    {
        $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
@@ -655,6 +713,11 @@
        if (is_array($add) && !empty($add)) {
            $this->texts += $add;
        }
        // merge additional texts (from plugin)
        if (is_array($merge) && !empty($merge)) {
            $this->texts = array_merge($this->texts, $merge);
        }
    }
@@ -672,7 +735,11 @@
        // user HTTP_ACCEPT_LANGUAGE if no language is specified
        if (empty($lang) || $lang == 'auto') {
            $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
            $lang         = str_replace('-', '_', $accept_langs[0]);
            $lang         = $accept_langs[0];
            if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) {
                $lang = $m[1] . '_' . strtoupper($m[2]);
            }
        }
        if (empty($rcube_languages)) {
@@ -892,23 +959,25 @@
            call_user_func($function);
        }
        // write session data as soon as possible and before
        // closing database connection, don't do this before
        // registered shutdown functions, they may need the session
        // Note: this will run registered gc handlers (ie. cache gc)
        if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
            $this->session->write_close();
        }
        if (is_object($this->smtp)) {
            $this->smtp->disconnect();
        }
        foreach ($this->caches as $cache) {
            if (is_object($cache)) {
                if ($this->expunge_cache) {
                    $cache->expunge();
                }
                $cache->close();
            }
        }
        if (is_object($this->storage)) {
            if ($this->expunge_cache) {
                $this->storage->expunge_cache();
            }
            $this->storage->close();
        }
    }
@@ -1072,7 +1141,20 @@
        // log_driver == 'file' is assumed here
        $line = sprintf("[%s]: %s\n", $date, $line);
        $log_dir  = self::$instance ? self::$instance->config->get('log_dir') : null;
        $log_dir = null;
        // per-user logging is activated
        if (self::$instance && self::$instance->config->get('per_user_logging', false) && self::$instance->get_user_id()) {
            $log_dir = self::$instance->get_user_log_dir();
            if (empty($log_dir))
                return false;
        }
        else if (!empty($log['dir'])) {
            $log_dir = $log['dir'];
        }
        else if (self::$instance) {
            $log_dir = self::$instance->config->get('log_dir');
        }
        if (empty($log_dir)) {
            $log_dir = RCUBE_INSTALL_PATH . 'logs';
@@ -1100,8 +1182,8 @@
     *      - code:    Error code (required)
     *      - type:    Error type [php|db|imap|javascript] (required)
     *      - message: Error message
     *      - file:    File where error occured
     *      - line:    Line where error occured
     *      - file:    File where error occurred
     *      - line:    Line where error occurred
     * @param boolean True to log the error
     * @param boolean Terminate script execution
     */
@@ -1110,7 +1192,6 @@
        // handle PHP exceptions
        if (is_object($arg) && is_a($arg, 'Exception')) {
            $arg = array(
                'type' => 'php',
                'code' => $arg->getCode(),
                'line' => $arg->getLine(),
                'file' => $arg->getFile(),
@@ -1118,7 +1199,7 @@
            );
        }
        else if (is_string($arg)) {
            $arg = array('message' => $arg, 'type' => 'php');
            $arg = array('message' => $arg);
        }
        if (empty($arg['code'])) {
@@ -1126,15 +1207,15 @@
        }
        // installer
        if (class_exists('rcube_install', false)) {
            $rci = rcube_install::get_instance();
        if (class_exists('rcmail_install', false)) {
            $rci = rcmail_install::get_instance();
            $rci->raise_error($arg);
            return;
        }
        $cli = php_sapi_name() == 'cli';
        if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) {
        if (($log || $terminate) && !$cli && $arg['message']) {
            $arg['fatal'] = $terminate;
            self::log_bug($arg);
        }
@@ -1162,7 +1243,7 @@
     */
    public static function log_bug($arg_arr)
    {
        $program = strtoupper($arg_arr['type']);
        $program = strtoupper(!empty($arg_arr['type']) ? $arg_arr['type'] : 'php');
        $level   = self::get_instance()->config->get('debug_level');
        // disable errors for ajax requests, write to log instead (#1487831)
@@ -1248,6 +1329,20 @@
        self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
    }
    /**
     * Setter for system user object
     *
     * @param rcube_user Current user instance
     */
    public function set_user($user)
    {
        if (is_object($user)) {
            $this->user = $user;
            // overwrite config with user preferences
            $this->config->set_user_prefs((array)$this->user->get_prefs());
        }
    }
    /**
     * Getter for logged user ID.
@@ -1311,6 +1406,17 @@
        }
    }
    /**
     * Get the per-user log directory
     */
    protected function get_user_log_dir()
    {
        $log_dir = $this->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs');
        $user_name = $this->get_user_name();
        $user_log_dir = $log_dir . '/' . $user_name;
        return !empty($user_name) && is_writable($user_log_dir) ? $user_log_dir : false;
    }
    /**
     * Getter for logged user language code.
@@ -1371,6 +1477,10 @@
            'mailto'  => $mailto,
            'options' => $options,
        ));
        if ($plugin['abort']) {
            return isset($plugin['result']) ? $plugin['result'] : false;
        }
        $from    = $plugin['from'];
        $mailto  = $plugin['mailto'];
@@ -1468,7 +1578,7 @@
                    $subject    = str_replace("\r\n", $delim, $subject);
                }
                if (ini_get('safe_mode'))
                if (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
                    $sent = mail($to, $subject, $msg_body, $header_str);
                else
                    $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
@@ -1497,6 +1607,10 @@
                    !empty($response) ? join('; ', $response) : ''));
            }
        }
        else {
            // allow plugins to catch sending errors with the same parameters as in 'message_before_send'
            $this->plugins->exec_hook('message_send_error', $plugin + array('error' => $error));
        }
        if (is_resource($msg_body)) {
            fclose($msg_body);