alecpl
2012-04-13 0c259682f65eaaf23ea4ccb56a706d6baf3007e4
- Merge devel-framework branch, resolved conflicts


6 files added
2 files renamed
36 files modified
2 files deleted
9792 ■■■■ changed files
CHANGELOG 11 ●●●●● patch | view | raw | blame | history
bin/msgexport.sh 2 ●●● patch | view | raw | blame | history
index.php 27 ●●●● patch | view | raw | blame | history
installer/index.php 2 ●●●●● patch | view | raw | blame | history
installer/utils.php 16 ●●●● patch | view | raw | blame | history
program/include/clisetup.php 2 ●●● patch | view | raw | blame | history
program/include/html.php 66 ●●●● patch | view | raw | blame | history
program/include/iniset.php 60 ●●●●● patch | view | raw | blame | history
program/include/main.inc 2114 ●●●●● patch | view | raw | blame | history
program/include/rcmail.php 1247 ●●●●● patch | view | raw | blame | history
program/include/rcube.php 1226 ●●●●● patch | view | raw | blame | history
program/include/rcube_addressbook.php 7 ●●●● patch | view | raw | blame | history
program/include/rcube_base_replacer.php 110 ●●●●● patch | view | raw | blame | history
program/include/rcube_cache.php 14 ●●●● patch | view | raw | blame | history
program/include/rcube_config.php 14 ●●●● patch | view | raw | blame | history
program/include/rcube_contacts.php 42 ●●●● patch | view | raw | blame | history
program/include/rcube_html_page.php 323 ●●●●● patch | view | raw | blame | history
program/include/rcube_imap.php 53 ●●●● patch | view | raw | blame | history
program/include/rcube_imap_cache.php 69 ●●●● patch | view | raw | blame | history
program/include/rcube_imap_generic.php 49 ●●●●● patch | view | raw | blame | history
program/include/rcube_ldap.php 32 ●●●● patch | view | raw | blame | history
program/include/rcube_mdb2.php 345 ●●●●● patch | view | raw | blame | history
program/include/rcube_message.php 18 ●●●●● patch | view | raw | blame | history
program/include/rcube_message_header.php 238 ●●●●● patch | view | raw | blame | history
program/include/rcube_message_part.php 103 ●●●●● patch | view | raw | blame | history
program/include/rcube_output.php 259 ●●●●● patch | view | raw | blame | history
program/include/rcube_output_html.php 646 ●●●●● patch | view | raw | blame | history
program/include/rcube_output_json.php 99 ●●●●● patch | view | raw | blame | history
program/include/rcube_plugin.php 16 ●●●●● patch | view | raw | blame | history
program/include/rcube_plugin_api.php 83 ●●●●● patch | view | raw | blame | history
program/include/rcube_session.php 21 ●●●● patch | view | raw | blame | history
program/include/rcube_shared.inc 654 ●●●● patch | view | raw | blame | history
program/include/rcube_smtp.php 6 ●●●● patch | view | raw | blame | history
program/include/rcube_spellchecker.php 20 ●●●● patch | view | raw | blame | history
program/include/rcube_sqlite.inc 79 ●●●●● patch | view | raw | blame | history
program/include/rcube_storage.php 92 ●●●●● patch | view | raw | blame | history
program/include/rcube_string_replacer.php 6 ●●●● patch | view | raw | blame | history
program/include/rcube_ui.php 1468 ●●●●● patch | view | raw | blame | history
program/include/rcube_user.php 36 ●●●● patch | view | raw | blame | history
program/include/rcube_vcard.php 6 ●●●● patch | view | raw | blame | history
program/steps/addressbook/func.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 16 ●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 25 ●●●● patch | view | raw | blame | history
program/steps/mail/get.inc 25 ●●●● patch | view | raw | blame | history
program/steps/mail/show.inc 28 ●●●●● patch | view | raw | blame | history
program/steps/utils/error.inc 15 ●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,17 @@
CHANGELOG Roundcube Webmail
===========================
- Roundcube Framework:
    Add possibility to replace IMAP driver with custom class
    Add IMAP auto-connection feature, improving performance with caching enabled
    Replace imap_init hook with storage_init (with additional 'driver' argument)
    Improved performance by caching IMAP server's capabilities in session
    Unified global functions naming (rcube_ prefix)
    Move global functions from main.inc and rcube_shared.inc into classes
    Better classes separation
RELEASE 0.8-rc
----------------
- Set flexible width to login form fields (#1488418)
- Fix re-draw bug on list columns change in IE8 (#1487822)
- Allow mass-removal of addresses from a group (#1487748)
bin/msgexport.sh
@@ -34,7 +34,7 @@
    $IMAP->set_folder($mbox);
    $index = $IMAP->index($mbox, null, 'ASC');
    $count = $index->countMessages();
    $count = $index->count();
    $index = $index->get();
    vputs("Getting message list of {$mbox}...");
index.php
@@ -2,7 +2,7 @@
/*
 +-------------------------------------------------------------------------+
 | Roundcube Webmail IMAP Client                                           |
 | Version 0.8-svn                                                         |
 | Version 0.9-svn                                                         |
 |                                                                         |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                         |
 |                                                                         |
@@ -45,7 +45,7 @@
$RCMAIL = rcmail::get_instance();
// Make the whole PHP output non-cacheable (#1487797)
send_nocacheing_headers();
$RCMAIL->output->nocacheing_headers();
// turn on output buffering
ob_start();
@@ -67,14 +67,14 @@
}
// error steps
if ($RCMAIL->action=='error' && !empty($_GET['_code'])) {
if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) {
  raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
}
// check if https is required (for login) and redirect if necessary
if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) {
  $https_port = is_bool($force_https) ? 443 : $force_https;
  if (!rcube_https_check($https_port)) {
  if (!rcube_ui::https_check($https_port)) {
    $host  = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
    $host .= ($https_port != 443 ? ':' . $https_port : '');
    header('Location: https://' . $host . $_SERVER['REQUEST_URI']);
@@ -89,15 +89,15 @@
// try to log in
if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
  $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(RCUBE_INPUT_POST, 'login');
  $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_ui::INPUT_POST, 'login');
  // purge the session in case of new login when a session already exists 
  $RCMAIL->kill_session();
  $auth = $RCMAIL->plugins->exec_hook('authenticate', array(
    'host' => $RCMAIL->autoselect_host(),
    'user' => trim(get_input_value('_user', RCUBE_INPUT_POST)),
    'pass' => get_input_value('_pass', RCUBE_INPUT_POST, true,
    'user' => trim(rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST)),
    'pass' => rcube_ui::get_input_value('_pass', rcube_ui::INPUT_POST, true,
       $RCMAIL->config->get('password_charset', 'ISO-8859-1')),
    'cookiecheck' => true,
    'valid' => $request_valid,
@@ -119,11 +119,11 @@
    $RCMAIL->session->set_auth_cookie();
    // log successful login
    rcmail_log_login();
    $RCMAIL->log_login();
    // restore original request parameters
    $query = array();
    if ($url = get_input_value('_url', RCUBE_INPUT_POST)) {
    if ($url = rcube_ui::get_input_value('_url', rcube_ui::INPUT_POST)) {
      parse_str($url, $query);
      // prevent endless looping on login page
@@ -149,7 +149,7 @@
}
// end session (after optional referer check)
else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcube_check_referer())) {
else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcmail::check_referer())) {
  $userdata = array(
    'user' => $_SESSION['username'],
    'host' => $_SESSION['storage_host'],
@@ -172,7 +172,8 @@
// not logged in -> show login page
if (empty($RCMAIL->user->ID)) {
  // log session failures
  if (($task = get_input_value('_task', RCUBE_INPUT_GPC)) && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) {
  $task = rcube_ui::get_input_value('_task', rcube_ui::INPUT_GPC);
  if ($task && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) {
    $RCMAIL->session->log("Aborted session " . $sess_id . "; no valid session data found");
    $session_error = true;
  }
@@ -208,7 +209,7 @@
  // check client X-header to verify request origin
  if ($OUTPUT->ajax_call) {
    if (rc_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) {
    if (rcube_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) {
      header('HTTP/1.1 403 Forbidden');
      die("Invalid Request");
    }
@@ -220,7 +221,7 @@
  }
  // check referer if configured
  if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcube_check_referer()) {
  if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcmail::check_referer()) {
    raise_error(array(
      'code' => 403,
      'type' => 'php',
installer/index.php
@@ -53,6 +53,8 @@
set_include_path($include_path);
require_once 'utils.php';
require_once 'rcube_shared.inc';
// deprecated aliases (to be removed)
require_once 'main.inc';
session_start();
installer/utils.php
@@ -45,26 +45,16 @@
    include_once $filename. '.php';
}
/**
 * Fake internal error handler to catch errors
 */
function raise_error($p)
{
    $rci = rcube_install::get_instance();
    $rci->raise_error($p);
}
/**
 * Local callback function for PEAR errors
 */
function rcube_pear_error($err)
function __pear_error($err)
{
    raise_error(array(
    rcmail::raise_error(array(
        'code' => $err->getCode(),
        'message' => $err->getMessage(),
    ));
}
// set PEAR error handling (will also load the PEAR main class)
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, '__pear_error');
program/include/clisetup.php
@@ -55,7 +55,7 @@
            continue;
        $args[$key] = preg_replace(array('/^["\']/', '/["\']$/'), '', $value);
        if ($alias = $aliases[$key])
            $args[$alias] = $args[$key];
    }
program/include/html.php
@@ -277,7 +277,7 @@
        $attrib_arr = array();
        foreach ($attrib as $key => $value) {
            // skip size if not numeric
            if (($key=='size' && !is_numeric($value))) {
            if ($key == 'size' && !is_numeric($value)) {
                continue;
            }
@@ -297,16 +297,56 @@
                    $attrib_arr[] = $key . '="' . $key . '"';
                }
            }
            else if ($key=='value') {
                $attrib_arr[] = $key . '="' . Q($value, 'strict', false) . '"';
            }
            else {
                $attrib_arr[] = $key . '="' . Q($value) . '"';
                $attrib_arr[] = $key . '="' . self::quote($value) . '"';
            }
        }
        return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
    }
    /**
     * Convert a HTML attribute string attributes to an associative array (name => value)
     *
     * @param string Input string
     * @return array Key-value pairs of parsed attributes
     */
    public static function parse_attrib_string($str)
    {
        $attrib = array();
        $regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui';
        preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER);
        // convert attributes to an associative array (name => value)
        if ($regs) {
            foreach ($regs as $attr) {
                $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
            }
        }
        return $attrib;
    }
    /**
     * Replacing specials characters in html attribute value
     *
     * @param  string  $str  Input string
     *
     * @return string  The quoted string
     */
    public static function quote($str)
    {
        $str = htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
        // avoid douple quotation of &
        // @TODO: get rid of it?
        $str = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $str);
        return $str;
    }
}
/**
 * Class to create an HTML input field
@@ -317,9 +357,11 @@
{
    protected $tagname = 'input';
    protected $type = 'text';
    protected $allowed = array('type','name','value','size','tabindex',
    protected $allowed = array(
        'type','name','value','size','tabindex',
        'autocomplete','checked','onchange','onclick','disabled','readonly',
        'spellcheck','results','maxlength','src','multiple','placeholder');
        'spellcheck','results','maxlength','src','multiple','placeholder',
    );
    /**
     * Object constructor
@@ -517,11 +559,11 @@
        }
        if (!empty($value) && !preg_match('/mce_editor/', $this->attrib['class'])) {
            $value = Q($value, 'strict', false);
            $value = self::quote($value);
        }
        return self::tag($this->tagname, $this->attrib, $value,
        array_merge(self::$common_attrib, $this->allowed));
            array_merge(self::$common_attrib, $this->allowed));
    }
}
@@ -550,7 +592,7 @@
    protected $options = array();
    protected $allowed = array('name','size','tabindex','autocomplete',
    'multiple','onchange','disabled','rel');
    /**
     * Add a new option to this drop-down
     *
@@ -591,8 +633,9 @@
                'selected' => (in_array($option['value'], $select, true) ||
                  in_array($option['text'], $select, true)) ? 1 : null);
            $this->content .= self::tag('option', $attr, Q($option['text']));
            $this->content .= self::tag('option', $attr, self::quote($option['text']));
        }
        return parent::show();
    }
}
@@ -803,4 +846,3 @@
    }
}
program/include/iniset.php
@@ -40,7 +40,7 @@
}
// application constants
define('RCMAIL_VERSION', '0.8-svn');
define('RCMAIL_VERSION', '0.9-svn');
define('RCMAIL_CHARSET', 'UTF-8');
define('JS_OBJECT_NAME', 'rcmail');
define('RCMAIL_START', microtime(true));
@@ -51,11 +51,6 @@
if (!defined('RCMAIL_CONFIG_DIR')) {
    define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config');
}
// make sure path_separator is defined
if (!defined('PATH_SEPARATOR')) {
    define('PATH_SEPARATOR', (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') ? ';' : ':');
}
// RC include folders MUST be included FIRST to avoid other
@@ -80,59 +75,14 @@
    @mb_regex_encoding(RCMAIL_CHARSET);
}
/**
 * Use PHP5 autoload for dynamic class loading
 *
 * @todo Make Zend, PEAR etc play with this
 * @todo Make our classes conform to a more straight forward CS.
 */
function rcube_autoload($classname)
{
    $filename = preg_replace(
        array(
            '/MDB2_(.+)/',
            '/Mail_(.+)/',
            '/Net_(.+)/',
            '/Auth_(.+)/',
            '/^html_.+/',
            '/^utf8$/',
        ),
        array(
            'MDB2/\\1',
            'Mail/\\1',
            'Net/\\1',
            'Auth/\\1',
            'html',
            'utf8.class',
        ),
        $classname
    );
// include global functions
require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
    if ($fp = @fopen("$filename.php", 'r', true)) {
        fclose($fp);
        include_once("$filename.php");
        return true;
    }
    return false;
}
// Register autoloader
spl_autoload_register('rcube_autoload');
/**
 * Local callback function for PEAR errors
 */
function rcube_pear_error($err)
{
    error_log(sprintf("%s (%s): %s",
        $err->getMessage(),
        $err->getCode(),
        $err->getUserinfo()), 0);
}
// set PEAR error handling (will also load the PEAR main class)
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
// include global functions
// backward compatybility (to be removed)
require_once INSTALL_PATH . 'program/include/main.inc';
require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
program/include/main.inc
@@ -5,14 +5,14 @@
 | program/include/main.inc                                              |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Provide basic functions for the webmail package                     |
 |   Provide deprecated functions aliases for backward compatibility     |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
@@ -23,2224 +23,324 @@
*/
/**
 * Roundcube Webmail common functions
 * Roundcube Webmail deprecated functions
 *
 * @package Core
 * @author Thomas Bruederli <roundcube@gmail.com>
 */
require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
// define constannts for input reading
define('RCUBE_INPUT_GET', 0x0101);
define('RCUBE_INPUT_POST', 0x0102);
define('RCUBE_INPUT_GPC', 0x0103);
// constants for input reading
define('RCUBE_INPUT_GET',  rcube_ui::INPUT_GET);
define('RCUBE_INPUT_POST', rcube_ui::INPUT_POST);
define('RCUBE_INPUT_GPC',  rcube_ui::INPUT_GPC);
/**
 * Return correct name for a specific database table
 *
 * @param string Table name
 * @return string Translated table name
 */
function get_table_name($table)
  {
  global $CONFIG;
{
    return rcmail::get_instance()->db->table_name($table);
}
  // return table name if configured
  $config_key = 'db_table_'.$table;
  if (strlen($CONFIG[$config_key]))
    return $CONFIG[$config_key];
  return $table;
  }
/**
 * Return correct name for a specific database sequence
 * (used for Postgres only)
 *
 * @param string Secuence name
 * @return string Translated sequence name
 */
function get_sequence_name($sequence)
  {
  // return sequence name if configured
  $config_key = 'db_sequence_'.$sequence;
  $opt = rcmail::get_instance()->config->get($config_key);
{
    return rcmail::get_instance()->db->sequence_name($sequence);
}
  if (!empty($opt))
    return $opt;
  return $sequence;
  }
/**
 * Get localized text in the desired language
 * It's a global wrapper for rcmail::gettext()
 *
 * @param mixed Named parameters array or label name
 * @param string Domain to search in (e.g. plugin name)
 * @return string Localized text
 * @see rcmail::gettext()
 */
function rcube_label($p, $domain=null)
{
  return rcmail::get_instance()->gettext($p, $domain);
    return rcmail::get_instance()->gettext($p, $domain);
}
/**
 * Global wrapper of rcmail::text_exists()
 * to check whether a text label is defined
 *
 * @see rcmail::text_exists()
 */
function rcube_label_exists($name, $domain=null, &$ref_domain = null)
{
  return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
    return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
}
/**
 * Overwrite action variable
 *
 * @param string New action value
 */
function rcmail_overwrite_action($action)
  {
  $app = rcmail::get_instance();
  $app->action = $action;
  $app->output->set_env('action', $action);
  }
{
    rcmail::get_instance()->overwrite_action($action);
}
/**
 * Compose an URL for a specific action
 *
 * @param string  Request action
 * @param array   More URL parameters
 * @param string  Request task (omit if the same)
 * @return The application URL
 */
function rcmail_url($action, $p=array(), $task=null)
{
  $app = rcmail::get_instance();
  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
    return rcube_ui::url($action, $p, $task);
}
/**
 * Garbage collector function for temp files.
 * Remove temp files older than two days
 */
function rcmail_temp_gc()
{
  $rcmail = rcmail::get_instance();
  $tmp = unslashify($rcmail->config->get('temp_dir'));
  $expire = mktime() - 172800;  // expire in 48 hours
  if ($dir = opendir($tmp)) {
    while (($fname = readdir($dir)) !== false) {
      if ($fname{0} == '.')
        continue;
      if (filemtime($tmp.'/'.$fname) < $expire)
        @unlink($tmp.'/'.$fname);
    }
    closedir($dir);
  }
  $rcmail = rcmail::get_instance()->temp_gc();
}
// Deprecated
function rcube_charset_convert($str, $from, $to=NULL)
{
    return rcube_charset::convert($str, $from, $to);
}
// Deprecated
function rc_detect_encoding($string, $failover='')
{
    return rcube_charset::detect($string, $failover);
}
// Deprecated
function rc_utf8_clean($input)
{
    return rcube_charset::clean($input);
}
/**
 * Convert a variable into a javascript object notation
 *
 * @param mixed Input value
 * @return string Serialized JSON string
 */
function json_serialize($input)
{
    $input = rcube_charset::clean($input);
    // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
    // that's why we have @ here
    return @json_encode($input);
    return rcube_output::json_serialize($input);
}
/**
 * Replacing specials characters to a specific encoding type
 *
 * @param  string  Input string
 * @param  string  Encoding type: text|html|xml|js|url
 * @param  string  Replace mode for tags: show|replace|remove
 * @param  boolean Convert newlines
 * @return string  The quoted string
 */
function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
  {
  static $html_encode_arr = false;
  static $js_rep_table = false;
  static $xml_rep_table = false;
{
    return rcube_ui::rep_specialchars_output($str, $enctype, $mode, $newlines);
}
  if (!$enctype)
    $enctype = $OUTPUT->type;
  // encode for HTML output
  if ($enctype=='html')
    {
    if (!$html_encode_arr)
      {
      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
      unset($html_encode_arr['?']);
      }
    $ltpos = strpos($str, '<');
    $encode_arr = $html_encode_arr;
    // don't replace quotes and html tags
    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
      {
      unset($encode_arr['"']);
      unset($encode_arr['<']);
      unset($encode_arr['>']);
      unset($encode_arr['&']);
      }
    else if ($mode=='remove')
      $str = strip_tags($str);
    $out = strtr($str, $encode_arr);
    // avoid douple quotation of &
    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
    return $newlines ? nl2br($out) : $out;
    }
  // if the replace tables for XML and JS are not yet defined
  if ($js_rep_table===false)
    {
    $js_rep_table = $xml_rep_table = array();
    $xml_rep_table['&'] = '&amp;';
    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
      $xml_rep_table[chr($c)] = "&#$c;";
    $xml_rep_table['"'] = '&quot;';
    $js_rep_table['"'] = '\\"';
    $js_rep_table["'"] = "\\'";
    $js_rep_table["\\"] = "\\\\";
    // Unicode line and paragraph separators (#1486310)
    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
    }
  // encode for javascript use
  if ($enctype=='js')
    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
  // encode for plaintext
  if ($enctype=='text')
    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
  if ($enctype=='url')
    return rawurlencode($str);
  // encode for XML
  if ($enctype=='xml')
    return strtr($str, $xml_rep_table);
  // no encoding given -> return original string
  return $str;
  }
/**
 * Quote a given string.
 * Shortcut function for rep_specialchars_output
 *
 * @return string HTML-quoted string
 * @see rep_specialchars_output()
 */
function Q($str, $mode='strict', $newlines=TRUE)
  {
  return rep_specialchars_output($str, 'html', $mode, $newlines);
  }
{
    return rcube_ui::Q($str, $mode, $newlines);
}
/**
 * Quote a given string for javascript output.
 * Shortcut function for rep_specialchars_output
 *
 * @return string JS-quoted string
 * @see rep_specialchars_output()
 */
function JQ($str)
  {
  return rep_specialchars_output($str, 'js');
  }
{
    return rcube_ui::JQ($str);
}
/**
 * Read input value and convert it for internal use
 * Performs stripslashes() and charset conversion if necessary
 *
 * @param  string   Field name to read
 * @param  int      Source to get value from (GPC)
 * @param  boolean  Allow HTML tags in field value
 * @param  string   Charset to convert into
 * @return string   Field value or NULL if not available
 */
function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
{
  $value = NULL;
  if ($source == RCUBE_INPUT_GET) {
    if (isset($_GET[$fname]))
      $value = $_GET[$fname];
  }
  else if ($source == RCUBE_INPUT_POST) {
    if (isset($_POST[$fname]))
      $value = $_POST[$fname];
  }
  else if ($source == RCUBE_INPUT_GPC) {
    if (isset($_POST[$fname]))
      $value = $_POST[$fname];
    else if (isset($_GET[$fname]))
      $value = $_GET[$fname];
    else if (isset($_COOKIE[$fname]))
      $value = $_COOKIE[$fname];
  }
  return parse_input_value($value, $allow_html, $charset);
    return rcube_ui::get_input_value($fname, $source, $allow_html, $charset);
}
/**
 * Parse/validate input value. See get_input_value()
 * Performs stripslashes() and charset conversion if necessary
 *
 * @param  string   Input value
 * @param  boolean  Allow HTML tags in field value
 * @param  string   Charset to convert into
 * @return string   Parsed value
 */
function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
{
  global $OUTPUT;
  if (empty($value))
    return $value;
  if (is_array($value)) {
    foreach ($value as $idx => $val)
      $value[$idx] = parse_input_value($val, $allow_html, $charset);
    return $value;
  }
  // strip single quotes if magic_quotes_sybase is enabled
  if (ini_get('magic_quotes_sybase'))
    $value = str_replace("''", "'", $value);
  // strip slashes if magic_quotes enabled
  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
    $value = stripslashes($value);
  // remove HTML tags if not allowed
  if (!$allow_html)
    $value = strip_tags($value);
  $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
  // remove invalid characters (#1488124)
  if ($output_charset == 'UTF-8')
    $value = rc_utf8_clean($value);
  // convert to internal charset
  if ($charset && $output_charset)
    $value = rcube_charset_convert($value, $output_charset, $charset);
  return $value;
    return rcube_ui::parse_input_value($value, $allow_html, $charset);
}
/**
 * Convert array of request parameters (prefixed with _)
 * to a regular array with non-prefixed keys.
 *
 * @param  int   Source to get value from (GPC)
 * @return array Hash array with all request parameters
 */
function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
{
  $out = array();
  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
  foreach ($src as $key => $value) {
    $fname = $key[0] == '_' ? substr($key, 1) : $key;
    if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
      $out[$fname] = get_input_value($key, $mode);
  }
  return $out;
    return rcube_ui::request2param($mode, $ignore);
}
/**
 * Remove all non-ascii and non-word chars
 * except ., -, _
 */
function asciiwords($str, $css_id = false, $replace_with = '')
{
  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
  return preg_replace("/[^$allowed]/i", $replace_with, $str);
}
/**
 * Convert the given string into a valid HTML identifier
 * Same functionality as done in app.js with rcube_webmail.html_identifier()
 */
function html_identifier($str, $encode=false)
{
  if ($encode)
    return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
  else
    return asciiwords($str, true, '_');
    return rcube_ui::html_identifier($str, $encode);
}
/**
 * Remove single and double quotes from given string
 *
 * @param string Input value
 * @return string Dequoted string
 */
function strip_quotes($str)
{
  return str_replace(array("'", '"'), '', $str);
}
/**
 * Remove new lines characters from given string
 *
 * @param string Input value
 * @return string Stripped string
 */
function strip_newlines($str)
{
  return preg_replace('/[\r\n]/', '', $str);
}
/**
 * Create a HTML table based on the given data
 *
 * @param  array  Named table attributes
 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
 * @param  array  List of cols to show
 * @param  string Name of the identifier col
 * @return string HTML table code
 */
function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
{
  global $RCMAIL;
  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
  // add table header
  if (!$attrib['noheader'])
    foreach ($a_show_cols as $col)
      $table->add_header($col, Q(rcube_label($col)));
  $c = 0;
  if (!is_array($table_data))
  {
    $db = $RCMAIL->get_dbh();
    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
    {
      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col])));
      // format each col
      foreach ($a_show_cols as $col)
        $table->add($col, Q($sql_arr[$col]));
      $c++;
    }
  }
  else {
    foreach ($table_data as $row_data)
    {
      $class = !empty($row_data['class']) ? $row_data['class'] : '';
      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class));
      // format each col
      foreach ($a_show_cols as $col)
        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
      $c++;
    }
  }
  return $table->show($attrib);
    return rcube_ui::table_output($attrib, $table_data, $a_show_cols, $id_col);
}
/**
 * Create an edit field for inclusion on a form
 *
 * @param string col field name
 * @param string value field value
 * @param array attrib HTML element attributes for field
 * @param string type HTML element type (default 'text')
 * @return string HTML field definition
 */
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
  static $colcounts = array();
  $fname = '_'.$col;
  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
  if ($type == 'checkbox') {
    $attrib['value'] = '1';
    $input = new html_checkbox($attrib);
  }
  else if ($type == 'textarea') {
    $attrib['cols'] = $attrib['size'];
    $input = new html_textarea($attrib);
  }
  else if ($type == 'select') {
    $input = new html_select($attrib);
    $input->add('---', '');
    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
  }
  else if ($attrib['type'] == 'password') {
    $input = new html_passwordfield($attrib);
  }
  else {
    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
        $attrib['type'] = 'text';
    $input = new html_inputfield($attrib);
  }
  // use value from post
  if (isset($_POST[$fname])) {
    $postvalue = get_input_value($fname, RCUBE_INPUT_POST, true);
    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
  }
  $out = $input->show($value);
  return $out;
  return rcube_ui::get_edit_field($col, $value, $attrib, $type);
}
/**
 * Replace all css definitions with #container [def]
 * and remove css-inlined scripting
 *
 * @param string CSS source code
 * @param string Container ID to use as prefix
 * @return string Modified CSS source
 */
function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
  {
  $last_pos = 0;
  $replacements = new rcube_string_replacer;
{
    return rcube_ui::mod_css_styles($source, $container_id, $allow_remote);
}
  // ignore the whole block if evil styles are detected
  $source = rcmail_xss_entity_decode($source);
  $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
  $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
  if (preg_match("/$evilexpr/i", $stripped))
    return '/* evil! */';
  // cut out all contents between { and }
  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
    $styles = substr($source, $pos+1, $pos2-($pos+1));
    // check every line of a style block...
    if ($allow_remote) {
      $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
      foreach ($a_styles as $line) {
        $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
        // ... and only allow strict url() values
        if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) {
          $a_styles = array('/* evil! */');
          break;
        }
      }
      $styles = join(";\n", $a_styles);
    }
    $key = $replacements->add($styles);
    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
    $last_pos = $pos+2;
  }
  // remove html comments and add #container to each tag selector.
  // also replace body definition because we also stripped off the <body> tag
  $styles = preg_replace(
    array(
      '/(^\s*<!--)|(-->\s*$)/',
      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
      '/'.preg_quote($container_id, '/').'\s+body/i',
    ),
    array(
      '',
      "\\1#$container_id \\2",
      $container_id,
    ),
    $source);
  // put block contents back in
  $styles = $replacements->resolve($styles);
  return $styles;
  }
/**
 * Decode escaped entities used by known XSS exploits.
 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
 *
 * @param string CSS content to decode
 * @return string Decoded string
 */
function rcmail_xss_entity_decode($content)
{
  $out = html_entity_decode(html_entity_decode($content));
  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
  $out = preg_replace('#/\*.*\*/#Ums', '', $out);
  return $out;
    return rcube_ui::xss_entity_decode($content);
}
/**
 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
 *
 * @param array matches result from preg_replace_callback
 * @return string decoded entity
 */
function rcmail_xss_entity_decode_callback($matches)
{
  return chr(hexdec($matches[1]));
}
/**
 * Compose a valid attribute string for HTML tags
 *
 * @param array Named tag attributes
 * @param array List of allowed attributes
 * @return string HTML formatted attribute string
 */
function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
  {
  // allow the following attributes to be added to the <iframe> tag
  $attrib_str = '';
  foreach ($allowed_attribs as $a)
    if (isset($attrib[$a]))
      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
  return $attrib_str;
  }
/**
 * Convert a HTML attribute string attributes to an associative array (name => value)
 *
 * @param string Input string
 * @return array Key-value pairs of parsed attributes
 */
function parse_attrib_string($str)
  {
  $attrib = array();
  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
  // convert attributes to an associative array (name => value)
  if ($regs) {
    foreach ($regs as $attr) {
      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
    }
  }
  return $attrib;
  }
/**
 * Improved equivalent to strtotime()
 *
 * @param string Date string
 * @return int
 */
function rcube_strtotime($date)
{
  // check for MS Outlook vCard date format YYYYMMDD
  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
  }
  else if (is_numeric($date))
    return $date;
  // support non-standard "GMTXXXX" literal
  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
  // if date parsing fails, we have a date in non-rfc format.
  // remove token from the end and try again
  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
    $d = explode(' ', $date);
    array_pop($d);
    if (!$d) break;
    $date = implode(' ', $d);
  }
  return $ts;
    return html::attrib_string($attrib, $allowed_attribs);
}
function parse_attrib_string($str)
{
    return html::parse_attrib_string($str);
}
/**
 * Convert the given date to a human readable form
 * This uses the date formatting properties from config
 *
 * @param mixed  Date representation (string, timestamp or DateTime object)
 * @param string Date format to use
 * @param bool   Enables date convertion according to user timezone
 *
 * @return string Formatted date string
 */
function format_date($date, $format=NULL, $convert=true)
{
  global $RCMAIL, $CONFIG;
  if (is_object($date) && is_a($date, 'DateTime')) {
    $timestamp = $date->format('U');
  }
  else {
    if (!empty($date))
      $timestamp = rcube_strtotime($date);
    if (empty($timestamp))
      return '';
    try {
      $date = new DateTime("@".$timestamp);
    }
    catch (Exception $e) {
      return '';
    }
  }
  if ($convert) {
    try {
      // convert to the right timezone
      $stz = date_default_timezone_get();
      $tz = new DateTimeZone($RCMAIL->config->get('timezone'));
      $date->setTimezone($tz);
      date_default_timezone_set($tz->getName());
      $timestamp = $date->format('U');
    }
    catch (Exception $e) {
    }
  }
  // define date format depending on current time
  if (!$format) {
    $now         = time();
    $now_date    = getdate($now);
    $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
    $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
      $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
      $today  = true;
    }
    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
      $format = $RCMAIL->config->get('date_short', 'D H:i');
    else
      $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
  }
  // strftime() format
  if (preg_match('/%[a-z]+/i', $format)) {
    $format = strftime($format, $timestamp);
    if ($convert && $stz) {
      date_default_timezone_set($stz);
    }
    return $today ? (rcube_label('today') . ' ' . $format) : $format;
  }
  // parse format string manually in order to provide localized weekday and month names
  // an alternative would be to convert the date() format string to fit with strftime()
  $out = '';
  for ($i=0; $i<strlen($format); $i++) {
    if ($format[$i]=='\\')  // skip escape chars
      continue;
    // write char "as-is"
    if ($format[$i]==' ' || $format{$i-1}=='\\')
      $out .= $format[$i];
    // weekday (short)
    else if ($format[$i]=='D')
      $out .= rcube_label(strtolower(date('D', $timestamp)));
    // weekday long
    else if ($format[$i]=='l')
      $out .= rcube_label(strtolower(date('l', $timestamp)));
    // month name (short)
    else if ($format[$i]=='M')
      $out .= rcube_label(strtolower(date('M', $timestamp)));
    // month name (long)
    else if ($format[$i]=='F')
      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
    else if ($format[$i]=='x')
      $out .= strftime('%x %X', $timestamp);
    else
      $out .= date($format[$i], $timestamp);
  }
  if ($today) {
    $label = rcube_label('today');
    // replcae $ character with "Today" label (#1486120)
    if (strpos($out, '$') !== false) {
      $out = preg_replace('/\$/', $label, $out, 1);
    }
    else {
      $out = $label . ' ' . $out;
    }
  }
  if ($convert && $stz) {
    date_default_timezone_set($stz);
  }
  return $out;
    return rcube_ui::format_date($date, $format, $convert);
}
/**
 * Compose a valid representation of name and e-mail address
 *
 * @param string E-mail address
 * @param string Person name
 * @return string Formatted string
 */
function format_email_recipient($email, $name='')
{
  if ($name && $name != $email) {
    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
  }
  return trim($email);
}
/**
 * Return the mailboxlist in HTML
 *
 * @param array Named parameters
 * @return string HTML code for the gui object
 */
function rcmail_mailbox_list($attrib)
{
  global $RCMAIL;
  static $a_mailboxes;
  $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
  // add some labels to client
  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
  $type = $attrib['type'] ? $attrib['type'] : 'ul';
  unset($attrib['type']);
  if ($type=='ul' && !$attrib['id'])
    $attrib['id'] = 'rcmboxlist';
  if (empty($attrib['folder_name']))
    $attrib['folder_name'] = '*';
  // get mailbox list
  $mbox_name = $RCMAIL->storage->get_folder();
  // build the folders tree
  if (empty($a_mailboxes)) {
    // get mailbox list
    $a_folders = $RCMAIL->storage->list_folders_subscribed('', $attrib['folder_name'], $attrib['folder_filter']);
    $delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
    $a_mailboxes = array();
    foreach ($a_folders as $folder)
      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
  }
  // allow plugins to alter the folder tree or to localize folder names
  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array(
    'list'      => $a_mailboxes,
    'delimiter' => $delimiter,
    'type'      => $type,
    'attribs'   => $attrib,
  ));
  $a_mailboxes = $hook['list'];
  $attrib      = $hook['attribs'];
  if ($type == 'select') {
    $select = new html_select($attrib);
    // add no-selection option
    if ($attrib['noselection'])
      $select->add(rcube_label($attrib['noselection']), '');
    rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
    $out = $select->show($attrib['default']);
  }
  else {
    $js_mailboxlist = array();
    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
    $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']);
    $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
  }
  return $out;
    return rcube_ui::folder_list($attrib);
}
/**
 * Return the mailboxlist as html_select object
 *
 * @param array Named parameters
 * @return html_select HTML drop-down object
 */
function rcmail_mailbox_select($p = array())
function rcmail_mailbox_select($attrib = array())
{
  global $RCMAIL;
  $p += array('maxlength' => 100, 'realnames' => false);
  $a_mailboxes = array();
  $storage = $RCMAIL->get_storage();
  if (empty($p['folder_name'])) {
    $p['folder_name'] = '*';
  }
  if ($p['unsubscribed'])
    $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
  else
    $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
  $delimiter = $storage->get_hierarchy_delimiter();
  foreach ($list as $folder) {
    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
  }
  $select = new html_select($p);
  if ($p['noselection'])
    $select->add($p['noselection'], '');
  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
  return $select;
    return rcube_ui::folder_selector($attrib);
}
/**
 * Create a hierarchical array of the mailbox list
 * @access private
 * @return void
 */
function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
{
  global $RCMAIL;
  // Handle namespace prefix
  $prefix = '';
  if (!$path) {
    $n_folder = $folder;
    $folder = $RCMAIL->storage->mod_folder($folder);
    if ($n_folder != $folder) {
      $prefix = substr($n_folder, 0, -strlen($folder));
    }
  }
  $pos = strpos($folder, $delm);
  if ($pos !== false) {
    $subFolders = substr($folder, $pos+1);
    $currentFolder = substr($folder, 0, $pos);
    // sometimes folder has a delimiter as the last character
    if (!strlen($subFolders))
      $virtual = false;
    else if (!isset($arrFolders[$currentFolder]))
      $virtual = true;
    else
      $virtual = $arrFolders[$currentFolder]['virtual'];
  }
  else {
    $subFolders = false;
    $currentFolder = $folder;
    $virtual = false;
  }
  $path .= $prefix.$currentFolder;
  if (!isset($arrFolders[$currentFolder])) {
    $arrFolders[$currentFolder] = array(
      'id' => $path,
      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
      'virtual' => $virtual,
      'folders' => array());
  }
  else
    $arrFolders[$currentFolder]['virtual'] = $virtual;
  if (strlen($subFolders))
    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
/**
 * Return html for a structured list &lt;ul&gt; for the mailbox tree
 * @access private
 * @return string
 */
function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
{
  global $RCMAIL, $CONFIG;
  $maxlength = intval($attrib['maxlength']);
  $realnames = (bool)$attrib['realnames'];
  $msgcounts = $RCMAIL->storage->get_cache('messagecount');
  $out = '';
  foreach ($arrFolders as $key => $folder) {
    $title        = null;
    $folder_class = rcmail_folder_classname($folder['id']);
    $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
    $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
    if ($folder_class && !$realnames) {
      $foldername = rcube_label($folder_class);
    }
    else {
      $foldername = $folder['name'];
      // shorten the folder name to a given length
      if ($maxlength && $maxlength > 1) {
        $fname = abbreviate_string($foldername, $maxlength);
        if ($fname != $foldername)
          $title = $foldername;
        $foldername = $fname;
      }
    }
    // make folder name safe for ids and class names
    $folder_id = html_identifier($folder['id'], true);
    $classes = array('mailbox');
    // set special class for Sent, Drafts, Trash and Junk
    if ($folder_class)
      $classes[] = $folder_class;
    if ($folder['id'] == $mbox_name)
      $classes[] = 'selected';
    if ($folder['virtual'])
      $classes[] = 'virtual';
    else if ($unread)
      $classes[] = 'unread';
    $js_name = JQ($folder['id']);
    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
    $link_attrib = $folder['virtual'] ? array() : array(
      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
      'rel' => $folder['id'],
      'title' => $title,
    );
    $out .= html::tag('li', array(
        'id' => "rcmli".$folder_id,
        'class' => join(' ', $classes),
        'noclose' => true),
      html::a($link_attrib, $html_name) .
      (!empty($folder['folders']) ? html::div(array(
        'class' => ($collapsed ? 'collapsed' : 'expanded'),
        'style' => "position:absolute",
        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
      ), '&nbsp;') : ''));
    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
    if (!empty($folder['folders'])) {
      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
    }
    $out .= "</li>\n";
  }
  return $out;
}
/**
 * Return html for a flat list <select> for the mailbox tree
 * @access private
 * @return string
 */
function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array())
{
  global $RCMAIL;
  $out = '';
  foreach ($arrFolders as $key => $folder) {
    // skip exceptions (and its subfolders)
    if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
      continue;
    }
    // skip folders in which it isn't possible to create subfolders
    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->storage->folder_attributes($folder['id']))
        && in_array('\\Noinferiors', $attrs)
    ) {
      continue;
    }
    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
      $foldername = rcube_label($folder_class);
    else {
      $foldername = $folder['name'];
      // shorten the folder name to a given length
      if ($maxlength && $maxlength>1)
        $foldername = abbreviate_string($foldername, $maxlength);
    }
    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
    if (!empty($folder['folders']))
      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
        $select, $realnames, $nestLevel+1, $opts);
  }
  return $out;
}
/**
 * Return internal name for the given folder if it matches the configured special folders
 * @access private
 * @return string
 */
function rcmail_folder_classname($folder_id)
{
  global $CONFIG;
  if ($folder_id == 'INBOX')
    return 'inbox';
  // for these mailboxes we have localized labels and css classes
  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
  {
    if ($folder_id == $CONFIG[$smbx.'_mbox'])
      return $smbx;
  }
}
/**
 * Try to localize the given IMAP folder name.
 * UTF-7 decode it in case no localized text was found
 *
 * @param string Folder name
 * @return string Localized folder name in UTF-8 encoding
 */
function rcmail_localize_foldername($name)
{
  if ($folder_class = rcmail_folder_classname($name))
    return rcube_label($folder_class);
  else
    return rcube_charset_convert($name, 'UTF7-IMAP');
    return rcube_ui::localize_foldername($name);
}
function rcmail_localize_folderpath($path)
{
    global $RCMAIL;
    $protect_folders = $RCMAIL->config->get('protect_default_folders');
    $default_folders = (array) $RCMAIL->config->get('default_folders');
    $delimiter       = $RCMAIL->storage->get_hierarchy_delimiter();
    $path            = explode($delimiter, $path);
    $result          = array();
    foreach ($path as $idx => $dir) {
        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
        if ($protect_folders && in_array($directory, $default_folders)) {
            unset($result);
            $result[] = rcmail_localize_foldername($directory);
        }
        else {
            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
        }
    }
    return implode($delimiter, $result);
    return rcube_ui::localize_folderpath($path);
}
function rcmail_quota_display($attrib)
{
  global $OUTPUT;
  if (!$attrib['id'])
    $attrib['id'] = 'rcmquotadisplay';
  $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
  $quota = rcmail_quota_content($attrib);
  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
  return html::span($attrib, '');
    return rcube_ui::quota_display($attrib);
}
function rcmail_quota_content($attrib=NULL)
function rcmail_quota_content($attrib = null)
{
  global $RCMAIL;
  $quota = $RCMAIL->storage->get_quota();
  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
  $quota_result = (array) $quota;
  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
    $quota_result['title'] = rcube_label('unlimited');
    $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));
    $title = sprintf('%s / %s (%.0f%%)',
        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
        $quota_result['percent']);
    $quota_result['title'] = $title;
    if ($attrib['width'])
      $quota_result['width'] = $attrib['width'];
    if ($attrib['height'])
      $quota_result['height']    = $attrib['height'];
  }
  else {
    $quota_result['title'] = rcube_label('unknown');
    $quota_result['percent'] = 0;
  }
  return $quota_result;
    return rcube_ui::quota_content($attrib);
}
/**
 * Outputs error message according to server error/response codes
 *
 * @param string Fallback message label
 * @param string Fallback message label arguments
 *
 * @return void
 */
function rcmail_display_server_error($fallback=null, $fallback_args=null)
{
    global $RCMAIL;
    $err_code = $RCMAIL->storage->get_error_code();
    $res_code = $RCMAIL->storage->get_response_code();
    if ($err_code < 0) {
        $RCMAIL->output->show_message('storageerror', 'error');
    }
    else if ($res_code == rcube_storage::NOPERM) {
        $RCMAIL->output->show_message('errornoperm', 'error');
    }
    else if ($res_code == rcube_storage::READONLY) {
        $RCMAIL->output->show_message('errorreadonly', 'error');
    }
    else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) {
        // try to detect access rights problem and display appropriate message
        if (stripos($err_str, 'Permission denied') !== false)
            $RCMAIL->output->show_message('errornoperm', 'error');
        else
            $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
    }
    else if ($fallback) {
        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
    }
    return true;
    rcube_ui::display_server_error($fallback, $fallback_args);
}
/**
 * Generate CSS classes from mimetype and filename extension
 *
 * @param string Mimetype
 * @param string The filename
 * @return string CSS classes separated by space
 */
function rcmail_filetype2classname($mimetype, $filename)
{
  list($primary, $secondary) = explode('/', $mimetype);
  $classes = array($primary ? $primary : 'unknown');
  if ($secondary) {
    $classes[] = $secondary;
  }
  if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
    $classes[] = $m[1];
  }
  return strtolower(join(" ", $classes));
    return rcube_ui::file2class($mimetype, $filename);
}
/**
 * Output HTML editor scripts
 *
 * @param string Editor mode
 * @return void
 */
function rcube_html_editor($mode='')
{
  global $RCMAIL;
  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
  if ($hook['abort'])
    return;
  $lang = strtolower($_SESSION['language']);
  // TinyMCE uses two-letter lang codes, with exception of Chinese
  if (strpos($lang, 'zh_') === 0)
    $lang = str_replace('_', '-', $lang);
  else
    $lang = substr($lang, 0, 2);
  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
    $lang = 'en';
  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
  $RCMAIL->output->include_script('editor.js');
  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
    json_encode(array(
        'mode'       => $mode,
        'lang'       => $lang,
        'skin_path'  => $RCMAIL->output->get_skin_path(),
        'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
        'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary')),
    ))), 'docready');
    rcube_ui::html_editor($mode);
}
/**
 * Replaces TinyMCE's emoticon images with plain-text representation
 *
 * @param string HTML content
 * @return string HTML content
 */
function rcmail_replace_emoticons($html)
{
  $emoticons = array(
    '8-)' => 'smiley-cool',
    ':-#' => 'smiley-foot-in-mouth',
    ':-*' => 'smiley-kiss',
    ':-X' => 'smiley-sealed',
    ':-P' => 'smiley-tongue-out',
    ':-@' => 'smiley-yell',
    ":'(" => 'smiley-cry',
    ':-(' => 'smiley-frown',
    ':-D' => 'smiley-laughing',
    ':-)' => 'smiley-smile',
    ':-S' => 'smiley-undecided',
    ':-$' => 'smiley-embarassed',
    'O:-)' => 'smiley-innocent',
    ':-|' => 'smiley-money-mouth',
    ':-O' => 'smiley-surprised',
    ';-)' => 'smiley-wink',
  );
  foreach ($emoticons as $idx => $file) {
    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
    $replace[] = $idx;
  }
  return preg_replace($search, $replace, $html);
    return rcube_ui::replace_emoticons($html);
}
/**
 * Send the given message using the configured method
 *
 * @param object $message    Reference to Mail_MIME object
 * @param string $from       Sender address string
 * @param array  $mailto     Array of recipient address strings
 * @param array  $smtp_error SMTP error array (reference)
 * @param string $body_file  Location of file with saved message body (reference),
 *                           used when delay_file_io is enabled
 * @param array  $smtp_opts  SMTP options (e.g. DSN request)
 *
 * @return boolean Send status.
 */
function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
{
  global $CONFIG, $RCMAIL;
  $headers = $message->headers();
  // send thru SMTP server using custom SMTP library
  if ($CONFIG['smtp_server']) {
    // generate list of recipients
    $a_recipients = array($mailto);
    if (strlen($headers['Cc']))
      $a_recipients[] = $headers['Cc'];
    if (strlen($headers['Bcc']))
      $a_recipients[] = $headers['Bcc'];
    // clean Bcc from header for recipients
    $send_headers = $headers;
    unset($send_headers['Bcc']);
    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
    unset($message->_headers['Bcc']);
    $smtp_headers = $message->txtHeaders($send_headers, true);
    if ($message->getParam('delay_file_io')) {
      // use common temp dir
      $temp_dir = $RCMAIL->config->get('temp_dir');
      $body_file = tempnam($temp_dir, 'rcmMsg');
      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
        raise_error(array('code' => 650, 'type' => 'php',
            'file' => __FILE__, 'line' => __LINE__,
            'message' => "Could not create message: ".$mime_result->getMessage()),
            TRUE, FALSE);
        return false;
      }
      $msg_body = fopen($body_file, 'r');
    } else {
      $msg_body = $message->get();
    }
    // send message
    if (!is_object($RCMAIL->smtp))
      $RCMAIL->smtp_init(true);
    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
    $smtp_response = $RCMAIL->smtp->get_response();
    $smtp_error = $RCMAIL->smtp->get_error();
    // log error
    if (!$sent)
      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
  }
  // send mail using PHP's mail() function
  else {
    // unset some headers because they will be added by the mail() function
    $headers_enc = $message->headers($headers);
    $headers_php = $message->_headers;
    unset($headers_php['To'], $headers_php['Subject']);
    // reset stored headers and overwrite
    $message->_headers = array();
    $header_str = $message->txtHeaders($headers_php);
    // #1485779
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
        $headers_enc['To'] = implode(', ', $m[1]);
      }
    }
    $msg_body = $message->get();
    if (PEAR::isError($msg_body))
      raise_error(array('code' => 650, 'type' => 'php',
            'file' => __FILE__, 'line' => __LINE__,
            'message' => "Could not create message: ".$msg_body->getMessage()),
            TRUE, FALSE);
    else {
      $delim   = $RCMAIL->config->header_delimiter();
      $to      = $headers_enc['To'];
      $subject = $headers_enc['Subject'];
      $header_str = rtrim($header_str);
      if ($delim != "\r\n") {
        $header_str = str_replace("\r\n", $delim, $header_str);
        $msg_body   = str_replace("\r\n", $delim, $msg_body);
        $to         = str_replace("\r\n", $delim, $to);
        $subject    = str_replace("\r\n", $delim, $subject);
      }
      if (ini_get('safe_mode'))
        $sent = mail($to, $subject, $msg_body, $header_str);
      else
        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
    }
  }
  if ($sent) {
    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
    // remove MDN headers after sending
    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
    // get all recipients
    if ($headers['Cc'])
      $mailto .= $headers['Cc'];
    if ($headers['Bcc'])
      $mailto .= $headers['Bcc'];
    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
      $mailto = implode(', ', array_unique($m[1]));
    if ($CONFIG['smtp_log']) {
      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
        $RCMAIL->user->get_username(),
        $_SERVER['REMOTE_ADDR'],
        $mailto,
        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
    }
  }
  if (is_resource($msg_body)) {
    fclose($msg_body);
  }
  $message->_headers = array();
  $message->headers($headers);
  return $sent;
    return rcmail::get_instance()->deliver_message($message, $from, $mailto, $smtp_error, $body_file, $smtp_opts);
}
// Returns unique Message-ID
function rcmail_gen_message_id()
{
  global $RCMAIL;
  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
  $domain_part = $RCMAIL->user->get_username('domain');
  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
      && preg_match('/\.[a-z]+$/i', $host)) {
        $domain_part = $host;
    }
    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
      && preg_match('/\.[a-z]+$/i', $host)) {
        $domain_part = $host;
    }
  }
  return sprintf('<%s@%s>', $local_part, $domain_part);
    return rcmail::get_instance()->gen_message_id();
}
// Returns RFC2822 formatted current date in user's timezone
function rcmail_user_date()
{
  global $RCMAIL;
  // get user's timezone
  try {
    $tz   = new DateTimeZone($RCMAIL->config->get('timezone'));
    $date = new DateTime('now', $tz);
  }
  catch (Exception $e) {
    $date = new DateTime();
  }
  return $date->format('r');
    return rcmail::get_instance()->user_date();
}
/**
 * Check if we can process not exceeding memory_limit
 *
 * @param integer Required amount of memory
 * @return boolean
 */
function rcmail_mem_check($need)
{
  $mem_limit = parse_bytes(ini_get('memory_limit'));
  $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
  return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
    return rcube_ui::mem_check($need);
}
/**
 * Check if working in SSL mode
 *
 * @param integer HTTPS port number
 * @param boolean Enables 'use_https' option checking
 * @return boolean
 */
function rcube_https_check($port=null, $use_https=true)
{
  global $RCMAIL;
  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
    return true;
  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
    return true;
  if ($port && $_SERVER['SERVER_PORT'] == $port)
    return true;
  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
    return true;
  return false;
    return rcube_ui::https_check($port, $use_https);
}
/**
 * For backward compatibility.
 *
 * @global rcmail $RCMAIL
 * @param string $var_name Variable name.
 * @return void
 */
function rcube_sess_unset($var_name=null)
{
  global $RCMAIL;
  $RCMAIL->session->remove($var_name);
    rcmail::get_instance()->session->remove($var_name);
}
/**
 * Replaces hostname variables
 *
 * @param string $name Hostname
 * @param string $host Optional IMAP hostname
 * @return string
 */
function rcube_parse_host($name, $host='')
{
  // %n - host
  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
  $d = preg_replace('/^[^\.]+\./', '', $n);
  // %h - IMAP host
  $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
  $z = preg_replace('/^[^\.]+\./', '', $h);
  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
  if ( strpos($name, '%s') !== false ){
    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
      return false;
  }
  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
  return $name;
    return rcmail::parse_host($name, $host);
}
/**
 * E-mail address validation
 *
 * @param string $email Email address
 * @param boolean $dns_check True to check dns
 * @return boolean
 */
function check_email($email, $dns_check=true)
{
  // Check for invalid characters
  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
    return false;
  // Check for length limit specified by RFC 5321 (#1486453)
  if (strlen($email) > 254)
    return false;
  $email_array = explode('@', $email);
  // Check that there's one @ symbol
  if (count($email_array) < 2)
    return false;
  $domain_part = array_pop($email_array);
  $local_part = implode('@', $email_array);
  // from PEAR::Validate
  $regexp = '&^(?:
    ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                  #1 quoted name
    ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))     #2 OR dot-atom (RFC5322)
    $&xi';
  if (!preg_match($regexp, $local_part))
    return false;
  // Check domain part
  if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part))
    return true; // IP address
  else {
    // If not an IP address
    $domain_array = explode('.', $domain_part);
    if (sizeof($domain_array) < 2)
      return false; // Not enough parts to be a valid domain
    foreach ($domain_array as $part)
      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
        return false;
    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
      return true;
    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
      $lookup = array();
      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
      foreach ($lookup as $line) {
        if (strpos($line, 'MX preference'))
          return true;
      }
      return false;
    }
    // find MX record(s)
    if (getmxrr($domain_part, $mx_records))
      return true;
    // find any DNS record
    if (checkdnsrr($domain_part, 'ANY'))
      return true;
  }
  return false;
    return rcmail::get_instance()->check_email($email, $dns_check);
}
/*
 * Idn_to_ascii wrapper.
 * Intl/Idn modules version of this function doesn't work with e-mail address
 */
function rcube_idn_to_ascii($str)
{
  return rcube_idn_convert($str, true);
}
/*
 * Idn_to_ascii wrapper.
 * Intl/Idn modules version of this function doesn't work with e-mail address
 */
function rcube_idn_to_utf8($str)
{
  return rcube_idn_convert($str, false);
}
function rcube_idn_convert($input, $is_utf=false)
{
  if ($at = strpos($input, '@')) {
    $user   = substr($input, 0, $at);
    $domain = substr($input, $at+1);
  }
  else {
    $domain = $input;
  }
  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
  if ($domain === false) {
    return '';
  }
  return $at ? $user . '@' . $domain : $domain;
}
/**
 * Helper class to turn relative urls into absolute ones
 * using a predefined base
 */
class rcube_base_replacer
{
  private $base_url;
  public function __construct($base)
  {
    $this->base_url = $base;
  }
  public function callback($matches)
  {
    return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
  }
  public function replace($body)
  {
    return preg_replace_callback(array(
      '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
      '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
      ),
      array($this, 'callback'), $body);
  }
  /**
   * Convert paths like ../xxx to an absolute path using a base url
   *
   * @param string $path     Relative path
   * @param string $base_url Base URL
   *
   * @return string Absolute URL
   */
  public static function absolute_url($path, $base_url)
  {
    $host_url = $base_url;
    $abs_path = $path;
    // check if path is an absolute URL
    if (preg_match('/^[fhtps]+:\/\//', $path)) {
      return $path;
    }
    // check if path is a content-id scheme
    if (strpos($path, 'cid:') === 0) {
      return $path;
    }
    // cut base_url to the last directory
    if (strrpos($base_url, '/') > 7) {
      $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
      $base_url = substr($base_url, 0, strrpos($base_url, '/'));
    }
    // $path is absolute
    if ($path[0] == '/') {
      $abs_path = $host_url.$path;
    }
    else {
      // strip './' because its the same as ''
      $path = preg_replace('/^\.\//', '', $path);
      if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $a_match) {
          if (strrpos($base_url, '/')) {
            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
          }
          $path = substr($path, 3);
        }
      }
      $abs_path = $base_url.'/'.$path;
    }
    return $abs_path;
  }
}
/****** debugging and logging functions ********/
/**
 * Print or write debug messages
 *
 * @param mixed Debug message or data
 * @return void
 */
function console()
{
    $args = func_get_args();
    if (class_exists('rcmail', false)) {
        $rcmail = rcmail::get_instance();
        if (is_object($rcmail->plugins)) {
            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
            if ($plugin['abort'])
                return;
            $args = $plugin['args'];
        }
    }
    $msg = array();
    foreach ($args as $arg)
        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
    write_log('console', join(";\n", $msg));
    call_user_func_array(array('rcmail', 'console'), func_get_args());
}
/**
 * Append a line to a logfile in the logs directory.
 * Date will be added automatically to the line.
 *
 * @param $name name of log file
 * @param line Line to append
 * @return void
 */
function write_log($name, $line)
{
  global $CONFIG, $RCMAIL;
  if (!is_string($line))
    $line = var_export($line, true);
  if (empty($CONFIG['log_date_format']))
    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
  $date = date($CONFIG['log_date_format']);
  // trigger logging hook
  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
    $name = $log['name'];
    $line = $log['line'];
    $date = $log['date'];
    if ($log['abort'])
      return true;
  }
  if ($CONFIG['log_driver'] == 'syslog') {
    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
    syslog($prio, $line);
    return true;
  }
  else {
    $line = sprintf("[%s]: %s\n", $date, $line);
    // log_driver == 'file' is assumed here
    if (empty($CONFIG['log_dir']))
      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
    // try to open specific log file for writing
    $logfile = $CONFIG['log_dir'].'/'.$name;
    if ($fp = @fopen($logfile, 'a')) {
      fwrite($fp, $line);
      fflush($fp);
      fclose($fp);
      return true;
    }
    else
      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
  }
  return false;
    return rcmail::write_log($name, $line);
}
/**
 * Write login data (name, ID, IP address) to the 'userlogins' log file.
 *
 * @return void
 */
function rcmail_log_login()
{
  global $RCMAIL;
  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
    return;
  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
    return rcmail::get_instance()->log_login();
}
/**
 * Returns remote IP address and forwarded addresses if found
 *
 * @return string Remote IP address(es)
 */
function rcmail_remote_ip()
{
    $address = $_SERVER['REMOTE_ADDR'];
    // append the NGINX X-Real-IP header, if set
    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
    }
    // append the X-Forwarded-For header, if set
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    if (!empty($remote_ip))
        $address .= '(' . implode(',', $remote_ip) . ')';
    return $address;
    return rcmail::remote_ip();
}
/**
 * Check whether the HTTP referer matches the current request
 *
 * @return boolean True if referer is the same host+path, false if not
 */
function rcube_check_referer()
{
  $uri = parse_url($_SERVER['REQUEST_URI']);
  $referer = parse_url(rc_request_header('Referer'));
  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
    return rcmail::check_referer();
}
/**
 * @access private
 * @return mixed
 */
function rcube_timer()
{
  return microtime(true);
    return rcmail::timer();
}
/**
 * @access private
 * @return void
 */
function rcube_print_time($timer, $label='Timer', $dest='console')
{
  static $print_count = 0;
  $print_count++;
  $now = rcube_timer();
  $diff = $now-$timer;
  if (empty($label))
    $label = 'Timer '.$print_count;
  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
    rcmail::print_timer($timer, $label, $dest);
}
/**
 * Throw system error and show error page
 *
 * @param array Named parameters
 *  - 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
 * @param boolean True to log the error
 * @param boolean Terminate script execution
 */
// may be defined in Installer
if (!function_exists('raise_error')) {
function raise_error($arg=array(), $log=false, $terminate=false)
{
    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
    // report bug (if not incompatible browser)
    if ($log && $arg['type'] && $arg['message'])
        rcube_log_bug($arg);
    // display error page and terminate script
    if ($terminate) {
        $ERROR_CODE = $arg['code'];
        $ERROR_MESSAGE = $arg['message'];
        include INSTALL_PATH . 'program/steps/utils/error.inc';
        exit;
    }
}
    rcmail::raise_error($arg, $log, $terminate);
}
/**
 * Report error according to configured debug_level
 *
 * @param array Named parameters
 * @return void
 * @see raise_error()
 */
function rcube_log_bug($arg_arr)
{
    global $CONFIG;
    $program = strtoupper($arg_arr['type']);
    $level   = $CONFIG['debug_level'];
    // disable errors for ajax requests, write to log instead (#1487831)
    if (($level & 4) && !empty($_REQUEST['_remote'])) {
        $level = ($level ^ 4) | 1;
    }
    // write error to local log file
    if ($level & 1) {
        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
        $log_entry = sprintf("%s Error: %s%s (%s %s)",
            $program,
            $arg_arr['message'],
            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
            $_SERVER['REQUEST_METHOD'],
            $_SERVER['REQUEST_URI'] . $post_query);
        if (!write_log('errors', $log_entry)) {
            // send error to PHPs error handler if write_log didn't succeed
            trigger_error($arg_arr['message']);
        }
    }
    // report the bug to the global bug reporting system
    if ($level & 2) {
        // TODO: Send error via HTTP
    }
    // show error if debug_mode is on
    if ($level & 4) {
        print "<b>$program Error";
        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
            print " in $arg_arr[file] ($arg_arr[line])";
        print ':</b>&nbsp;';
        print nl2br($arg_arr['message']);
        print '<br />';
        flush();
    }
    rcmail::log_bug($arg_arr);
}
function rcube_upload_progress()
{
    global $RCMAIL;
    $prefix = ini_get('apc.rfc1867_prefix');
    $params = array(
        'action' => $RCMAIL->action,
        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
    );
    if (function_exists('apc_fetch')) {
        $status = apc_fetch($prefix . $params['name']);
        if (!empty($status)) {
            $status['percent'] = round($status['current']/$status['total']*100);
            $params = array_merge($status, $params);
        }
    }
    if (isset($params['percent']))
        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
            'percent' => $params['percent'] . '%',
            'current' => show_bytes($params['current']),
            'total'   => show_bytes($params['total'])
        )));
    $RCMAIL->output->command('upload_progress_update', $params);
    $RCMAIL->output->send();
    rcube_ui::upload_progress();
}
function rcube_upload_init()
{
    global $RCMAIL;
    // Enable upload progress bar
    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
        if ($field_name = ini_get('apc.rfc1867_name')) {
            $RCMAIL->output->set_env('upload_progress_name', $field_name);
            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
        }
    }
    // 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;
    $RCMAIL->output->set_env('max_filesize', $max_filesize);
    $max_filesize = show_bytes($max_filesize);
    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
    return $max_filesize;
    return rcube_ui::upload_init();
}
/**
 * Initializes client-side autocompletion
 */
function rcube_autocomplete_init()
{
    global $RCMAIL;
    static $init;
    if ($init)
        return;
    $init = 1;
    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
      if (count($book_types) > 1) {
        $RCMAIL->output->set_env('autocomplete_threads', $threads);
        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
      }
    }
    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
    $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
    rcube_ui::autocomplete_init();
}
function rcube_fontdefs($font = null)
{
  $fonts = array(
    'Andale Mono'   => '"Andale Mono",Times,monospace',
    'Arial'         => 'Arial,Helvetica,sans-serif',
    'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
    'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
    'Courier New'   => '"Courier New",Courier,monospace',
    'Georgia'       => 'Georgia,Palatino,serif',
    'Helvetica'     => 'Helvetica,Arial,sans-serif',
    'Impact'        => 'Impact,Chicago,sans-serif',
    'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
    'Terminal'      => 'Terminal,Monaco,monospace',
    'Times New Roman' => '"Times New Roman",Times,serif',
    'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
    'Verdana'       => 'Verdana,Geneva,sans-serif',
  );
    return rcube_ui::font_defs($font);
}
  if ($font)
    return $fonts[$font];
function send_nocacheing_headers()
{
    return rcmail::get_instance()->output->nocacheing_headers();
}
  return $fonts;
function show_bytes($bytes)
{
    return rcube_ui::show_bytes($bytes);
}
function rc_wordwrap($string, $width=75, $break="\n", $cut=false)
{
    return rcube_mime::wordwrap($string, $width, $break, $cut);
}
function rc_request_header($name)
{
    return rcube_request_header($name);
}
function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false)
{
    return rcube_mime::file_content_type($path, $name, $failover, $is_stream);
}
function rc_image_content_type($data)
{
    return rcube_mime::image_content_type($data);
}
program/include/rcmail.php
@@ -5,8 +5,8 @@
 | program/include/rcmail.php                                            |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2011, Kolab Systems AG                                  |
 | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -30,7 +30,7 @@
 *
 * @package Core
 */
class rcmail
class rcmail extends rcube
{
  /**
   * Main tasks.
@@ -38,76 +38,6 @@
   * @var array
   */
  static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
  /**
   * Singleton instace of rcmail
   *
   * @var rcmail
   */
  static private $instance;
  /**
   * Stores instance of rcube_config.
   *
   * @var rcube_config
   */
  public $config;
  /**
   * Stores rcube_user instance.
   *
   * @var rcube_user
   */
  public $user;
  /**
   * Instace of database class.
   *
   * @var rcube_mdb2
   */
  public $db;
  /**
   * Instace of Memcache class.
   *
   * @var rcube_mdb2
   */
  public $memcache;
  /**
   * Instace of rcube_session class.
   *
   * @var rcube_session
   */
  public $session;
  /**
   * Instance of rcube_smtp class.
   *
   * @var rcube_smtp
   */
  public $smtp;
  /**
   * Instance of rcube_storage class.
   *
   * @var rcube_storage
   */
  public $storage;
  /**
   * Instance of rcube_template class.
   *
   * @var rcube_template
   */
  public $output;
  /**
   * Instance of rcube_plugin_api.
   *
   * @var rcube_plugin_api
   */
  public $plugins;
  /**
   * Current task.
@@ -124,12 +54,8 @@
  public $action = '';
  public $comm_path = './';
  private $texts;
  private $address_books = array();
  private $caches = array();
  private $action_map = array();
  private $shutdown_functions = array();
  private $expunge_cache = false;
  /**
@@ -139,7 +65,7 @@
   */
  static function get_instance()
  {
    if (!self::$instance) {
    if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
      self::$instance = new rcmail();
      self::$instance->startup();  // init AFTER object was linked with self::$instance
    }
@@ -149,32 +75,12 @@
  /**
   * Private constructor
   */
  private function __construct()
  {
    // load configuration
    $this->config = new rcube_config();
    register_shutdown_function(array($this, 'shutdown'));
  }
  /**
   * Initial startup function
   * to register session, create database and imap connections
   */
  private function startup()
  protected function startup()
  {
    // initialize syslog
    if ($this->config->get('log_driver') == 'syslog') {
      $syslog_id = $this->config->get('syslog_id', 'roundcube');
      $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
      openlog($syslog_id, LOG_ODELAY, $syslog_facility);
    }
    // connect to database
    $this->get_dbh();
    $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS);
    // start session
    $this->session_init();
@@ -186,8 +92,8 @@
    $this->session_configure();
    // set task and action properties
    $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
    $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
    $this->set_task(rcube_ui::get_input_value('_task', rcube_ui::INPUT_GPC));
    $this->action = asciiwords(rcube_ui::get_input_value('_action', rcube_ui::INPUT_GPC));
    // reset some session parameters when changing task
    if ($this->task != 'utils') {
@@ -203,11 +109,9 @@
    else
      $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
    // create plugin API and load plugins
    $this->plugins = rcube_plugin_api::get_instance();
    // init plugins
    $this->plugins->init();
    // load plugins
    $this->plugins->init($this, $this->task);
    $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui'));
  }
@@ -259,144 +163,6 @@
  /**
   * Check the given string and return a valid language code
   *
   * @param string Language code
   * @return string Valid language code
   */
  private function language_prop($lang)
  {
    static $rcube_languages, $rcube_language_aliases;
    // 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]);
     }
    if (empty($rcube_languages)) {
      @include(INSTALL_PATH . 'program/localization/index.inc');
    }
    // check if we have an alias for that language
    if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
      $lang = $rcube_language_aliases[$lang];
    }
    // try the first two chars
    else if (!isset($rcube_languages[$lang])) {
      $short = substr($lang, 0, 2);
      // check if we have an alias for the short language code
      if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
        $lang = $rcube_language_aliases[$short];
      }
      // expand 'nn' to 'nn_NN'
      else if (!isset($rcube_languages[$short])) {
        $lang = $short.'_'.strtoupper($short);
      }
    }
    if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
      $lang = 'en_US';
    }
    return $lang;
  }
  /**
   * Get the current database connection
   *
   * @return rcube_mdb2  Database connection object
   */
  public function get_dbh()
  {
    if (!$this->db) {
      $config_all = $this->config->all();
      $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
      $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
      $this->db->set_debug((bool)$config_all['sql_debug']);
    }
    return $this->db;
  }
  /**
   * Get global handle for memcache access
   *
   * @return object Memcache
   */
  public function get_memcache()
  {
    if (!isset($this->memcache)) {
      // no memcache support in PHP
      if (!class_exists('Memcache')) {
        $this->memcache = false;
        return false;
      }
      $this->memcache = new Memcache;
      $this->mc_available = 0;
      // add alll configured hosts to pool
      $pconnect = $this->config->get('memcache_pconnect', true);
      foreach ($this->config->get('memcache_hosts', array()) as $host) {
        list($host, $port) = explode(':', $host);
        if (!$port) $port = 11211;
        $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
      }
      // test connection and failover (will result in $this->mc_available == 0 on complete failure)
      $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
      if (!$this->mc_available)
        $this->memcache = false;
    }
    return $this->memcache;
  }
  /**
   * Callback for memcache failure
   */
  public function memcache_failure($host, $port)
  {
    static $seen = array();
    // only report once
    if (!$seen["$host:$port"]++) {
      $this->mc_available--;
      raise_error(array('code' => 604, 'type' => 'db',
        'line' => __LINE__, 'file' => __FILE__,
        'message' => "Memcache failure on host $host:$port"),
        true, false);
    }
  }
  /**
   * Initialize and get cache object
   *
   * @param string $name   Cache identifier
   * @param string $type   Cache type ('db', 'apc' or 'memcache')
   * @param int    $ttl    Expiration time for cache items in seconds
   * @param bool   $packed Enables/disables data serialization
   *
   * @return rcube_cache Cache object
   */
  public function get_cache($name, $type='db', $ttl=0, $packed=true)
  {
    if (!isset($this->caches[$name])) {
      $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
    }
    return $this->caches[$name];
  }
  /**
   * Return instance of the internal address book class
   *
   * @param string  Address book identifier
@@ -425,7 +191,7 @@
      $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['storage_host']));
    }
    else if ($id === '0') {
      $contacts = new rcube_contacts($this->db, $this->user->ID);
      $contacts = new rcube_contacts($this->db, $this->get_user_id());
    }
    else {
      $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
@@ -446,7 +212,7 @@
    }
    if (!$contacts) {
      raise_error(array(
      self::raise_error(array(
        'code' => 700, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Addressbook source ($id) not found!"),
@@ -481,10 +247,10 @@
    // We are using the DB address book
    if ($abook_type != 'ldap') {
      if (!isset($this->address_books['0']))
        $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
        $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id());
      $list['0'] = array(
        'id'       => '0',
        'name'     => rcube_label('personaladrbook'),
        'name'     => $this->gettext('personaladrbook'),
        'groups'   => $this->address_books['0']->groups,
        'readonly' => $this->address_books['0']->readonly,
        'autocomplete' => in_array('sql', $autocomplete),
@@ -532,13 +298,13 @@
   * environment vars according to the current session and configuration
   *
   * @param boolean True if this request is loaded in a (i)frame
   * @return rcube_template Reference to HTML output object
   * @return rcube_output_html Reference to HTML output object
   */
  public function load_gui($framed = false)
  {
    // init output page
    if (!($this->output instanceof rcube_template))
      $this->output = new rcube_template($this->task, $framed);
    if (!($this->output instanceof rcube_output_html))
      $this->output = new rcube_output_html($this->task, $framed);
    // set keep-alive/check-recent interval
    if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
@@ -565,173 +331,14 @@
  /**
   * Create an output object for JSON responses
   *
   * @return rcube_json_output Reference to JSON output object
   * @return rcube_output_json Reference to JSON output object
   */
  public function json_init()
  {
    if (!($this->output instanceof rcube_json_output))
      $this->output = new rcube_json_output($this->task);
    if (!($this->output instanceof rcube_output_json))
      $this->output = new rcube_output_json($this->task);
    return $this->output;
  }
  /**
   * Create SMTP object and connect to server
   *
   * @param boolean True if connection should be established
   */
  public function smtp_init($connect = false)
  {
    $this->smtp = new rcube_smtp();
    if ($connect)
      $this->smtp->connect();
  }
  /**
   * Initialize and get storage object
   *
   * @return rcube_storage Storage object
   */
  public function get_storage()
  {
    // already initialized
    if (!is_object($this->storage)) {
      $this->storage_init();
    }
    return $this->storage;
  }
  /**
   * Connect to the IMAP server with stored session data.
   *
   * @return bool True on success, False on error
   * @deprecated
   */
  public function imap_connect()
  {
    return $this->storage_connect();
  }
  /**
   * Initialize IMAP object.
   *
   * @deprecated
   */
  public function imap_init()
  {
    $this->storage_init();
  }
  /**
   * Initialize storage object
   */
  public function storage_init()
  {
    // already initialized
    if (is_object($this->storage)) {
      return;
    }
    $driver = $this->config->get('storage_driver', 'imap');
    $driver_class = "rcube_{$driver}";
    if (!class_exists($driver_class)) {
      raise_error(array(
        'code' => 700, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Storage driver class ($driver) not found!"),
        true, true);
    }
    // Initialize storage object
    $this->storage = new $driver_class;
    // 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'),
      'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
      'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
      'debug'       => (bool) $this->config->get("{$driver}_debug"),
      'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
      'timeout'     => (int) $this->config->get("{$driver}_timeout"),
      'skip_deleted' => (bool) $this->config->get('skip_deleted'),
      'driver'      => $driver,
    );
    if (!empty($_SESSION['storage_host'])) {
      $options['host']     = $_SESSION['storage_host'];
      $options['user']     = $_SESSION['username'];
      $options['port']     = $_SESSION['storage_port'];
      $options['ssl']      = $_SESSION['storage_ssl'];
      $options['password'] = $this->decrypt($_SESSION['password']);
      // set 'imap_host' for backwards compatibility
      $_SESSION[$driver.'_host'] = &$_SESSION['storage_host'];
    }
    $options = $this->plugins->exec_hook("storage_init", $options);
    $this->storage->set_options($options);
    $this->set_storage_prop();
  }
  /**
   * Connect to the mail storage server with stored session data
   *
   * @return bool True on success, False on error
   */
  public function storage_connect()
  {
    $storage = $this->get_storage();
    if ($_SESSION['storage_host'] && !$storage->is_connected()) {
      $host = $_SESSION['storage_host'];
      $user = $_SESSION['username'];
      $port = $_SESSION['storage_port'];
      $ssl  = $_SESSION['storage_ssl'];
      $pass = $this->decrypt($_SESSION['password']);
      if (!$storage->connect($host, $user, $pass, $port, $ssl)) {
        if ($this->output)
          $this->output->show_message($storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror', 'error');
      }
      else {
        $this->set_storage_prop();
      }
    }
    return $storage->is_connected();
  }
@@ -757,7 +364,7 @@
      ini_set('session.gc_maxlifetime', $lifetime * 2);
    }
    ini_set('session.cookie_secure', rcube_https_check());
    ini_set('session.cookie_secure', rcube_ui::https_check());
    ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
    ini_set('session.use_cookies', 1);
    ini_set('session.use_only_cookies', 1);
@@ -766,7 +373,7 @@
    // use database for storing session data
    $this->session = new rcube_session($this->get_dbh(), $this->config);
    $this->session->register_gc_handler('rcmail_temp_gc');
    $this->session->register_gc_handler(array($this, 'temp_gc'));
    $this->session->register_gc_handler(array($this, 'cache_gc'));
    // start PHP session (if not in CLI mode)
@@ -842,7 +449,7 @@
      if (!$allowed)
        return false;
      }
    else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
    else if (!empty($config['default_host']) && $host != self::parse_host($config['default_host']))
      return false;
    // parse $host URL
@@ -866,9 +473,9 @@
    // Check if we need to add domain
    if (!empty($config['username_domain']) && strpos($username, '@') === false) {
      if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
        $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
        $username .= '@'.self::parse_host($config['username_domain'][$host], $host);
      else if (is_string($config['username_domain']))
        $username .= '@'.rcube_parse_host($config['username_domain'], $host);
        $username .= '@'.self::parse_host($config['username_domain'], $host);
    }
    // Convert username to lowercase. If storage backend
@@ -929,7 +536,7 @@
        $user = $created;
      }
      else {
        raise_error(array(
        self::raise_error(array(
          'code' => 620, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Failed to create a user record. Maybe aborted by a plugin?"
@@ -937,7 +544,7 @@
      }
    }
    else {
      raise_error(array(
      self::raise_error(array(
        'code' => 621, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
@@ -984,28 +591,6 @@
  /**
   * Set storage parameters.
   * This must be done AFTER connecting to the server!
   */
  private function set_storage_prop()
  {
    $storage = $this->get_storage();
    $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
    if ($default_folders = $this->config->get('default_folders')) {
      $storage->set_default_folders($default_folders);
    }
    if (isset($_SESSION['mbox'])) {
      $storage->set_folder($_SESSION['mbox']);
    }
    if (isset($_SESSION['page'])) {
      $storage->set_page($_SESSION['page']);
    }
  }
  /**
   * Auto-select IMAP host based on the posted login information
   *
   * @return string Selected IMAP host
@@ -1016,7 +601,7 @@
    $host = null;
    if (is_array($default_host)) {
      $post_host = get_input_value('_host', RCUBE_INPUT_POST);
      $post_host = rcube_ui::get_input_value('_host', rcube_ui::INPUT_POST);
      // direct match in default_host array
      if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
@@ -1024,7 +609,7 @@
      }
      // try to select host by mail domain
      list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
      list($user, $domain) = explode('@', rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST));
      if (!empty($domain)) {
        foreach ($default_host as $storage_host => $mail_domains) {
          if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
@@ -1045,174 +630,12 @@
      }
    }
    else if (empty($default_host)) {
      $host = get_input_value('_host', RCUBE_INPUT_POST);
      $host = rcube_ui::get_input_value('_host', rcube_ui::INPUT_POST);
    }
    else
      $host = rcube_parse_host($default_host);
      $host = self::parse_host($default_host);
    return $host;
  }
  /**
   * Get localized text in the desired language
   *
   * @param mixed   $attrib  Named parameters array or label name
   * @param string  $domain  Label domain (plugin) name
   *
   * @return string Localized text
   */
  public function gettext($attrib, $domain=null)
  {
    // load localization files if not done yet
    if (empty($this->texts))
      $this->load_language();
    // extract attributes
    if (is_string($attrib))
      $attrib = array('name' => $attrib);
    $name = $attrib['name'] ? $attrib['name'] : '';
    // attrib contain text values: use them from now
    if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
        $this->texts[$name] = $setval;
    // check for text with domain
    if ($domain && ($text = $this->texts[$domain.'.'.$name]))
      ;
    // text does not exist
    else if (!($text = $this->texts[$name])) {
      return "[$name]";
    }
    // replace vars in text
    if (is_array($attrib['vars'])) {
      foreach ($attrib['vars'] as $var_key => $var_value)
        $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
    }
    // format output
    if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
      return ucfirst($text);
    else if ($attrib['uppercase'])
      return mb_strtoupper($text);
    else if ($attrib['lowercase'])
      return mb_strtolower($text);
    return strtr($text, array('\n' => "\n"));
  }
  /**
   * Check if the given text label exists
   *
   * @param string  $name       Label name
   * @param string  $domain     Label domain (plugin) name or '*' for all domains
   * @param string  $ref_domain Sets domain name if label is found
   *
   * @return boolean True if text exists (either in the current language or in en_US)
   */
  public function text_exists($name, $domain = null, &$ref_domain = null)
  {
    // load localization files if not done yet
    if (empty($this->texts))
      $this->load_language();
    if (isset($this->texts[$name])) {
        $ref_domain = '';
        return true;
    }
    // any of loaded domains (plugins)
    if ($domain == '*') {
      foreach ($this->plugins->loaded_plugins() as $domain)
        if (isset($this->texts[$domain.'.'.$name])) {
          $ref_domain = $domain;
          return true;
        }
    }
    // specified domain
    else if ($domain) {
      $ref_domain = $domain;
      return isset($this->texts[$domain.'.'.$name]);
    }
    return false;
  }
  /**
   * Load a localization package
   *
   * @param string Language ID
   */
  public function load_language($lang = null, $add = array())
  {
    $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
    // load localized texts
    if (empty($this->texts) || $lang != $_SESSION['language']) {
      $this->texts = array();
      // handle empty lines after closing PHP tag in localization files
      ob_start();
      // get english labels (these should be complete)
      @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
      @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
      if (is_array($labels))
        $this->texts = $labels;
      if (is_array($messages))
        $this->texts = array_merge($this->texts, $messages);
      // include user language files
      if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
        if (is_array($labels))
          $this->texts = array_merge($this->texts, $labels);
        if (is_array($messages))
          $this->texts = array_merge($this->texts, $messages);
      }
      ob_end_clean();
      $_SESSION['language'] = $lang;
    }
    // append additional texts (from plugin)
    if (is_array($add) && !empty($add))
      $this->texts += $add;
  }
  /**
   * Read directory program/localization and return a list of available languages
   *
   * @return array List of available localizations
   */
  public function list_languages()
  {
    static $sa_languages = array();
    if (!sizeof($sa_languages)) {
      @include(INSTALL_PATH . 'program/localization/index.inc');
      if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
        while (($name = readdir($dh)) !== false) {
          if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
            continue;
          if ($label = $rcube_languages[$name])
            $sa_languages[$name] = $label;
        }
        closedir($dh);
      }
    }
    return $sa_languages;
  }
@@ -1260,68 +683,6 @@
  /**
   * Function to be executed in script shutdown
   * Registered with register_shutdown_function()
   */
  public function shutdown()
  {
    foreach ($this->shutdown_functions as $function)
      call_user_func($function);
    if (is_object($this->smtp))
      $this->smtp->disconnect();
    foreach ($this->address_books as $book) {
      if (is_object($book) && is_a($book, 'rcube_addressbook'))
        $book->close();
    }
    foreach ($this->caches as $cache) {
        if (is_object($cache))
            $cache->close();
    }
    if (is_object($this->storage)) {
        if ($this->expunge_cache)
            $this->storage->expunge_cache();
      $this->storage->close();
  }
    // before closing the database connection, write session data
    if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
      session_write_close();
    }
    // write performance stats to logs/console
    if ($this->config->get('devel_mode')) {
      if (function_exists('memory_get_usage'))
        $mem = show_bytes(memory_get_usage());
      if (function_exists('memory_get_peak_usage'))
        $mem .= '/'.show_bytes(memory_get_peak_usage());
      $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
      if (defined('RCMAIL_START'))
        rcube_print_time(RCMAIL_START, $log);
      else
        console($log);
    }
  }
  /**
   * Registers shutdown function to be executed on shutdown.
   * The functions will be executed before destroying any
   * objects like smtp, imap, session, etc.
   *
   * @param callback Function callback
   */
  public function add_shutdown_function($function)
  {
    $this->shutdown_functions[] = $function;
  }
  /**
   * Garbage collector for cache entries.
   * Set flag to expunge caches on shutdown
   */
@@ -1342,7 +703,7 @@
  {
    $sess_id = $_COOKIE[ini_get('session.name')];
    if (!$sess_id) $sess_id = session_id();
    $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->user->ID . $this->config->get('des_key') . $sess_id)));
    $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
    return $plugin['value'];
  }
@@ -1353,9 +714,9 @@
   * @param int Request method
   * @return boolean True if request token is valid false if not
   */
  public function check_request($mode = RCUBE_INPUT_POST)
  public function check_request($mode = rcube_ui::INPUT_POST)
  {
    $token = get_input_value('_token', $mode);
    $token = rcube_ui::get_input_value('_token', $mode);
    $sess_id = $_COOKIE[ini_get('session.name')];
    return !empty($sess_id) && $token == $this->get_request_token();
  }
@@ -1382,130 +743,6 @@
      return md5($auth_string);
  }
  /**
   * Encrypt using 3DES
   *
   * @param string $clear clear text input
   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
   * @param boolean $base64 whether or not to base64_encode() the result before returning
   *
   * @return string encrypted text
   */
  public function encrypt($clear, $key = 'des_key', $base64 = true)
  {
    if (!$clear)
      return '';
    /*-
     * Add a single canary byte to the end of the clear text, which
     * will help find out how much of padding will need to be removed
     * upon decryption; see http://php.net/mcrypt_generic#68082
     */
    $clear = pack("a*H2", $clear, "80");
    if (function_exists('mcrypt_module_open') &&
        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
    {
      $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
      $cipher = $iv . mcrypt_generic($td, $clear);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      @include_once 'des.inc';
      if (function_exists('des')) {
        $des_iv_size = 8;
        $iv = $this->create_iv($des_iv_size);
        $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
      }
      else {
        raise_error(array(
          'code' => 500, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
        ), true, true);
      }
    }
    return $base64 ? base64_encode($cipher) : $cipher;
  }
  /**
   * Decrypt 3DES-encrypted string
   *
   * @param string $cipher encrypted text
   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
   * @param boolean $base64 whether or not input is base64-encoded
   *
   * @return string decrypted text
   */
  public function decrypt($cipher, $key = 'des_key', $base64 = true)
  {
    if (!$cipher)
      return '';
    $cipher = $base64 ? base64_decode($cipher) : $cipher;
    if (function_exists('mcrypt_module_open') &&
        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
    {
      $iv_size = mcrypt_enc_get_iv_size($td);
      $iv = substr($cipher, 0, $iv_size);
      // session corruption? (#1485970)
      if (strlen($iv) < $iv_size)
        return '';
      $cipher = substr($cipher, $iv_size);
      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
      $clear = mdecrypt_generic($td, $cipher);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      @include_once 'des.inc';
      if (function_exists('des')) {
        $des_iv_size = 8;
        $iv = substr($cipher, 0, $des_iv_size);
        $cipher = substr($cipher, $des_iv_size);
        $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
      }
      else {
        raise_error(array(
          'code' => 500, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
        ), true, true);
      }
    }
    /*-
     * Trim PHP's padding and the canary byte; see note in
     * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
     */
    $clear = substr(rtrim($clear, "\0"), 0, -1);
    return $clear;
  }
  /**
   * Generates encryption initialization vector (IV)
   *
   * @param int Vector size
   * @return string Vector string
   */
  private function create_iv($size)
  {
    // mcrypt_create_iv() can be slow when system lacks entrophy
    // we'll generate IV vector manually
    $iv = '';
    for ($i = 0; $i < $size; $i++)
        $iv .= chr(mt_rand(0, 255));
    return $iv;
  }
  /**
   * Build a valid URL to this instance of Roundcube
@@ -1536,50 +773,36 @@
  /**
   * Construct shell command, execute it and return output as string.
   * Keywords {keyword} are replaced with arguments
   *
   * @param $cmd Format string with {keywords} to be replaced
   * @param $values (zero, one or more arrays can be passed)
   * @return output of command. shell errors not detectable
   * Function to be executed in script shutdown
   */
  public static function exec(/* $cmd, $values1 = array(), ... */)
  public function shutdown()
  {
    $args = func_get_args();
    $cmd = array_shift($args);
    $values = $replacements = array();
    parent::shutdown();
    // merge values into one array
    foreach ($args as $arg)
      $values += (array)$arg;
    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
    foreach ($matches as $tags) {
      list(, $tag, $option, $key) = $tags;
      $parts = array();
      if ($option) {
        foreach ((array)$values["-$key"] as $key => $value) {
          if ($value === true || $value === false || $value === null)
            $parts[] = $value ? $key : "";
          else foreach ((array)$value as $val)
            $parts[] = "$key " . escapeshellarg($val);
        }
      }
      else {
        foreach ((array)$values[$key] as $value)
          $parts[] = escapeshellarg($value);
      }
      $replacements[$tag] = join(" ", $parts);
    foreach ($this->address_books as $book) {
      if (is_object($book) && is_a($book, 'rcube_addressbook'))
        $book->close();
    }
    // use strtr behaviour of going through source string once
    $cmd = strtr($cmd, $replacements);
    // before closing the database connection, write session data
    if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
      session_write_close();
    }
    return (string)shell_exec($cmd);
    // write performance stats to logs/console
    if ($this->config->get('devel_mode')) {
      if (function_exists('memory_get_usage'))
        $mem = rcube_ui::show_bytes(memory_get_usage());
      if (function_exists('memory_get_peak_usage'))
        $mem .= '/'.rcube_ui::show_bytes(memory_get_peak_usage());
      $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
      if (defined('RCMAIL_START'))
        self::print_timer(RCMAIL_START, $log);
      else
        self::console($log);
    }
  }
  /**
   * Helper method to set a cookie with the current path and host settings
@@ -1596,7 +819,7 @@
    $cookie = session_get_cookie_params();
    setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
      rcube_https_check(), true);
      rcube_ui::https_check(), true);
  }
  /**
@@ -1727,4 +950,356 @@
    $this->set_storage_prop();
  }
    /**
     * Overwrite action variable
     *
     * @param string New action value
     */
    public function overwrite_action($action)
    {
        $this->action = $action;
        $this->output->set_env('action', $action);
    }
    /**
     * Send the given message using the configured method.
     *
     * @param object $message    Reference to Mail_MIME object
     * @param string $from       Sender address string
     * @param array  $mailto     Array of recipient address strings
     * @param array  $smtp_error SMTP error array (reference)
     * @param string $body_file  Location of file with saved message body (reference),
     *                           used when delay_file_io is enabled
     * @param array  $smtp_opts  SMTP options (e.g. DSN request)
     *
     * @return boolean Send status.
     */
    public function deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file = null, $smtp_opts = null)
    {
        $headers = $message->headers();
        // send thru SMTP server using custom SMTP library
        if ($this->config->get('smtp_server')) {
            // generate list of recipients
            $a_recipients = array($mailto);
            if (strlen($headers['Cc']))
                $a_recipients[] = $headers['Cc'];
            if (strlen($headers['Bcc']))
                $a_recipients[] = $headers['Bcc'];
            // clean Bcc from header for recipients
            $send_headers = $headers;
            unset($send_headers['Bcc']);
            // here too, it because txtHeaders() below use $message->_headers not only $send_headers
            unset($message->_headers['Bcc']);
            $smtp_headers = $message->txtHeaders($send_headers, true);
            if ($message->getParam('delay_file_io')) {
                // use common temp dir
                $temp_dir = $this->config->get('temp_dir');
                $body_file = tempnam($temp_dir, 'rcmMsg');
                if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
                    self::raise_error(array('code' => 650, 'type' => 'php',
                        'file' => __FILE__, 'line' => __LINE__,
                        'message' => "Could not create message: ".$mime_result->getMessage()),
                        TRUE, FALSE);
                    return false;
                }
                $msg_body = fopen($body_file, 'r');
            }
            else {
                $msg_body = $message->get();
            }
            // send message
            if (!is_object($this->smtp)) {
                $this->smtp_init(true);
            }
            $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
            $smtp_response = $this->smtp->get_response();
            $smtp_error = $this->smtp->get_error();
            // log error
            if (!$sent) {
                self::raise_error(array('code' => 800, 'type' => 'smtp',
                    'line' => __LINE__, 'file' => __FILE__,
                    'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
            }
        }
        // send mail using PHP's mail() function
        else {
            // unset some headers because they will be added by the mail() function
            $headers_enc = $message->headers($headers);
            $headers_php = $message->_headers;
            unset($headers_php['To'], $headers_php['Subject']);
            // reset stored headers and overwrite
            $message->_headers = array();
            $header_str = $message->txtHeaders($headers_php);
            // #1485779
            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
                    $headers_enc['To'] = implode(', ', $m[1]);
                }
            }
            $msg_body = $message->get();
            if (PEAR::isError($msg_body)) {
                self::raise_error(array('code' => 650, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Could not create message: ".$msg_body->getMessage()),
                    TRUE, FALSE);
            }
            else {
                $delim   = $this->config->header_delimiter();
                $to      = $headers_enc['To'];
                $subject = $headers_enc['Subject'];
                $header_str = rtrim($header_str);
                if ($delim != "\r\n") {
                    $header_str = str_replace("\r\n", $delim, $header_str);
                    $msg_body   = str_replace("\r\n", $delim, $msg_body);
                    $to         = str_replace("\r\n", $delim, $to);
                    $subject    = str_replace("\r\n", $delim, $subject);
                }
                if (ini_get('safe_mode'))
                    $sent = mail($to, $subject, $msg_body, $header_str);
                else
                    $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
            }
        }
        if ($sent) {
            $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
            // remove MDN headers after sending
            unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
            // get all recipients
            if ($headers['Cc'])
                $mailto .= $headers['Cc'];
            if ($headers['Bcc'])
                $mailto .= $headers['Bcc'];
            if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
                $mailto = implode(', ', array_unique($m[1]));
            if ($this->config->get('smtp_log')) {
                self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
                    $this->user->get_username(),
                    $_SERVER['REMOTE_ADDR'],
                    $mailto,
                    !empty($smtp_response) ? join('; ', $smtp_response) : ''));
            }
        }
        if (is_resource($msg_body)) {
            fclose($msg_body);
        }
        $message->_headers = array();
        $message->headers($headers);
        return $sent;
    }
    /**
     * Unique Message-ID generator.
     *
     * @return string Message-ID
     */
    public function gen_message_id()
    {
        $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
        $domain_part = $this->user->get_username('domain');
        // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
        if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
            foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
                $host = preg_replace('/:[0-9]+$/', '', $host);
                if ($host && preg_match('/\.[a-z]+$/i', $host)) {
                    $domain_part = $host;
                }
            }
        }
        return sprintf('<%s@%s>', $local_part, $domain_part);
    }
    /**
     * Returns RFC2822 formatted current date in user's timezone
     *
     * @return string Date
     */
    public function user_date()
    {
        // get user's timezone
        try {
            $tz   = new DateTimeZone($this->config->get('timezone'));
            $date = new DateTime('now', $tz);
        }
        catch (Exception $e) {
            $date = new DateTime();
        }
        return $date->format('r');
    }
    /**
     * E-mail address validation.
     *
     * @param string $email Email address
     * @param boolean $dns_check True to check dns
     *
     * @return boolean True on success, False if address is invalid
     */
    public function check_email($email, $dns_check=true)
    {
        // Check for invalid characters
        if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email)) {
            return false;
        }
        // Check for length limit specified by RFC 5321 (#1486453)
        if (strlen($email) > 254) {
            return false;
        }
        $email_array = explode('@', $email);
        // Check that there's one @ symbol
        if (count($email_array) < 2) {
            return false;
        }
        $domain_part = array_pop($email_array);
        $local_part  = implode('@', $email_array);
        // from PEAR::Validate
        $regexp = '&^(?:
            ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                              #1 quoted name
            ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))     #2 OR dot-atom (RFC5322)
            $&xi';
        if (!preg_match($regexp, $local_part)) {
            return false;
        }
        // Check domain part
        if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) {
            return true; // IP address
        }
        else {
            // If not an IP address
            $domain_array = explode('.', $domain_part);
            // Not enough parts to be a valid domain
            if (sizeof($domain_array) < 2) {
                return false;
            }
            foreach ($domain_array as $part) {
                if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
                    return false;
                }
            }
            if (!$dns_check || !$this->config->get('email_dns_check')) {
                return true;
            }
            if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
                $lookup = array();
                @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
                foreach ($lookup as $line) {
                    if (strpos($line, 'MX preference')) {
                        return true;
                    }
                }
                return false;
            }
            // find MX record(s)
            if (getmxrr($domain_part, $mx_records)) {
                return true;
            }
            // find any DNS record
            if (checkdnsrr($domain_part, 'ANY')) {
                return true;
            }
        }
        return false;
    }
    /**
     * Write login data (name, ID, IP address) to the 'userlogins' log file.
     */
    public function log_login()
    {
        if (!$this->config->get('log_logins')) {
            return;
        }
        $user_name = $this->get_user_name();
        $user_id   = $this->get_user_id();
        if (!$user_id) {
            return;
        }
        self::write_log('userlogins',
            sprintf('Successful login for %s (ID: %d) from %s in session %s',
                $user_name, $user_id, self::remote_ip(), session_id()));
    }
    /**
     * Check whether the HTTP referer matches the current request
     *
     * @return boolean True if referer is the same host+path, false if not
     */
    public static function check_referer()
    {
        $uri = parse_url($_SERVER['REQUEST_URI']);
        $referer = parse_url(rcube_request_header('Referer'));
        return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path'];
    }
    /**
     * Garbage collector function for temp files.
     * Remove temp files older than two days
     */
    public function temp_gc()
    {
        $tmp = unslashify($this->config->get('temp_dir'));
        $expire = mktime() - 172800;  // expire in 48 hours
        if ($dir = opendir($tmp)) {
            while (($fname = readdir($dir)) !== false) {
                if ($fname{0} == '.') {
                    continue;
                }
                if (filemtime($tmp.'/'.$fname) < $expire) {
                    @unlink($tmp.'/'.$fname);
                }
            }
            closedir($dir);
        }
    }
}
program/include/rcube.php
New file
@@ -0,0 +1,1226 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/include/rcmail.php                                            |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Framework base class providing core functions and holding           |
 |   instances of all 'global' objects like db- and storage-connections  |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 +-----------------------------------------------------------------------+
 $Id$
*/
/**
 * Base class of the Roundcube Framework
 * implemented as singleton
 *
 * @package Core
 */
class rcube
{
  const INIT_WITH_DB = 1;
  const INIT_WITH_PLUGINS = 2;
  /**
   * Singleton instace of rcmail
   *
   * @var rcmail
   */
  static protected $instance;
  /**
   * Stores instance of rcube_config.
   *
   * @var rcube_config
   */
  public $config;
  /**
   * Instace of database class.
   *
   * @var rcube_mdb2
   */
  public $db;
  /**
   * Instace of Memcache class.
   *
   * @var rcube_mdb2
   */
  public $memcache;
  /**
   * Instace of rcube_session class.
   *
   * @var rcube_session
   */
  public $session;
  /**
   * Instance of rcube_smtp class.
   *
   * @var rcube_smtp
   */
  public $smtp;
  /**
   * Instance of rcube_storage class.
   *
   * @var rcube_storage
   */
  public $storage;
  /**
   * Instance of rcube_output class.
   *
   * @var rcube_output
   */
  public $output;
  /**
   * Instance of rcube_plugin_api.
   *
   * @var rcube_plugin_api
   */
  public $plugins;
  /* private/protected vars */
  protected $texts;
  protected $caches = array();
  protected $shutdown_functions = array();
  protected $expunge_cache = false;
  /**
   * This implements the 'singleton' design pattern
   *
   * @return rcmail The one and only instance
   */
  static function get_instance()
  {
    if (!self::$instance) {
      self::$instance = new rcube();
    }
    return self::$instance;
  }
  /**
   * Private constructor
   */
  protected function __construct()
  {
    // load configuration
    $this->config = new rcube_config();
    $this->plugins = new rcube_dummy_plugin_api;
    register_shutdown_function(array($this, 'shutdown'));
  }
  /**
   * Initial startup function
   */
  protected function init($mode = 0)
  {
    // initialize syslog
    if ($this->config->get('log_driver') == 'syslog') {
      $syslog_id = $this->config->get('syslog_id', 'roundcube');
      $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
      openlog($syslog_id, LOG_ODELAY, $syslog_facility);
    }
    // connect to database
    if ($mode & self::INIT_WITH_DB) {
      $this->get_dbh();
    }
    // create plugin API and load plugins
    if ($mode & self::INIT_WITH_PLUGINS) {
      $this->plugins = rcube_plugin_api::get_instance();
    }
  }
  /**
   * Get the current database connection
   *
   * @return rcube_mdb2  Database connection object
   */
  public function get_dbh()
  {
    if (!$this->db) {
      $config_all = $this->config->all();
      $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
      $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
      $this->db->set_debug((bool)$config_all['sql_debug']);
    }
    return $this->db;
  }
  /**
   * Get global handle for memcache access
   *
   * @return object Memcache
   */
  public function get_memcache()
  {
    if (!isset($this->memcache)) {
      // no memcache support in PHP
      if (!class_exists('Memcache')) {
        $this->memcache = false;
        return false;
      }
      $this->memcache = new Memcache;
      $this->mc_available = 0;
      // add alll configured hosts to pool
      $pconnect = $this->config->get('memcache_pconnect', true);
      foreach ($this->config->get('memcache_hosts', array()) as $host) {
        list($host, $port) = explode(':', $host);
        if (!$port) $port = 11211;
        $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
      }
      // test connection and failover (will result in $this->mc_available == 0 on complete failure)
      $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
      if (!$this->mc_available)
        $this->memcache = false;
    }
    return $this->memcache;
  }
  /**
   * Callback for memcache failure
   */
  public function memcache_failure($host, $port)
  {
    static $seen = array();
    // only report once
    if (!$seen["$host:$port"]++) {
      $this->mc_available--;
      self::raise_error(array('code' => 604, 'type' => 'db',
        'line' => __LINE__, 'file' => __FILE__,
        'message' => "Memcache failure on host $host:$port"),
        true, false);
    }
  }
  /**
   * Initialize and get cache object
   *
   * @param string $name   Cache identifier
   * @param string $type   Cache type ('db', 'apc' or 'memcache')
   * @param int    $ttl    Expiration time for cache items in seconds
   * @param bool   $packed Enables/disables data serialization
   *
   * @return rcube_cache Cache object
   */
  public function get_cache($name, $type='db', $ttl=0, $packed=true)
  {
    if (!isset($this->caches[$name])) {
      $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
    }
    return $this->caches[$name];
  }
  /**
   * Create SMTP object and connect to server
   *
   * @param boolean True if connection should be established
   */
  public function smtp_init($connect = false)
  {
    $this->smtp = new rcube_smtp();
    if ($connect)
      $this->smtp->connect();
  }
  /**
   * Initialize and get storage object
   *
   * @return rcube_storage Storage object
   */
  public function get_storage()
  {
    // already initialized
    if (!is_object($this->storage)) {
      $this->storage_init();
    }
    return $this->storage;
  }
  /**
   * Initialize storage object
   */
  public function storage_init()
  {
    // already initialized
    if (is_object($this->storage)) {
      return;
    }
    $driver = $this->config->get('storage_driver', 'imap');
    $driver_class = "rcube_{$driver}";
    if (!class_exists($driver_class)) {
      self::raise_error(array(
        'code' => 700, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Storage driver class ($driver) not found!"),
        true, true);
    }
    // Initialize storage object
    $this->storage = new $driver_class;
    // 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'),
      'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
      'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
      'debug'       => (bool) $this->config->get("{$driver}_debug"),
      'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
      'timeout'     => (int) $this->config->get("{$driver}_timeout"),
      'skip_deleted' => (bool) $this->config->get('skip_deleted'),
      'driver'      => $driver,
    );
    if (!empty($_SESSION['storage_host'])) {
      $options['host']     = $_SESSION['storage_host'];
      $options['user']     = $_SESSION['username'];
      $options['port']     = $_SESSION['storage_port'];
      $options['ssl']      = $_SESSION['storage_ssl'];
      $options['password'] = $this->decrypt($_SESSION['password']);
    }
    $options = $this->plugins->exec_hook("storage_init", $options);
    // for backward compat. (deprecated, to be removed)
    $options = $this->plugins->exec_hook("imap_init", $options);
    $this->storage->set_options($options);
    $this->set_storage_prop();
  }
  /**
   * Connect to the mail storage server with stored session data
   *
   * @return bool True on success, False on error
   */
  public function storage_connect()
  {
    $storage = $this->get_storage();
    if ($_SESSION['storage_host'] && !$storage->is_connected()) {
      $host = $_SESSION['storage_host'];
      $user = $_SESSION['username'];
      $port = $_SESSION['storage_port'];
      $ssl  = $_SESSION['storage_ssl'];
      $pass = $this->decrypt($_SESSION['password']);
      if (!$storage->connect($host, $user, $pass, $port, $ssl)) {
        if (is_object($this->output))
          $this->output->show_message($storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror', 'error');
      }
      else {
        $this->set_storage_prop();
        return $storage->is_connected();
      }
    }
    return false;
  }
  /**
   * Set storage parameters.
   * This must be done AFTER connecting to the server!
   */
  protected function set_storage_prop()
  {
    $storage = $this->get_storage();
    $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
    if ($default_folders = $this->config->get('default_folders')) {
      $storage->set_default_folders($default_folders);
    }
    if (isset($_SESSION['mbox'])) {
      $storage->set_folder($_SESSION['mbox']);
    }
    if (isset($_SESSION['page'])) {
      $storage->set_page($_SESSION['page']);
    }
  }
  /**
   * Get localized text in the desired language
   *
   * @param mixed   $attrib  Named parameters array or label name
   * @param string  $domain  Label domain (plugin) name
   *
   * @return string Localized text
   */
  public function gettext($attrib, $domain=null)
  {
    // load localization files if not done yet
    if (empty($this->texts))
      $this->load_language();
    // extract attributes
    if (is_string($attrib))
      $attrib = array('name' => $attrib);
    $name = $attrib['name'] ? $attrib['name'] : '';
    // attrib contain text values: use them from now
    if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
        $this->texts[$name] = $setval;
    // check for text with domain
    if ($domain && ($text = $this->texts[$domain.'.'.$name]))
      ;
    // text does not exist
    else if (!($text = $this->texts[$name])) {
      return "[$name]";
    }
    // replace vars in text
    if (is_array($attrib['vars'])) {
      foreach ($attrib['vars'] as $var_key => $var_value)
        $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
    }
    // format output
    if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
      return ucfirst($text);
    else if ($attrib['uppercase'])
      return mb_strtoupper($text);
    else if ($attrib['lowercase'])
      return mb_strtolower($text);
    return strtr($text, array('\n' => "\n"));
  }
  /**
   * Check if the given text label exists
   *
   * @param string  $name       Label name
   * @param string  $domain     Label domain (plugin) name or '*' for all domains
   * @param string  $ref_domain Sets domain name if label is found
   *
   * @return boolean True if text exists (either in the current language or in en_US)
   */
  public function text_exists($name, $domain = null, &$ref_domain = null)
  {
    // load localization files if not done yet
    if (empty($this->texts))
      $this->load_language();
    if (isset($this->texts[$name])) {
        $ref_domain = '';
        return true;
    }
    // any of loaded domains (plugins)
    if ($domain == '*') {
      foreach ($this->plugins->loaded_plugins() as $domain)
        if (isset($this->texts[$domain.'.'.$name])) {
          $ref_domain = $domain;
          return true;
        }
    }
    // specified domain
    else if ($domain) {
      $ref_domain = $domain;
      return isset($this->texts[$domain.'.'.$name]);
    }
    return false;
  }
  /**
   * Load a localization package
   *
   * @param string Language ID
   */
  public function load_language($lang = null, $add = array())
  {
    $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
    // load localized texts
    if (empty($this->texts) || $lang != $_SESSION['language']) {
      $this->texts = array();
      // handle empty lines after closing PHP tag in localization files
      ob_start();
      // get english labels (these should be complete)
      @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
      @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
      if (is_array($labels))
        $this->texts = $labels;
      if (is_array($messages))
        $this->texts = array_merge($this->texts, $messages);
      // include user language files
      if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
        if (is_array($labels))
          $this->texts = array_merge($this->texts, $labels);
        if (is_array($messages))
          $this->texts = array_merge($this->texts, $messages);
      }
      ob_end_clean();
      $_SESSION['language'] = $lang;
    }
    // append additional texts (from plugin)
    if (is_array($add) && !empty($add))
      $this->texts += $add;
  }
  /**
   * Check the given string and return a valid language code
   *
   * @param string Language code
   * @return string Valid language code
   */
  protected function language_prop($lang)
  {
    static $rcube_languages, $rcube_language_aliases;
    // 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]);
     }
    if (empty($rcube_languages)) {
      @include(INSTALL_PATH . 'program/localization/index.inc');
    }
    // check if we have an alias for that language
    if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
      $lang = $rcube_language_aliases[$lang];
    }
    // try the first two chars
    else if (!isset($rcube_languages[$lang])) {
      $short = substr($lang, 0, 2);
      // check if we have an alias for the short language code
      if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
        $lang = $rcube_language_aliases[$short];
      }
      // expand 'nn' to 'nn_NN'
      else if (!isset($rcube_languages[$short])) {
        $lang = $short.'_'.strtoupper($short);
      }
    }
    if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
      $lang = 'en_US';
    }
    return $lang;
  }
  /**
   * Read directory program/localization and return a list of available languages
   *
   * @return array List of available localizations
   */
  public function list_languages()
  {
    static $sa_languages = array();
    if (!sizeof($sa_languages)) {
      @include(INSTALL_PATH . 'program/localization/index.inc');
      if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
        while (($name = readdir($dh)) !== false) {
          if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
            continue;
          if ($label = $rcube_languages[$name])
            $sa_languages[$name] = $label;
        }
        closedir($dh);
      }
    }
    return $sa_languages;
  }
  /**
   * Encrypt using 3DES
   *
   * @param string $clear clear text input
   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
   * @param boolean $base64 whether or not to base64_encode() the result before returning
   *
   * @return string encrypted text
   */
  public function encrypt($clear, $key = 'des_key', $base64 = true)
  {
    if (!$clear)
      return '';
    /*-
     * Add a single canary byte to the end of the clear text, which
     * will help find out how much of padding will need to be removed
     * upon decryption; see http://php.net/mcrypt_generic#68082
     */
    $clear = pack("a*H2", $clear, "80");
    if (function_exists('mcrypt_module_open') &&
        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
      $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
      $cipher = $iv . mcrypt_generic($td, $clear);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      @include_once 'des.inc';
      if (function_exists('des')) {
        $des_iv_size = 8;
        $iv = $this->create_iv($des_iv_size);
        $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
      }
      else {
        self::raise_error(array(
          'code' => 500, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
        ), true, true);
      }
    }
    return $base64 ? base64_encode($cipher) : $cipher;
  }
  /**
   * Decrypt 3DES-encrypted string
   *
   * @param string $cipher encrypted text
   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
   * @param boolean $base64 whether or not input is base64-encoded
   *
   * @return string decrypted text
   */
  public function decrypt($cipher, $key = 'des_key', $base64 = true)
  {
    if (!$cipher)
      return '';
    $cipher = $base64 ? base64_decode($cipher) : $cipher;
    if (function_exists('mcrypt_module_open') &&
        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
      $iv_size = mcrypt_enc_get_iv_size($td);
      $iv = substr($cipher, 0, $iv_size);
      // session corruption? (#1485970)
      if (strlen($iv) < $iv_size)
        return '';
      $cipher = substr($cipher, $iv_size);
      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
      $clear = mdecrypt_generic($td, $cipher);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      @include_once 'des.inc';
      if (function_exists('des')) {
        $des_iv_size = 8;
        $iv = substr($cipher, 0, $des_iv_size);
        $cipher = substr($cipher, $des_iv_size);
        $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
      }
      else {
        self::raise_error(array(
          'code' => 500, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
        ), true, true);
      }
    }
    /*-
     * Trim PHP's padding and the canary byte; see note in
     * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
     */
    $clear = substr(rtrim($clear, "\0"), 0, -1);
    return $clear;
  }
  /**
   * Generates encryption initialization vector (IV)
   *
   * @param int Vector size
   * @return string Vector string
   */
  private function create_iv($size)
  {
    // mcrypt_create_iv() can be slow when system lacks entrophy
    // we'll generate IV vector manually
    $iv = '';
    for ($i = 0; $i < $size; $i++)
        $iv .= chr(mt_rand(0, 255));
    return $iv;
  }
  /**
   * Build a valid URL to this instance of Roundcube
   *
   * @param mixed Either a string with the action or url parameters as key-value pairs
   * @return string Valid application URL
   */
  public function url($p)
  {
      // STUB: should be overloaded by the application
      return '';
  }
  /**
   * Function to be executed in script shutdown
   * Registered with register_shutdown_function()
   */
  public function shutdown()
  {
    foreach ($this->shutdown_functions as $function)
      call_user_func($function);
    if (is_object($this->smtp))
      $this->smtp->disconnect();
    foreach ($this->caches as $cache) {
        if (is_object($cache))
            $cache->close();
    }
    if (is_object($this->storage)) {
      if ($this->expunge_cache)
        $this->storage->expunge_cache();
      $this->storage->close();
    }
  }
  /**
   * Registers shutdown function to be executed on shutdown.
   * The functions will be executed before destroying any
   * objects like smtp, imap, session, etc.
   *
   * @param callback Function callback
   */
  public function add_shutdown_function($function)
  {
    $this->shutdown_functions[] = $function;
  }
  /**
   * Use imagemagick or GD lib to read image properties
   *
   * @param string Absolute file path
   * @return mixed Hash array with image props like type, width, height or False on error
   */
  public static function imageprops($filepath)
  {
    $rcube = self::get_instance();
    if ($cmd = $rcube->config->get('im_identify_path', false)) {
      list(, $type, $size) = explode(' ', strtolower(self::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
      if ($size)
        list($width, $height) = explode('x', $size);
    }
    else if (function_exists('getimagesize')) {
      $imsize = @getimagesize($filepath);
      $width = $imsize[0];
      $height = $imsize[1];
      $type = preg_replace('!image/!', '', $imsize['mime']);
    }
    return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
  }
  /**
   * Convert an image to a given size and type using imagemagick (ensures input is an image)
   *
   * @param $p['in']  Input filename (mandatory)
   * @param $p['out'] Output filename (mandatory)
   * @param $p['size']  Width x height of resulting image, e.g. "160x60"
   * @param $p['type']  Output file type, e.g. "jpg"
   * @param $p['-opts'] Custom command line options to ImageMagick convert
   * @return Success of convert as true/false
   */
  public static function imageconvert($p)
  {
    $result = false;
    $rcube = self::get_instance();
    $convert  = $rcube->config->get('im_convert_path', false);
    $identify = $rcube->config->get('im_identify_path', false);
    // imagemagick is required for this
    if (!$convert)
        return false;
    if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
      list(, $type) = explode(' ', strtolower(self::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
    $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
    $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
    $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
    if (in_array($type, explode(',', $p['types']))) # Valid type?
      $result = self::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
    return $result;
  }
  /**
   * Construct shell command, execute it and return output as string.
   * Keywords {keyword} are replaced with arguments
   *
   * @param $cmd Format string with {keywords} to be replaced
   * @param $values (zero, one or more arrays can be passed)
   * @return output of command. shell errors not detectable
   */
  public static function exec(/* $cmd, $values1 = array(), ... */)
  {
    $args = func_get_args();
    $cmd = array_shift($args);
    $values = $replacements = array();
    // merge values into one array
    foreach ($args as $arg)
      $values += (array)$arg;
    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
    foreach ($matches as $tags) {
      list(, $tag, $option, $key) = $tags;
      $parts = array();
      if ($option) {
        foreach ((array)$values["-$key"] as $key => $value) {
          if ($value === true || $value === false || $value === null)
            $parts[] = $value ? $key : "";
          else foreach ((array)$value as $val)
            $parts[] = "$key " . escapeshellarg($val);
        }
      }
      else {
        foreach ((array)$values[$key] as $value)
          $parts[] = escapeshellarg($value);
      }
      $replacements[$tag] = join(" ", $parts);
    }
    // use strtr behaviour of going through source string once
    $cmd = strtr($cmd, $replacements);
    return (string)shell_exec($cmd);
  }
    /**
     * Replaces hostname variables.
     *
     * @param string $name Hostname
     * @param string $host Optional IMAP hostname
     *
     * @return string Hostname
     */
    public static function parse_host($name, $host = '')
    {
        // %n - host
        $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
        // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
        $d = preg_replace('/^[^\.]+\./', '', $n);
        // %h - IMAP host
        $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
        // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
        $z = preg_replace('/^[^\.]+\./', '', $h);
        // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
        if (strpos($name, '%s') !== false) {
            $user_email = rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST);
            $user_email = rcube_idn_convert($user_email, true);
            $matches    = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
            if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
                return false;
            }
        }
        $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
        return $name;
    }
    /**
     * Print or write debug messages
     *
     * @param mixed Debug message or data
     */
    public static function console()
    {
        $args = func_get_args();
        if (class_exists('rcmail', false)) {
            $rcube = self::get_instance();
            if (is_object($rcube->plugins)) {
                $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
                if ($plugin['abort']) {
                    return;
                }
               $args = $plugin['args'];
            }
        }
        $msg = array();
        foreach ($args as $arg) {
            $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
        }
        self::write_log('console', join(";\n", $msg));
    }
    /**
     * Append a line to a logfile in the logs directory.
     * Date will be added automatically to the line.
     *
     * @param $name name of log file
     * @param line Line to append
     */
    public static function write_log($name, $line)
    {
        if (!is_string($line)) {
            $line = var_export($line, true);
        }
        $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
        $log_driver  = self::$instance ? self::$instance->config->get('log_driver') : null;
        if (empty($date_format)) {
            $date_format = 'd-M-Y H:i:s O';
        }
        $date = date($date_format);
        // trigger logging hook
        if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
            $log  = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
            $name = $log['name'];
            $line = $log['line'];
            $date = $log['date'];
            if ($log['abort'])
                return true;
        }
        if ($log_driver == 'syslog') {
            $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
            syslog($prio, $line);
            return true;
        }
        // log_driver == 'file' is assumed here
        $line = sprintf("[%s]: %s\n", $date, $line);
        $log_dir  = self::$instance ? self::$instance->config->get('log_dir') : null;
        if (empty($log_dir)) {
            $log_dir = INSTALL_PATH . 'logs';
        }
        // try to open specific log file for writing
        $logfile = $log_dir.'/'.$name;
        if ($fp = @fopen($logfile, 'a')) {
            fwrite($fp, $line);
            fflush($fp);
            fclose($fp);
            return true;
        }
        trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
        return false;
    }
    /**
     * Throw system error (and show error page).
     *
     * @param array Named parameters
     *      - 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
     * @param boolean True to log the error
     * @param boolean Terminate script execution
     */
    public static function raise_error($arg = array(), $log = false, $terminate = false)
    {
        // installer
        if (class_exists('rcube_install', false)) {
            $rci = rcube_install::get_instance();
            $rci->raise_error($arg);
            return;
        }
        if ($log && $arg['type'] && $arg['message']) {
            self::log_bug($arg);
        }
        // display error page and terminate script
        if ($terminate && is_object(self::$instance->output)) {
            self::$instance->output->raise_error($arg['code'], $arg['message']);
        }
    }
    /**
     * Report error according to configured debug_level
     *
     * @param array Named parameters
     * @see self::raise_error()
     */
    public static function log_bug($arg_arr)
    {
        $program = strtoupper($arg_arr['type']);
        $level   = self::get_instance()->config->get('debug_level');
        // disable errors for ajax requests, write to log instead (#1487831)
        if (($level & 4) && !empty($_REQUEST['_remote'])) {
            $level = ($level ^ 4) | 1;
        }
        // write error to local log file
        if ($level & 1) {
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
                $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
            }
            else {
                $post_query = '';
            }
            $log_entry = sprintf("%s Error: %s%s (%s %s)",
                $program,
                $arg_arr['message'],
                $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
                $_SERVER['REQUEST_METHOD'],
                $_SERVER['REQUEST_URI'] . $post_query);
            if (!self::write_log('errors', $log_entry)) {
                // send error to PHPs error handler if write_log didn't succeed
                trigger_error($arg_arr['message']);
            }
        }
        // report the bug to the global bug reporting system
        if ($level & 2) {
            // TODO: Send error via HTTP
        }
        // show error if debug_mode is on
        if ($level & 4) {
            print "<b>$program Error";
            if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
                print " in $arg_arr[file] ($arg_arr[line])";
            }
            print ':</b>&nbsp;';
            print nl2br($arg_arr['message']);
            print '<br />';
            flush();
        }
    }
    /**
     * Returns remote IP address and forwarded addresses if found
     *
     * @return string Remote IP address(es)
     */
    public static function remote_ip()
    {
        $address = $_SERVER['REMOTE_ADDR'];
        // append the NGINX X-Real-IP header, if set
        if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
            $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
        }
        // append the X-Forwarded-For header, if set
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        if (!empty($remote_ip)) {
            $address .= '(' . implode(',', $remote_ip) . ')';
        }
        return $address;
    }
    /**
     * Returns current time (with microseconds).
     *
     * @return float Current time in seconds since the Unix
     */
    public static function timer()
    {
        return microtime(true);
    }
    /**
     * Logs time difference according to provided timer
     *
     * @param float  $timer  Timer (self::timer() result)
     * @param string $label  Log line prefix
     * @param string $dest   Log file name
     *
     * @see self::timer()
     */
    public static function print_timer($timer, $label = 'Timer', $dest = 'console')
    {
        static $print_count = 0;
        $print_count++;
        $now  = self::timer();
        $diff = $now - $timer;
        if (empty($label)) {
            $label = 'Timer '.$print_count;
        }
        self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
    }
    /**
     * Getter for logged user ID.
     *
     * @return mixed User identifier
     */
    public function get_user_id()
    {
        if (is_object($this->user)) {
            return $this->user->ID;
        }
        return null;
    }
    /**
     * Getter for logged user name.
     *
     * @return string User name
     */
    public function get_user_name()
    {
        if (is_object($this->user)) {
            return $this->user->get_username();
        }
        return null;
    }
}
/**
 * Lightweight plugin API class serving as a dummy if plugins are not enabled
 *
 * @package Core
 */
class rcube_dummy_plugin_api
{
    /**
     * Triggers a plugin hook.
     * @see rcube_plugin_api::exec_hook()
     */
    public function exec_hook($hook, $args = array())
    {
        return $args;
    }
}
program/include/rcube_addressbook.php
@@ -211,11 +211,14 @@
     */
    public function validate(&$save_data, $autofix = false)
    {
        $rcmail = rcmail::get_instance();
        // check validity of email addresses
        foreach ($this->get_col_values('email', $save_data, true) as $email) {
            if (strlen($email)) {
                if (!check_email(rcube_idn_to_ascii($email))) {
                    $this->set_error(self::ERROR_VALIDATE, rcube_label(array('name' => 'emailformaterror', 'vars' => array('email' => $email))));
                if (!$rcmail->check_email(rcube_idn_to_ascii($email))) {
                    $error = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
                    $this->set_error(self::ERROR_VALIDATE, $error);
                    return false;
                }
            }
program/include/rcube_base_replacer.php
New file
@@ -0,0 +1,110 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_base_replacer.php                               |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Provide basic functions for base URL replacement                    |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 +-----------------------------------------------------------------------+
 $Id$
*/
/**
 * Helper class to turn relative urls into absolute ones
 * using a predefined base
 *
 * @package Core
 * @author Thomas Bruederli <roundcube@gmail.com>
 */
class rcube_base_replacer
{
    private $base_url;
    public function __construct($base)
    {
        $this->base_url = $base;
    }
    public function callback($matches)
    {
        return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
    }
    public function replace($body)
    {
        return preg_replace_callback(array(
            '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
            '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
        ),
        array($this, 'callback'), $body);
    }
    /**
     * Convert paths like ../xxx to an absolute path using a base url
     *
     * @param string $path     Relative path
     * @param string $base_url Base URL
     *
     * @return string Absolute URL
     */
    public static function absolute_url($path, $base_url)
    {
        $host_url = $base_url;
        $abs_path = $path;
        // check if path is an absolute URL
        if (preg_match('/^[fhtps]+:\/\//', $path)) {
            return $path;
        }
        // check if path is a content-id scheme
        if (strpos($path, 'cid:') === 0) {
            return $path;
        }
        // cut base_url to the last directory
        if (strrpos($base_url, '/') > 7) {
            $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
        }
        // $path is absolute
        if ($path[0] == '/') {
            $abs_path = $host_url.$path;
        }
        else {
            // strip './' because its the same as ''
            $path = preg_replace('/^\.\//', '', $path);
            if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
                foreach ($matches as $a_match) {
                    if (strrpos($base_url, '/')) {
                        $base_url = substr($base_url, 0, strrpos($base_url, '/'));
                    }
                    $path = substr($path, 3);
                }
            }
            $abs_path = $base_url.'/'.$path;
        }
        return $abs_path;
    }
}
program/include/rcube_cache.php
@@ -66,7 +66,7 @@
     */
    function __construct($type, $userid, $prefix='', $ttl=0, $packed=true)
    {
        $rcmail = rcmail::get_instance();
        $rcmail = rcube::get_instance();
        $type   = strtolower($type);
        if ($type == 'memcache') {
@@ -197,7 +197,7 @@
    {
        if ($this->type == 'db' && $this->db) {
            $this->db->query(
                "DELETE FROM ".get_table_name('cache').
                "DELETE FROM ".$this->db->table_name('cache').
                " WHERE user_id = ?".
                " AND cache_key LIKE ?".
                " AND " . $this->db->unixtimestamp('created')." < ?",
@@ -274,7 +274,7 @@
        else {
            $sql_result = $this->db->limitquery(
                "SELECT cache_id, data, cache_key".
                " FROM ".get_table_name('cache').
                " FROM ".$this->db->table_name('cache').
                " WHERE user_id = ?".
                " AND cache_key = ?".
                // for better performance we allow more records for one key
@@ -330,7 +330,7 @@
        // Remove NULL rows (here we don't need to check if the record exist)
        if ($data == 'N;') {
            $this->db->query(
                "DELETE FROM ".get_table_name('cache').
                "DELETE FROM ".$this->db->table_name('cache').
                " WHERE user_id = ?".
                " AND cache_key = ?",
                $this->userid, $key);
@@ -341,7 +341,7 @@
        // update existing cache record
        if ($key_exists) {
            $result = $this->db->query(
                "UPDATE ".get_table_name('cache').
                "UPDATE ".$this->db->table_name('cache').
                " SET created = ". $this->db->now().", data = ?".
                " WHERE user_id = ?".
                " AND cache_key = ?",
@@ -352,7 +352,7 @@
            // for better performance we allow more records for one key
            // so, no need to check if record exist (see rcube_cache::read_record())
            $result = $this->db->query(
                "INSERT INTO ".get_table_name('cache').
                "INSERT INTO ".$this->db->table_name('cache').
                " (created, user_id, cache_key, data)".
                " VALUES (".$this->db->now().", ?, ?, ?)",
                $this->userid, $key, $data);
@@ -416,7 +416,7 @@
        }
        $this->db->query(
            "DELETE FROM ".get_table_name('cache').
            "DELETE FROM ".$this->db->table_name('cache').
            " WHERE user_id = ?" . $where,
            $this->userid);
    }
program/include/rcube_config.php
@@ -85,11 +85,11 @@
        // fix default imap folders encoding
        foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder)
            $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP');
            $this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP');
        if (!empty($this->prop['default_folders']))
            foreach ($this->prop['default_folders'] as $n => $folder)
                $this->prop['default_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
                $this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
        // set PHP error logging according to config
        if ($this->prop['debug_level'] & 1) {
@@ -186,7 +186,7 @@
            $result = $def;
        }
        $rcmail = rcmail::get_instance();
        $rcmail = rcube::get_instance();
        if ($name == 'timezone' && isset($this->prop['_timezone_value']))
            $result = $this->prop['_timezone_value'];
@@ -300,7 +300,7 @@
    {
        // Bomb out if the requested key does not exist
        if (!array_key_exists($key, $this->prop)) {
            raise_error(array(
            rcube::raise_error(array(
                'code' => 500, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Request for unconfigured crypto key \"$key\""
@@ -311,7 +311,7 @@
        // Bomb out if the configured key is not exactly 24 bytes long
        if (strlen($key) != 24) {
            raise_error(array(
            rcube::raise_error(array(
                'code' => 500, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
@@ -335,7 +335,7 @@
            if ($delim == "\n" || $delim == "\r\n")
                return $delim;
            else
                raise_error(array(
                rcube::raise_error(array(
                    'code' => 500, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Invalid mail_header_delimiter setting"
@@ -370,7 +370,7 @@
                $domain = $this->prop['mail_domain'][$host];
        }
        else if (!empty($this->prop['mail_domain']))
            $domain = rcube_parse_host($this->prop['mail_domain']);
            $domain = rcmail::parse_host($this->prop['mail_domain']);
        if ($encode)
            $domain = rcube_idn_to_ascii($domain);
program/include/rcube_contacts.php
@@ -153,7 +153,7 @@
        $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : '';
        $sql_result = $this->db->query(
            "SELECT * FROM ".get_table_name($this->db_groups).
            "SELECT * FROM ".$this->db->table_name($this->db_groups).
            " WHERE del<>1".
            " AND user_id=?".
            $sql_filter.
@@ -178,7 +178,7 @@
    function get_group($group_id)
    {
        $sql_result = $this->db->query(
            "SELECT * FROM ".get_table_name($this->db_groups).
            "SELECT * FROM ".$this->db->table_name($this->db_groups).
            " WHERE del<>1".
            " AND contactgroup_id=?".
            " AND user_id=?",
@@ -214,7 +214,7 @@
        $length = $subset != 0 ? abs($subset) : $this->page_size;
        if ($this->group_id)
            $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m".
            $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m".
                " ON (m.contact_id = c.".$this->primary_key.")";
        $order_col = (in_array($this->sort_col, $this->table_cols) ? $this->sort_col : 'name');
@@ -228,7 +228,7 @@
        $order_cols[] = 'c.email';
        $sql_result = $this->db->limitquery(
            "SELECT * FROM ".get_table_name($this->db_name)." AS c" .
            "SELECT * FROM ".$this->db->table_name($this->db_name)." AS c" .
            $join .
            " WHERE c.del<>1" .
                " AND c.user_id=?" .
@@ -488,13 +488,13 @@
    private function _count()
    {
        if ($this->group_id)
            $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m".
            $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m".
                " ON (m.contact_id=c.".$this->primary_key.")";
        // count contacts for this user
        $sql_result = $this->db->query(
            "SELECT COUNT(c.contact_id) AS rows".
            " FROM ".get_table_name($this->db_name)." AS c".
            " FROM ".$this->db->table_name($this->db_name)." AS c".
                $join.
            " WHERE c.del<>1".
            " AND c.user_id=?".
@@ -536,7 +536,7 @@
            return $assoc ? $first : $this->result;
        $this->db->query(
            "SELECT * FROM ".get_table_name($this->db_name).
            "SELECT * FROM ".$this->db->table_name($this->db_name).
            " WHERE contact_id=?".
                " AND user_id=?".
                " AND del<>1",
@@ -568,8 +568,8 @@
          return $results;
      $sql_result = $this->db->query(
        "SELECT cgm.contactgroup_id, cg.name FROM " . get_table_name($this->db_groupmembers) . " AS cgm" .
        " LEFT JOIN " . get_table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" .
        "SELECT cgm.contactgroup_id, cg.name FROM " . $this->db->table_name($this->db_groupmembers) . " AS cgm" .
        " LEFT JOIN " . $this->db->table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" .
        " WHERE cgm.contact_id=?",
        $id
      );
@@ -638,7 +638,7 @@
        if (!$existing->count && !empty($a_insert_cols)) {
            $this->db->query(
                "INSERT INTO ".get_table_name($this->db_name).
                "INSERT INTO ".$this->db->table_name($this->db_name).
                " (user_id, changed, del, ".join(', ', $a_insert_cols).")".
                " VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")"
            );
@@ -676,7 +676,7 @@
        if (!empty($write_sql)) {
            $this->db->query(
                "UPDATE ".get_table_name($this->db_name).
                "UPDATE ".$this->db->table_name($this->db_name).
                " SET changed=".$this->db->now().", ".join(', ', $write_sql).
                " WHERE contact_id=?".
                    " AND user_id=?".
@@ -772,7 +772,7 @@
        // flag record as deleted (always)
        $this->db->query(
            "UPDATE ".get_table_name($this->db_name).
            "UPDATE ".$this->db->table_name($this->db_name).
            " SET del=1, changed=".$this->db->now().
            " WHERE user_id=?".
                " AND contact_id IN ($ids)",
@@ -799,7 +799,7 @@
        // clear deleted flag
        $this->db->query(
            "UPDATE ".get_table_name($this->db_name).
            "UPDATE ".$this->db->table_name($this->db_name).
            " SET del=0, changed=".$this->db->now().
            " WHERE user_id=?".
                " AND contact_id IN ($ids)",
@@ -819,7 +819,7 @@
    {
        $this->cache = null;
        $this->db->query("UPDATE ".get_table_name($this->db_name).
        $this->db->query("UPDATE ".$this->db->table_name($this->db_name).
            " SET del=1, changed=".$this->db->now().
            " WHERE user_id = ?", $this->user_id);
@@ -841,7 +841,7 @@
        $name = $this->unique_groupname($name);
        $this->db->query(
            "INSERT INTO ".get_table_name($this->db_groups).
            "INSERT INTO ".$this->db->table_name($this->db_groups).
            " (user_id, changed, name)".
            " VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")"
        );
@@ -863,7 +863,7 @@
    {
        // flag group record as deleted
        $sql_result = $this->db->query(
            "UPDATE ".get_table_name($this->db_groups).
            "UPDATE ".$this->db->table_name($this->db_groups).
            " SET del=1, changed=".$this->db->now().
            " WHERE contactgroup_id=?".
            " AND user_id=?",
@@ -889,7 +889,7 @@
        $name = $this->unique_groupname($newname);
        $sql_result = $this->db->query(
            "UPDATE ".get_table_name($this->db_groups).
            "UPDATE ".$this->db->table_name($this->db_groups).
            " SET name=?, changed=".$this->db->now().
            " WHERE contactgroup_id=?".
            " AND user_id=?",
@@ -917,7 +917,7 @@
        // get existing assignments ...
        $sql_result = $this->db->query(
            "SELECT contact_id FROM ".get_table_name($this->db_groupmembers).
            "SELECT contact_id FROM ".$this->db->table_name($this->db_groupmembers).
            " WHERE contactgroup_id=?".
                " AND contact_id IN (".$this->db->array2list($ids, 'integer').")",
            $group_id
@@ -930,7 +930,7 @@
        foreach ($ids as $contact_id) {
            $this->db->query(
                "INSERT INTO ".get_table_name($this->db_groupmembers).
                "INSERT INTO ".$this->db->table_name($this->db_groupmembers).
                " (contactgroup_id, contact_id, created)".
                " VALUES (?, ?, ".$this->db->now().")",
                $group_id,
@@ -960,7 +960,7 @@
        $ids = $this->db->array2list($ids, 'integer');
        $sql_result = $this->db->query(
            "DELETE FROM ".get_table_name($this->db_groupmembers).
            "DELETE FROM ".$this->db->table_name($this->db_groupmembers).
            " WHERE contactgroup_id=?".
                " AND contact_id IN ($ids)",
            $group_id
@@ -983,7 +983,7 @@
        do {
            $sql_result = $this->db->query(
                "SELECT 1 FROM ".get_table_name($this->db_groups).
                "SELECT 1 FROM ".$this->db->table_name($this->db_groups).
                " WHERE del<>1".
                    " AND user_id=?".
                    " AND name=?",
program/include/rcube_html_page.php
File was deleted
program/include/rcube_imap.php
@@ -132,7 +132,7 @@
            $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
        }
        else if ($use_ssl) {
            raise_error(array('code' => 403, 'type' => 'imap',
            rcube::raise_error(array('code' => 403, 'type' => 'imap',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "OpenSSL not available"), true, false);
            $port = 143;
@@ -154,7 +154,7 @@
        $attempt = 0;
        do {
            $data = rcmail::get_instance()->plugins->exec_hook('imap_connect',
            $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
                array_merge($this->options, array('host' => $host, 'user' => $user,
                    'attempt' => ++$attempt)));
@@ -185,9 +185,9 @@
        else if ($this->conn->error) {
            if ($pass && $user) {
                $message = sprintf("Login failed for %s from %s. %s",
                    $user, rcmail_remote_ip(), $this->conn->error);
                    $user, rcmail::remote_ip(), $this->conn->error);
                raise_error(array('code' => 403, 'type' => 'imap',
                rcube::raise_error(array('code' => 403, 'type' => 'imap',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => $message), true, false);
            }
@@ -457,7 +457,7 @@
            return;
        }
        $config = rcmail::get_instance()->config;
        $config = rcube::get_instance()->config;
        $imap_personal  = $config->get('imap_ns_personal');
        $imap_other     = $config->get('imap_ns_other');
        $imap_shared    = $config->get('imap_ns_shared');
@@ -546,7 +546,7 @@
            $folder = $this->folder;
        }
        return $this->messagecount($folder, $mode, $force, $status);
        return $this->countmessages($folder, $mode, $force, $status);
    }
@@ -562,7 +562,7 @@
     * @return int Number of messages
     * @see rcube_imap::count()
     */
    protected function messagecount($folder, $mode='ALL', $force=false, $status=true)
    protected function countmessages($folder, $mode='ALL', $force=false, $status=true)
    {
        $mode = strtoupper($mode);
@@ -834,8 +834,8 @@
     * protected method for setting threaded messages flags:
     * depth, has_children and unread_children
     *
     * @param  array             $headers Reference to headers array indexed by message UID
     * @param  rcube_imap_result $threads Threads data object
     * @param  array               $headers  Reference to headers array indexed by message UID
     * @param  rcube_result_thread $threads  Threads data object
     *
     * @return array Message headers array indexed by message UID
     */
@@ -1048,7 +1048,7 @@
        if ($sort) {
            // use this class for message sorting
            $sorter = new rcube_header_sorter();
            $sorter = new rcube_message_header_sorter();
            $sorter->set_index($msgs);
            $sorter->sort_headers($a_msg_headers);
        }
@@ -1075,7 +1075,7 @@
        $old = $this->get_folder_stats($folder);
        // refresh message count -> will update
        $this->messagecount($folder, 'ALL', true);
        $this->countmessages($folder, 'ALL', true);
        $result = 0;
@@ -1456,7 +1456,7 @@
            foreach ($matches[1] as $m) {
                $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
                $string = substr($str, $string_offset - 1, $m[0]);
                $string = rcube_charset_convert($string, $charset, $dest_charset);
                $string = rcube_charset::convert($string, $charset, $dest_charset);
                if ($string === false) {
                    continue;
                }
@@ -1498,7 +1498,7 @@
     * @param string  $folder   Folder to read from
     * @param bool    $force    True to skip cache
     *
     * @return rcube_mail_header Message headers
     * @return rcube_message_header Message headers
     */
    public function get_message_headers($uid, $folder = null, $force = false)
    {
@@ -1529,7 +1529,7 @@
     * @param int     $uid      Message UID to fetch
     * @param string  $folder   Folder to read from
     *
     * @return object rcube_mail_header Message data
     * @return object rcube_message_header Message data
     */
    public function get_message($uid, $folder = null)
    {
@@ -1948,7 +1948,7 @@
                $charset = $this->struct_charset;
            }
            else {
                $charset = rc_detect_encoding($filename_mime, $this->default_charset);
                $charset = rcube_charset::detect($filename_mime, $this->default_charset);
            }
            $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset);
@@ -1960,7 +1960,7 @@
                $filename_encoded = $fmatches[2];
            }
            $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset);
            $part->filename = rcube_charset::convert(urldecode($filename_encoded), $filename_charset);
        }
    }
@@ -2039,7 +2039,7 @@
                        $o_part->charset = $this->default_charset;
                    }
                }
                $body = rcube_charset_convert($body, $o_part->charset);
                $body = rcube_charset::convert($body, $o_part->charset);
            }
        }
@@ -2227,7 +2227,7 @@
            }
        }
        $config = rcmail::get_instance()->config;
        $config = rcube::get_instance()->config;
        $to_trash = $to_mbox == $config->get('trash_mbox');
        // flag messages as read before moving them
@@ -2510,7 +2510,7 @@
        $a_defaults = $a_out = array();
        // Give plugins a chance to provide a list of folders
        $data = rcmail::get_instance()->plugins->exec_hook('storage_folders',
        $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
            array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
        if (isset($data['folders'])) {
@@ -2521,7 +2521,7 @@
        }
        else {
            // Server supports LIST-EXTENDED, we can use selection options
            $config = rcmail::get_instance()->config;
            $config = rcube::get_instance()->config;
            // #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
            if (!$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED')) {
                // This will also set folder options, LSUB doesn't do that
@@ -3530,7 +3530,7 @@
    protected function get_cache_engine()
    {
        if ($this->caching && !$this->cache) {
            $rcmail = rcmail::get_instance();
            $rcmail = rcube::get_instance();
            $ttl = $rcmail->config->get('message_cache_lifetime', '10d') - mktime();
            $this->cache = $rcmail->get_cache('IMAP', $this->caching, $ttl);
        }
@@ -3589,8 +3589,9 @@
            $this->mcache->expunge($ttl);
        }
        if ($this->cache)
        if ($this->cache) {
            $this->cache->expunge();
        }
    }
@@ -3624,10 +3625,10 @@
    protected function get_mcache_engine()
    {
        if ($this->messages_caching && !$this->mcache) {
            $rcmail = rcmail::get_instance();
            $rcmail = rcube::get_instance();
            if ($dbh = $rcmail->get_dbh()) {
                $this->mcache = new rcube_imap_cache(
                    $dbh, $this, $rcmail->user->ID, $this->options['skip_deleted']);
                    $dbh, $this, $rcmail->get_user_id(), $this->options['skip_deleted']);
            }
        }
@@ -3691,7 +3692,7 @@
                $a_defaults[$p] = $folder;
            }
            else {
                $folders[$folder] = rcube_charset_convert($folder, 'UTF7-IMAP');
                $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
            }
        }
@@ -3851,7 +3852,7 @@
     */
    public function debug_handler(&$imap, $message)
    {
        write_log('imap', $message);
        rcmail::write_log('imap', $message);
    }
program/include/rcube_imap_cache.php
@@ -95,7 +95,7 @@
    {
        $this->db           = $db;
        $this->imap         = $imap;
        $this->userid       = (int)$userid;
        $this->userid       = $userid;
        $this->skip_deleted = $skip_deleted;
    }
@@ -290,7 +290,7 @@
     * @param string $mailbox  Folder name
     * @param array  $msgs     Message UIDs
     *
     * @return array The list of messages (rcube_mail_header) indexed by UID
     * @return array The list of messages (rcube_message_header) indexed by UID
     */
    function get_messages($mailbox, $msgs = array())
    {
@@ -301,7 +301,7 @@
        // Fetch messages from cache
        $sql_result = $this->db->query(
            "SELECT uid, data, flags"
            ." FROM ".get_table_name('cache_messages')
            ." FROM ".$this->db->table_name('cache_messages')
            ." WHERE user_id = ?"
                ." AND mailbox = ?"
                ." AND uid IN (".$this->db->array2list($msgs, 'integer').")",
@@ -348,7 +348,7 @@
     *                         from IMAP server
     * @param bool   $no_cache Enables internal cache usage
     *
     * @return rcube_mail_header Message data
     * @return rcube_message_header Message data
     */
    function get_message($mailbox, $uid, $update = true, $cache = true)
    {
@@ -362,7 +362,7 @@
        $sql_result = $this->db->query(
            "SELECT flags, data"
            ." FROM ".get_table_name('cache_messages')
            ." FROM ".$this->db->table_name('cache_messages')
            ." WHERE user_id = ?"
                ." AND mailbox = ?"
                ." AND uid = ?",
@@ -404,9 +404,9 @@
    /**
     * Saves the message in cache.
     *
     * @param string            $mailbox  Folder name
     * @param rcube_mail_header $message  Message data
     * @param bool              $force    Skips message in-cache existance check
     * @param string               $mailbox  Folder name
     * @param rcube_message_header $message  Message data
     * @param bool                 $force    Skips message in-cache existance check
     */
    function add_message($mailbox, $message, $force = false)
    {
@@ -430,7 +430,7 @@
        // here will work as select, assume row exist if affected_rows=0)
        if (!$force) {
            $res = $this->db->query(
                "UPDATE ".get_table_name('cache_messages')
                "UPDATE ".$this->db->table_name('cache_messages')
                ." SET flags = ?, data = ?, changed = ".$this->db->now()
                ." WHERE user_id = ?"
                    ." AND mailbox = ?"
@@ -444,7 +444,7 @@
        // insert new record
        $this->db->query(
            "INSERT INTO ".get_table_name('cache_messages')
            "INSERT INTO ".$this->db->table_name('cache_messages')
            ." (user_id, mailbox, uid, flags, changed, data)"
            ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)",
            $this->userid, $mailbox, (int) $message->uid, $flags, $msg);
@@ -479,7 +479,7 @@
        }
        $this->db->query(
            "UPDATE ".get_table_name('cache_messages')
            "UPDATE ".$this->db->table_name('cache_messages')
            ." SET changed = ".$this->db->now()
            .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
            ." WHERE user_id = ?"
@@ -500,7 +500,7 @@
    {
        if (!strlen($mailbox)) {
            $this->db->query(
                "DELETE FROM ".get_table_name('cache_messages')
                "DELETE FROM ".$this->db->table_name('cache_messages')
                ." WHERE user_id = ?",
                $this->userid);
        }
@@ -513,11 +513,11 @@
            }
            $this->db->query(
                "DELETE FROM ".get_table_name('cache_messages')
                "DELETE FROM ".$this->db->table_name('cache_messages')
                ." WHERE user_id = ?"
                    ." AND mailbox = ".$this->db->quote($mailbox)
                    ." AND mailbox = ?"
                    .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
                $this->userid);
                $this->userid, $mailbox);
        }
    }
@@ -536,17 +536,19 @@
        // otherwise use 'valid' flag to not loose HIGHESTMODSEQ value
        if ($remove) {
            $this->db->query(
                "DELETE FROM ".get_table_name('cache_index')
                ." WHERE user_id = ".intval($this->userid)
                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
                "DELETE FROM ".$this->db->table_name('cache_index')
                ." WHERE user_id = ?"
                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
                $this->userid
            );
        }
        else {
            $this->db->query(
                "UPDATE ".get_table_name('cache_index')
                "UPDATE ".$this->db->table_name('cache_index')
                ." SET valid = 0"
                ." WHERE user_id = ".intval($this->userid)
                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
                ." WHERE user_id = ?"
                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
                $this->userid
            );
        }
@@ -569,9 +571,10 @@
    function remove_thread($mailbox = null)
    {
        $this->db->query(
            "DELETE FROM ".get_table_name('cache_thread')
            ." WHERE user_id = ".intval($this->userid)
                .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
            "DELETE FROM ".$this->db->table_name('cache_thread')
            ." WHERE user_id = ?"
                .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
            $this->userid
        );
        if (strlen($mailbox)) {
@@ -628,7 +631,7 @@
        // Get index from DB
        $sql_result = $this->db->query(
            "SELECT data, valid"
            ." FROM ".get_table_name('cache_index')
            ." FROM ".$this->db->table_name('cache_index')
            ." WHERE user_id = ?"
                ." AND mailbox = ?",
            $this->userid, $mailbox);
@@ -665,7 +668,7 @@
        // Get thread from DB
        $sql_result = $this->db->query(
            "SELECT data"
            ." FROM ".get_table_name('cache_thread')
            ." FROM ".$this->db->table_name('cache_thread')
            ." WHERE user_id = ?"
                ." AND mailbox = ?",
            $this->userid, $mailbox);
@@ -709,7 +712,7 @@
        if ($exists) {
            $sql_result = $this->db->query(
                "UPDATE ".get_table_name('cache_index')
                "UPDATE ".$this->db->table_name('cache_index')
                ." SET data = ?, valid = 1, changed = ".$this->db->now()
                ." WHERE user_id = ?"
                    ." AND mailbox = ?",
@@ -717,7 +720,7 @@
        }
        else {
            $sql_result = $this->db->query(
                "INSERT INTO ".get_table_name('cache_index')
                "INSERT INTO ".$this->db->table_name('cache_index')
                ." (user_id, mailbox, data, valid, changed)"
                ." VALUES (?, ?, ?, 1, ".$this->db->now().")",
                $this->userid, $mailbox, $data);
@@ -740,7 +743,7 @@
        if ($exists) {
            $sql_result = $this->db->query(
                "UPDATE ".get_table_name('cache_thread')
                "UPDATE ".$this->db->table_name('cache_thread')
                ." SET data = ?, changed = ".$this->db->now()
                ." WHERE user_id = ?"
                    ." AND mailbox = ?",
@@ -748,7 +751,7 @@
        }
        else {
            $sql_result = $this->db->query(
                "INSERT INTO ".get_table_name('cache_thread')
                "INSERT INTO ".$this->db->table_name('cache_thread')
                ." (user_id, mailbox, data, changed)"
                ." VALUES (?, ?, ?, ".$this->db->now().")",
                $this->userid, $mailbox, $data);
@@ -956,7 +959,7 @@
        $uids = array();
        $sql_result = $this->db->query(
            "SELECT uid"
            ." FROM ".get_table_name('cache_messages')
            ." FROM ".$this->db->table_name('cache_messages')
            ." WHERE user_id = ?"
                ." AND mailbox = ?",
            $this->userid, $mailbox);
@@ -1003,7 +1006,7 @@
                }
                $this->db->query(
                    "UPDATE ".get_table_name('cache_messages')
                    "UPDATE ".$this->db->table_name('cache_messages')
                    ." SET flags = ?, changed = ".$this->db->now()
                    ." WHERE user_id = ?"
                        ." AND mailbox = ?"
@@ -1058,7 +1061,7 @@
     *
     * @param array $sql_arr Message row data
     *
     * @return rcube_mail_header Message object
     * @return rcube_message_header Message object
     */
    private function build_message($sql_arr)
    {
program/include/rcube_imap_generic.php
@@ -29,42 +29,9 @@
*/
/**
 * Struct representing an e-mail message header
 *
 * @package Mail
 * @author  Aleksander Machniak <alec@alec.pl>
 */
class rcube_mail_header
{
    public $id;
    public $uid;
    public $subject;
    public $from;
    public $to;
    public $cc;
    public $replyto;
    public $in_reply_to;
    public $date;
    public $messageID;
    public $size;
    public $encoding;
    public $charset;
    public $ctype;
    public $timestamp;
    public $bodystructure;
    public $internaldate;
    public $references;
    public $priority;
    public $mdn_to;
    public $others = array();
    public $flags = array();
}
// for backward copat.
class rcube_mail_header extends rcube_message_header { }
// For backward compatibility with cached messages (#1486602)
class iilBasicHeader extends rcube_mail_header
{
}
/**
 * PHP based wrapper class to connect to an IMAP server
@@ -1545,8 +1512,6 @@
     */
    function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII')
    {
        require_once dirname(__FILE__) . '/rcube_result_index.php';
        $field = strtoupper($field);
        if ($field == 'INTERNALDATE') {
            $field = 'ARRIVAL';
@@ -1595,8 +1560,6 @@
     */
    function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII')
    {
        require_once dirname(__FILE__) . '/rcube_result_thread.php';
        $old_sel = $this->selected;
        if (!$this->select($mailbox)) {
@@ -1635,8 +1598,6 @@
     */
    function search($mailbox, $criteria, $return_uid=false, $items=array())
    {
        require_once dirname(__FILE__) . '/rcube_result_index.php';
        $old_sel = $this->selected;
        if (!$this->select($mailbox)) {
@@ -1696,8 +1657,6 @@
    function index($mailbox, $message_set, $index_field='', $skip_deleted=true,
        $uidfetch=false, $return_uid=false)
    {
        require_once dirname(__FILE__) . '/rcube_result_index.php';
        $msg_index = $this->fetchHeaderIndex($mailbox, $message_set,
            $index_field, $skip_deleted, $uidfetch, $return_uid);
@@ -2034,7 +1993,7 @@
     * @param string $mod_seq     Modification sequence for CHANGEDSINCE (RFC4551) query
     * @param bool   $vanished    Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query
     *
     * @return array List of rcube_mail_header elements, False on error
     * @return array List of rcube_message_header elements, False on error
     * @since 0.6
     */
    function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(),
@@ -2074,7 +2033,7 @@
            if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
                $id = intval($m[1]);
                $result[$id]            = new rcube_mail_header;
                $result[$id]            = new rcube_message_header;
                $result[$id]->id        = $id;
                $result[$id]->subject   = '';
                $result[$id]->messageID = 'mid:' . $id;
program/include/rcube_ldap.php
@@ -63,12 +63,11 @@
    /**
    * Object constructor
    *
    * @param array         LDAP connection properties
    * @param boolean     Enables debug mode
    * @param string     Current user mail domain name
    * @param integer User-ID
    * @param array      $p            LDAP connection properties
    * @param boolean $debug        Enables debug mode
    * @param string  $mail_domain  Current user mail domain name
    */
    function __construct($p, $debug=false, $mail_domain=NULL)
    function __construct($p, $debug = false, $mail_domain = null)
    {
        $this->prop = $p;
@@ -176,10 +175,10 @@
    */
    private function _connect()
    {
        global $RCMAIL;
        $RCMAIL = rcmail::get_instance();
        if (!function_exists('ldap_connect'))
            raise_error(array('code' => 100, 'type' => 'ldap',
            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "No ldap support in this installation of PHP"),
                true, true);
@@ -195,7 +194,7 @@
        foreach ($this->prop['hosts'] as $host)
        {
            $host     = idn_to_ascii(rcube_parse_host($host));
            $host     = idn_to_ascii(rcmail::parse_host($host));
            $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : '');
            $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]");
@@ -225,7 +224,7 @@
        }
        if (!is_resource($this->conn)) {
            raise_error(array('code' => 100, 'type' => 'ldap',
            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Could not connect to any LDAP server, last tried $hostname"), true);
@@ -248,7 +247,7 @@
            }
            // Get the pieces needed for variable replacement.
            if ($fu = $RCMAIL->user->get_username())
            if ($fu = $RCMAIL->get_user_name())
                list($u, $d) = explode('@', $fu);
            else
                $d = $this->mail_domain;
@@ -287,7 +286,7 @@
                    if (!empty($this->prop['search_dn_default']))
                        $replaces['%dn'] = $this->prop['search_dn_default'];
                    else {
                        raise_error(array(
                        rcube::raise_error(array(
                            'code' => 100, 'type' => 'ldap',
                            'file' => __FILE__, 'line' => __LINE__,
                            'message' => "DN not found using LDAP search."), true);
@@ -341,7 +340,7 @@
        }
        if (!function_exists('ldap_sasl_bind')) {
            raise_error(array('code' => 100, 'type' => 'ldap',
            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Unable to bind: ldap_sasl_bind() not exists"),
                true, true);
@@ -367,7 +366,7 @@
        $this->_debug("S: ".ldap_error($this->conn));
        raise_error(array(
        rcube::raise_error(array(
            'code' => ldap_errno($this->conn), 'type' => 'ldap',
            'file' => __FILE__, 'line' => __LINE__,
            'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)),
@@ -400,7 +399,7 @@
        $this->_debug("S: ".ldap_error($this->conn));
        raise_error(array(
        rcube::raise_error(array(
            'code' => ldap_errno($this->conn), 'type' => 'ldap',
            'file' => __FILE__, 'line' => __LINE__,
            'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
@@ -1562,8 +1561,9 @@
     */
    private function _debug($str)
    {
        if ($this->debug)
            write_log('ldap', $str);
        if ($this->debug) {
            rcmail::write_log('ldap', $str);
        }
    }
program/include/rcube_mdb2.php
@@ -59,10 +59,11 @@
     * @param  string $db_dsnw DSN for read/write operations
     * @param  string $db_dsnr Optional DSN for read only operations
     */
    function __construct($db_dsnw, $db_dsnr='', $pconn=false)
    public function __construct($db_dsnw, $db_dsnr='', $pconn=false)
    {
        if (empty($db_dsnr))
        if (empty($db_dsnr)) {
            $db_dsnr = $db_dsnw;
        }
        $this->db_dsnw = $db_dsnw;
        $this->db_dsnr = $db_dsnr;
@@ -88,7 +89,8 @@
            'emulate_prepared' => $this->debug_mode,
            'debug'            => $this->debug_mode,
            'debug_handler'    => array($this, 'debug_handler'),
            'portability'      => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL);
            'portability'      => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL,
        );
        if ($this->db_provider == 'pgsql') {
            $db_options['disable_smart_seqname'] = true;
@@ -103,17 +105,19 @@
            $this->db_error = true;
            $this->db_error_msg = $dbh->getMessage();
            raise_error(array('code' => 500, 'type' => 'db',
            rcube::raise_error(array('code' => 500, 'type' => 'db',
                'line' => __LINE__, 'file' => __FILE__,
                'message' => $dbh->getUserInfo()), true, false);
        }
        else if ($this->db_provider == 'sqlite') {
            $dsn_array = MDB2::parseDSN($dsn);
            if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
                $this->_sqlite_create_database($dbh, $this->sqlite_initials);
            if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) {
                $this->sqlite_create_database($dbh, $this->sqlite_initials);
            }
        }
        else if ($this->db_provider!='mssql' && $this->db_provider!='sqlsrv')
        else if ($this->db_provider != 'mssql' && $this->db_provider != 'sqlsrv') {
            $dbh->setCharset('utf8');
        }
        return $dbh;
    }
@@ -123,9 +127,8 @@
     * Connect to appropiate database depending on the operation
     *
     * @param  string $mode Connection mode (r|w)
     * @access public
     */
    function db_connect($mode)
    public function db_connect($mode)
    {
        // previous connection failed, don't attempt to connect again
        if ($this->conn_failure) {
@@ -157,10 +160,12 @@
            $this->db_connected = !PEAR::isError($this->db_handle);
        }
        if ($this->db_connected)
        if ($this->db_connected) {
            $this->db_mode = $mode;
        else
        }
        else {
            $this->conn_failure = true;
        }
    }
@@ -168,9 +173,8 @@
     * Activate/deactivate debug mode
     *
     * @param boolean $dbg True if SQL queries should be logged
     * @access public
     */
    function set_debug($dbg = true)
    public function set_debug($dbg = true)
    {
        $this->debug_mode = $dbg;
        if ($this->db_connected) {
@@ -184,9 +188,8 @@
     * Getter for error state
     *
     * @param  boolean  True on error
     * @access public
     */
    function is_error()
    public function is_error()
    {
        return $this->db_error ? $this->db_error_msg : false;
    }
@@ -196,9 +199,8 @@
     * Connection state checker
     *
     * @param  boolean  True if in connected state
     * @access public
     */
    function is_connected()
    public function is_connected()
    {
        return PEAR::isError($this->db_handle) ? false : $this->db_connected;
    }
@@ -208,7 +210,7 @@
     * Is database replication configured?
     * This returns true if dsnw != dsnr
     */
    function is_replicated()
    public function is_replicated()
    {
      return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
    }
@@ -219,17 +221,18 @@
     *
     * @param  string  SQL query to execute
     * @param  mixed   Values to be inserted in query
     *
     * @return number  Query handle identifier
     * @access public
     */
    function query()
    public function query()
    {
        $params = func_get_args();
        $query = array_shift($params);
        // Support one argument of type array, instead of n arguments
        if (count($params) == 1 && is_array($params[0]))
        if (count($params) == 1 && is_array($params[0])) {
            $params = $params[0];
        }
        return $this->_query($query, 0, 0, $params);
    }
@@ -242,10 +245,10 @@
     * @param  number  Offset for LIMIT statement
     * @param  number  Number of rows for LIMIT statement
     * @param  mixed   Values to be inserted in query
     *
     * @return number  Query handle identifier
     * @access public
     */
    function limitquery()
    public function limitquery()
    {
        $params  = func_get_args();
        $query   = array_shift($params);
@@ -274,17 +277,21 @@
        $this->db_connect($mode);
        // check connection before proceeding
        if (!$this->is_connected())
        if (!$this->is_connected()) {
            return null;
        }
        if ($this->db_provider == 'sqlite')
            $this->_sqlite_prepare();
        if ($this->db_provider == 'sqlite') {
            $this->sqlite_prepare();
        }
        if ($numrows || $offset)
        if ($numrows || $offset) {
            $result = $this->db_handle->setLimit($numrows,$offset);
        }
        if (empty($params))
        if (empty($params)) {
            $result = $mode == 'r' ? $this->db_handle->query($query) : $this->db_handle->exec($query);
        }
        else {
            $params = (array)$params;
            $q = $this->db_handle->prepare($query, null, $mode=='w' ? MDB2_PREPARE_MANIP : null);
@@ -292,7 +299,7 @@
                $this->db_error = true;
                $this->db_error_msg = $q->userinfo;
                raise_error(array('code' => 500, 'type' => 'db',
                rcube::raise_error(array('code' => 500, 'type' => 'db',
                    'line' => __LINE__, 'file' => __FILE__,
                    'message' => $this->db_error_msg), true, false);
@@ -315,17 +322,18 @@
     *
     * @param  number $res_id  Optional query handle identifier
     * @return mixed   Number of rows or false on failure
     * @access public
     */
    function num_rows($res_id=null)
    public function num_rows($res_id=null)
    {
        if (!$this->db_connected)
        if (!$this->db_connected) {
            return false;
        }
        if ($result = $this->_get_result($res_id))
        if ($result = $this->_get_result($res_id)) {
            return $result->numRows();
        else
            return false;
        }
        return false;
    }
@@ -334,12 +342,12 @@
     *
     * @param  number $res_id Optional query handle identifier
     * @return mixed   Number of rows or false on failure
     * @access public
     */
    function affected_rows($res_id = null)
    public function affected_rows($res_id = null)
    {
        if (!$this->db_connected)
        if (!$this->db_connected) {
            return false;
        }
        return $this->_get_result($res_id);
    }
@@ -350,21 +358,24 @@
     * For Postgres databases, a sequence name is required
     *
     * @param  string $table  Table name (to find the incremented sequence)
     *
     * @return mixed   ID or false on failure
     * @access public
     */
    function insert_id($table = '')
    public function insert_id($table = '')
    {
        if (!$this->db_connected || $this->db_mode == 'r')
        if (!$this->db_connected || $this->db_mode == 'r') {
            return false;
        }
        if ($table) {
            if ($this->db_provider == 'pgsql')
            if ($this->db_provider == 'pgsql') {
                // find sequence name
                $table = get_sequence_name($table);
            else
                $table = $this->sequence_name($table);
            }
            else {
                // resolve table name
                $table = get_table_name($table);
                $table = $this->table_name($table);
            }
        }
        $id = $this->db_handle->lastInsertID($table);
@@ -378,10 +389,10 @@
     * If no query handle is specified, the last query will be taken as reference
     *
     * @param  number $res_id Optional query handle identifier
     *
     * @return mixed   Array with col values or false on failure
     * @access public
     */
    function fetch_assoc($res_id=null)
    public function fetch_assoc($res_id = null)
    {
        $result = $this->_get_result($res_id);
        return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC);
@@ -393,10 +404,10 @@
     * If no query handle is specified, the last query will be taken as reference
     *
     * @param  number $res_id  Optional query handle identifier
     *
     * @return mixed   Array with col values or false on failure
     * @access public
     */
    function fetch_array($res_id=null)
    public function fetch_array($res_id = null)
    {
        $result = $this->_get_result($res_id);
        return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED);
@@ -408,13 +419,14 @@
     *
     * @param  MDB2_Result_Common Query $result result handle
     * @param  number                   $mode   Fetch mode identifier
     * @return mixed   Array with col values or false on failure
     * @access private
     *
     * @return mixed  Array with col values or false on failure
     */
    private function _fetch_row($result, $mode)
    {
        if ($result === false || PEAR::isError($result) || !$this->is_connected())
        if ($result === false || PEAR::isError($result) || !$this->is_connected()) {
            return false;
        }
        return $result->fetchRow($mode);
    }
@@ -424,18 +436,19 @@
     * Wrapper for the SHOW TABLES command
     *
     * @return array List of all tables of the current database
     * @access public
     * @since 0.4-beta
     */
    function list_tables()
    public function list_tables()
    {
        // get tables if not cached
        if (!$this->tables) {
            $this->db_handle->loadModule('Manager');
            if (!PEAR::isError($result = $this->db_handle->listTables()))
            if (!PEAR::isError($result = $this->db_handle->listTables())) {
                $this->tables = $result;
            else
            }
            else {
                $this->tables = array();
            }
        }
        return $this->tables;
@@ -446,9 +459,10 @@
     * Wrapper for SHOW COLUMNS command
     *
     * @param string Table name
     *
     * @return array List of table cols
     */
    function list_cols($table)
    public function list_cols($table)
    {
        $this->db_handle->loadModule('Manager');
        if (!PEAR::isError($result = $this->db_handle->listTableFields($table))) {
@@ -464,18 +478,20 @@
     *
     * @param  mixed  $input  Value to quote
     * @param  string $type   Type of data
     *
     * @return string  Quoted/converted string for use in query
     * @access public
     */
    function quote($input, $type = null)
    public function quote($input, $type = null)
    {
        // handle int directly for better performance
        if ($type == 'integer')
        if ($type == 'integer') {
            return intval($input);
        }
        // create DB handle if not available
        if (!$this->db_handle)
        if (!$this->db_handle) {
            $this->db_connect('r');
        }
        return $this->db_connected ? $this->db_handle->quote($input, $type) : addslashes($input);
    }
@@ -485,12 +501,12 @@
     * Quotes a string so it can be safely used as a table or column name
     *
     * @param  string $str Value to quote
     *
     * @return string  Quoted string for use in query
     * @deprecated     Replaced by rcube_MDB2::quote_identifier
     * @see            rcube_mdb2::quote_identifier
     * @access public
     */
    function quoteIdentifier($str)
    public function quoteIdentifier($str)
    {
        return $this->quote_identifier($str);
    }
@@ -500,13 +516,14 @@
     * Quotes a string so it can be safely used as a table or column name
     *
     * @param  string $str Value to quote
     *
     * @return string  Quoted string for use in query
     * @access public
     */
    function quote_identifier($str)
    public function quote_identifier($str)
    {
        if (!$this->db_handle)
        if (!$this->db_handle) {
            $this->db_connect('r');
        }
        return $this->db_connected ? $this->db_handle->quoteIdentifier($str) : $str;
    }
@@ -516,14 +533,15 @@
     * Escapes a string
     *
     * @param  string $str The string to be escaped
     *
     * @return string  The escaped string
     * @access public
     * @since  0.1.1
     */
    function escapeSimple($str)
    public function escapeSimple($str)
    {
        if (!$this->db_handle)
        if (!$this->db_handle) {
            $this->db_connect('r');
        }
        return $this->db_handle->escape($str);
    }
@@ -533,9 +551,8 @@
     * Return SQL function for current time and date
     *
     * @return string SQL function to use in query
     * @access public
     */
    function now()
    public function now()
    {
        switch ($this->db_provider) {
            case 'mssql':
@@ -553,16 +570,18 @@
     *
     * @param  array  $arr  Input array
     * @param  string $type Type of data
     *
     * @return string Comma-separated list of quoted values for use in query
     * @access public
     */
    function array2list($arr, $type = null)
    public function array2list($arr, $type = null)
    {
        if (!is_array($arr))
        if (!is_array($arr)) {
            return $this->quote($arr, $type);
        }
        foreach ($arr as $idx => $item)
        foreach ($arr as $idx => $item) {
            $arr[$idx] = $this->quote($item, $type);
        }
        return implode(',', $arr);
    }
@@ -575,10 +594,11 @@
     * of timestamp functions in Mysql (year 2038 problem)
     *
     * @param  string $field Field name
     *
     * @return string  SQL statement to use in query
     * @deprecated
     */
    function unixtimestamp($field)
    public function unixtimestamp($field)
    {
        switch($this->db_provider) {
            case 'pgsql':
@@ -598,10 +618,10 @@
     * Return SQL statement to convert from a unix timestamp
     *
     * @param  string $timestamp Field name
     *
     * @return string  SQL statement to use in query
     * @access public
     */
    function fromunixtime($timestamp)
    public function fromunixtime($timestamp)
    {
        return date("'Y-m-d H:i:s'", $timestamp);
    }
@@ -612,13 +632,13 @@
     *
     * @param  string $column  Field name
     * @param  string $value   Search value
     *
     * @return string  SQL statement to use in query
     * @access public
     */
    function ilike($column, $value)
    public function ilike($column, $value)
    {
        // TODO: use MDB2's matchPattern() function
        switch($this->db_provider) {
        switch ($this->db_provider) {
            case 'pgsql':
                return $this->quote_identifier($column).' ILIKE '.$this->quote($value);
            default:
@@ -626,20 +646,20 @@
        }
    }
    /**
     * Abstract SQL statement for value concatenation
     *
     * @return string SQL statement to be used in query
     * @access public
     */
    function concat(/* col1, col2, ... */)
    public function concat(/* col1, col2, ... */)
    {
        $func = '';
        $args = func_get_args();
        if (is_array($args[0]))
            $args = $args[0];
        switch($this->db_provider) {
        switch ($this->db_provider) {
            case 'mysql':
            case 'mysqli':
                $func = 'CONCAT';
@@ -661,20 +681,22 @@
     * Encodes non-UTF-8 characters in string/array/object (recursive)
     *
     * @param  mixed  $input Data to fix
     *
     * @return mixed  Properly UTF-8 encoded data
     * @access public
     */
    function encode($input)
    public static function encode($input)
    {
        if (is_object($input)) {
            foreach (get_object_vars($input) as $idx => $value)
                $input->$idx = $this->encode($value);
            foreach (get_object_vars($input) as $idx => $value) {
                $input->$idx = self::encode($value);
            }
            return $input;
        }
        else if (is_array($input)) {
            foreach ($input as $idx => $value)
                $input[$idx] = $this->encode($value);
            return $input;
            foreach ($input as $idx => $value) {
                $input[$idx] = self::encode($value);
            }
            return $input;
        }
        return utf8_encode($input);
@@ -685,20 +707,22 @@
     * Decodes encoded UTF-8 string/object/array (recursive)
     *
     * @param  mixed $input Input data
     *
     * @return mixed  Decoded data
     * @access public
     */
    function decode($input)
    public static function decode($input)
    {
        if (is_object($input)) {
            foreach (get_object_vars($input) as $idx => $value)
                $input->$idx = $this->decode($value);
            foreach (get_object_vars($input) as $idx => $value) {
                $input->$idx = self::decode($value);
            }
            return $input;
        }
        else if (is_array($input)) {
            foreach ($input as $idx => $value)
                $input[$idx] = $this->decode($value);
            return $input;
            foreach ($input as $idx => $value) {
                $input[$idx] = self::decode($value);
            }
            return $input;
        }
        return utf8_decode($input);
@@ -709,8 +733,8 @@
     * Adds a query result and returns a handle ID
     *
     * @param  object $res Query handle
     *
     * @return mixed   Handle ID
     * @access private
     */
    private function _add_result($res)
    {
@@ -718,7 +742,7 @@
        if (PEAR::isError($res)) {
            $this->db_error = true;
            $this->db_error_msg = $res->getMessage();
            raise_error(array('code' => 500, 'type' => 'db',
            rcube::raise_error(array('code' => 500, 'type' => 'db',
                'line' => __LINE__, 'file' => __FILE__,
                'message' => $res->getMessage() . " Query: " 
                . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)),
@@ -737,17 +761,20 @@
     * If no ID is specified, the last resource handle will be returned
     *
     * @param  number $res_id Handle ID
     *
     * @return mixed   Resource handle or false on failure
     * @access private
     */
    private function _get_result($res_id = null)
    {
        if ($res_id == null)
        if ($res_id == null) {
            $res_id = $this->last_res_id;
        }
        if (isset($this->a_query_results[$res_id]))
            if (!PEAR::isError($this->a_query_results[$res_id]))
        if (isset($this->a_query_results[$res_id])) {
            if (!PEAR::isError($this->a_query_results[$res_id])) {
                return $this->a_query_results[$res_id];
            }
        }
        return false;
    }
@@ -758,42 +785,36 @@
     *
     * @param  MDB2   $dbh       SQLite database handle
     * @param  string $file_name File path to use for DB creation
     * @access private
     */
    private function _sqlite_create_database($dbh, $file_name)
    private function sqlite_create_database($dbh, $file_name)
    {
        if (empty($file_name) || !is_string($file_name))
        if (empty($file_name) || !is_string($file_name)) {
            return;
        }
        $data = file_get_contents($file_name);
        if (strlen($data))
            if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh))
                raise_error(array('code' => 500, 'type' => 'db',
        if (strlen($data)) {
            if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) {
                rcube::raise_error(array('code' => 500, 'type' => 'db',
                    'line' => __LINE__, 'file' => __FILE__,
                    'message' => $error), true, false);
                    'message' => $error), true, false);
            }
        }
    }
    /**
     * Add some proprietary database functions to the current SQLite handle
     * in order to make it MySQL compatible
     *
     * @access private
     */
    private function _sqlite_prepare()
    private function sqlite_prepare()
    {
        include_once(INSTALL_PATH . 'program/include/rcube_sqlite.inc');
        // we emulate via callback some missing MySQL function
        // we emulate via callback some missing MySQL functions
        sqlite_create_function($this->db_handle->connection,
            'from_unixtime', 'rcube_sqlite_from_unixtime');
            'unix_timestamp', array('rcube_mdb2', 'sqlite_unix_timestamp'));
        sqlite_create_function($this->db_handle->connection,
            'unix_timestamp', 'rcube_sqlite_unix_timestamp');
        sqlite_create_function($this->db_handle->connection,
            'now', 'rcube_sqlite_now');
        sqlite_create_function($this->db_handle->connection,
            'md5', 'rcube_sqlite_md5');
            'now', array('rcube_mdb2', 'sqlite_now'));
    }
@@ -805,8 +826,82 @@
        if ($scope != 'prepare') {
            $debug_output = sprintf('%s(%d): %s;',
                $scope, $db->db_index, rtrim($message, ';'));
            write_log('sql', $debug_output);
            rcmail::write_log('sql', $debug_output);
        }
    }
}  // end class rcube_db
    /**
     * Return correct name for a specific database table
     *
     * @param string $table Table name
     *
     * @return string Translated table name
     */
    public function table_name($table)
    {
        $rcmail = rcube::get_instance();
        // return table name if configured
        $config_key = 'db_table_'.$table;
        if ($name = $rcmail->config->get($config_key)) {
            return $name;
        }
        return $table;
    }
    /**
     * Return correct name for a specific database sequence
     * (used for Postgres only)
     *
     * @param string $sequence Secuence name
     *
     * @return string Translated sequence name
     */
    public function sequence_name($sequence)
    {
        $rcmail = rcube::get_instance();
        // return sequence name if configured
        $config_key = 'db_sequence_'.$sequence;
        if ($name = $rcmail->config->get($config_key)) {
            return $name;
        }
        return $sequence;
    }
    /**
     * Callback for sqlite: unix_timestamp()
     */
    public static function sqlite_unix_timestamp($timestamp = '')
    {
        $timestamp = trim($timestamp);
        if (!$timestamp) {
            $ret = time();
        }
        else if (!preg_match('/^[0-9]+$/s', $timestamp)) {
            $ret = strtotime($timestamp);
        }
        else {
            $ret = $timestamp;
        }
        return $ret;
    }
    /**
     * Callback for sqlite: now()
     */
    public static function sqlite_now()
    {
        return date("Y-m-d H:i:s");
    }
}
program/include/rcube_message.php
@@ -78,7 +78,7 @@
    function __construct($uid)
    {
        $this->uid  = $uid;
        $this->app  = rcmail::get_instance();
        $this->app  = rcube::get_instance();
        $this->storage = $this->app->get_storage();
        $this->storage->set_options(array('all_headers' => true));
@@ -96,8 +96,10 @@
        $this->opt = array(
            'safe' => $this->is_safe,
            'prefer_html' => $this->app->config->get('prefer_html'),
            'get_url' => rcmail_url('get', array(
                '_mbox' => $this->storage->get_folder(), '_uid' => $uid))
            'get_url' => $this->app->url(array(
                'action' => 'get',
                'mbox'   => $this->storage->get_folder(),
                'uid'    => $uid))
        );
        if (!empty($this->headers->structure)) {
@@ -380,7 +382,8 @@
                $c->type            = 'content';
                $c->ctype_primary   = 'text';
                $c->ctype_secondary = 'plain';
                $c->body            = rcube_label('htmlmessage');
                $c->mimetype        = 'text/plain';
                $c->realtype        = 'text/html';
                $this->parts[] = $c;
            }
@@ -388,7 +391,6 @@
            // add html part as attachment
            if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
                $html_part = &$structure->parts[$html_part];
                $html_part->filename = rcube_label('htmlmessage');
                $html_part->mimetype = 'text/html';
                $this->attachments[] = $html_part;
@@ -400,8 +402,8 @@
            $p->type            = 'content';
            $p->ctype_primary   = 'text';
            $p->ctype_secondary = 'plain';
            $p->body            = rcube_label('encryptedmessage');
            $p->size            = strlen($p->body);
            $p->mimetype        = 'text/plain';
            $p->realtype        = 'multipart/encrypted';
            $this->parts[] = $p;
        }
@@ -671,7 +673,7 @@
                $uupart->size     = strlen($uupart->body);
                $uupart->mime_id  = 'uu.' . $part->mime_id . '.' . $pid;
                $ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
                $ctype = rcube_mime::content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
                $uupart->mimetype = $ctype;
                list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
program/include/rcube_message_header.php
New file
@@ -0,0 +1,238 @@
<?php
/**
 +-----------------------------------------------------------------------+
 | program/include/rcube_message_header.php                              |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   E-mail message headers representation                               |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
 $Id$
*/
/**
 * Struct representing an e-mail message header
 *
 * @package Mail
 * @author  Aleksander Machniak <alec@alec.pl>
 */
class rcube_message_header
{
    /**
     * Message sequence number
     *
     * @var int
     */
    public $id;
    /**
     * Message unique identifier
     *
     * @var int
     */
    public $uid;
    /**
     * Message subject
     *
     * @var string
     */
    public $subject;
    /**
     * Message sender (From)
     *
     * @var string
     */
    public $from;
    /**
     * Message recipient (To)
     *
     * @var string
     */
    public $to;
    /**
     * Message additional recipients (Cc)
     *
     * @var string
     */
    public $cc;
    /**
     * Message Reply-To header
     *
     * @var string
     */
    public $replyto;
    /**
     * Message In-Reply-To header
     *
     * @var string
     */
    public $in_reply_to;
    /**
     * Message date (Date)
     *
     * @var string
     */
    public $date;
    /**
     * Message identifier (Message-ID)
     *
     * @var string
     */
    public $messageID;
    /**
     * Message size
     *
     * @var int
     */
    public $size;
    /**
     * Message encoding
     *
     * @var string
     */
    public $encoding;
    /**
     * Message charset
     *
     * @var string
     */
    public $charset;
    /**
     * Message Content-type
     *
     * @var string
     */
    public $ctype;
    /**
     * Message timestamp (based on message date)
     *
     * @var int
     */
    public $timestamp;
    /**
     * IMAP bodystructure string
     *
     * @var string
     */
    public $bodystructure;
    /**
     * IMAP internal date
     *
     * @var string
     */
    public $internaldate;
    /**
     * Message References header
     *
     * @var string
     */
    public $references;
    /**
     * Message priority (X-Priority)
     *
     * @var int
     */
    public $priority;
    /**
     * Message receipt recipient
     *
     * @var string
     */
    public $mdn_to;
    /**
     * Other message headers
     *
     * @var array
     */
    public $others = array();
    /**
     * Message flags
     *
     * @var array
     */
    public $flags = array();
}
/**
 * Class for sorting an array of rcube_message_header objects in a predetermined order.
 *
 * @package Mail
 * @author  Aleksander Machniak <alec@alec.pl>
 */
class rcube_message_header_sorter
{
    private $uids = array();
    /**
     * Set the predetermined sort order.
     *
     * @param array $index  Numerically indexed array of IMAP UIDs
     */
    function set_index($index)
    {
        $index = array_flip($index);
        $this->uids = $index;
    }
    /**
     * Sort the array of header objects
     *
     * @param array $headers Array of rcube_message_header objects indexed by UID
     */
    function sort_headers(&$headers)
    {
        uksort($headers, array($this, "compare_uids"));
    }
    /**
     * Sort method called by uksort()
     *
     * @param int $a Array key (UID)
     * @param int $b Array key (UID)
     */
    function compare_uids($a, $b)
    {
        // then find each sequence number in my ordered list
        $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
        $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1;
        // return the relative position as the comparison value
        return $posa - $posb;
    }
}
program/include/rcube_message_part.php
New file
@@ -0,0 +1,103 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_message_part.php                                |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Class representing a message part                                   |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
 $Id$
*/
/**
 * Class representing a message part
 *
 * @package    Mail
 * @author     Thomas Bruederli <roundcube@gmail.com>
 * @author     Aleksander Machniak <alec@alec.pl>
 * @version    2.0
 */
class rcube_message_part
{
    /**
     * Part MIME identifier
     *
     * @var string
     */
    public $mime_id = '';
    /**
     * Content main type
     *
     * @var string
     */
    public $ctype_primary = 'text';
    /**
     * Content subtype
     *
     * @var string
     */
    public $ctype_secondary = 'plain';
    /**
     * Complete content type
     *
     * @var string
     */
    public $mimetype = 'text/plain';
    public $disposition = '';
    public $filename = '';
    public $encoding = '8bit';
    public $charset = '';
    /**
     * Part size in bytes
     *
     * @var int
     */
    public $size = 0;
    /**
     * Part headers
     *
     * @var array
     */
    public $headers = array();
    public $d_parameters = array();
    public $ctype_parameters = array();
    /**
     * Clone handler.
     */
    function __clone()
    {
        if (isset($this->parts)) {
            foreach ($this->parts as $idx => $part) {
                if (is_object($part)) {
                    $this->parts[$idx] = clone $part;
                }
            }
        }
    }
}
program/include/rcube_output.php
New file
@@ -0,0 +1,259 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_output.php                                      |
 |                                                                       |
 | This file is part of the Roundcube PHP suite                          |
 | Copyright (C) 2005-2012 The Roundcube Dev Team                        |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 | CONTENTS:                                                             |
 |   Abstract class for output generation                                |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
 $Id$
*/
/**
 * Class for output generation
 *
 * @package HTML
 */
abstract class rcube_output
{
    public $browser;
    public $type = 'html';
    public $ajax_call = false;
    public $framed = false;
    protected $app;
    protected $config;
    protected $charset = RCMAIL_CHARSET;
    protected $env = array();
    protected $pagetitle = '';
    protected $object_handlers = array();
    /**
     * Object constructor
     */
    public function __construct($task = null, $framed = false)
    {
        $this->app     = rcmail::get_instance();
        $this->config  = $this->app->config;
        $this->browser = new rcube_browser();
    }
    /**
     * Setter for page title
     *
     * @param string $title Page title
     */
    public function set_pagetitle($title)
    {
        $this->pagetitle = $title;
    }
    /**
     * Setter for output charset.
     * To be specified in a meta tag and sent as http-header
     *
     * @param string $charset Charset name
     */
    public function set_charset($charset)
    {
        $this->charset = $charset;
    }
    /**
     * Getter for output charset
     *
     * @return string Output charset name
     */
    public function get_charset()
    {
        return $this->charset;
    }
    /**
     * Getter for the current skin path property
     */
    public function get_skin_path()
    {
        return $this->config->get('skin_path');
    }
    /**
     * Set environment variable
     *
     * @param string $name   Property name
     * @param mixed  $value  Property value
     */
    public function set_env($name, $value)
    {
        $this->env[$name] = $value;
    }
    /**
     * Environment variable getter.
     *
     * @param string $name  Property name
     *
     * @return mixed Property value
     */
    public function get_env($name)
    {
        return $this->env[$name];
    }
    /**
     * Delete all stored env variables and commands
     */
    public function reset()
    {
        $this->env = array();
        $this->object_handlers = array();
        $this->pagetitle = '';
    }
    /**
     * Call a client method
     *
     * @param string Method to call
     * @param ... Additional arguments
     */
    abstract function command();
    /**
     * Add a localized label to the client environment
     */
    abstract function add_label();
    /**
     * Invoke display_message command
     *
     * @param string  $message  Message to display
     * @param string  $type     Message type [notice|confirm|error]
     * @param array   $vars     Key-value pairs to be replaced in localized text
     * @param boolean $override Override last set message
     * @param int     $timeout  Message displaying time in seconds
     */
    abstract function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0);
    /**
     * Redirect to a certain url.
     *
     * @param mixed $p     Either a string with the action or url parameters as key-value pairs
     * @param int   $delay Delay in seconds
     */
    abstract function redirect($p = array(), $delay = 1);
    /**
     * Send output to the client.
     */
    abstract function send();
    /**
     * Register a template object handler
     *
     * @param  string Object name
     * @param  string Function name to call
     * @return void
     */
    public function add_handler($obj, $func)
    {
        $this->object_handlers[$obj] = $func;
    }
    /**
     * Register a list of template object handlers
     *
     * @param  array Hash array with object=>handler pairs
     * @return void
     */
    public function add_handlers($arr)
    {
        $this->object_handlers = array_merge($this->object_handlers, $arr);
    }
    /**
     * Send HTTP headers to prevent caching a page
     */
    public function nocacheing_headers()
    {
        if (headers_sent()) {
            return;
        }
        header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
        // Request browser to disable DNS prefetching (CVE-2010-0464)
        header("X-DNS-Prefetch-Control: off");
        // We need to set the following headers to make downloads work using IE in HTTPS mode.
        if ($this->browser->ie && rcube_ui::https_check()) {
            header('Pragma: private');
            header("Cache-Control: private, must-revalidate");
        }
        else {
            header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
            header("Pragma: no-cache");
        }
    }
    /**
     * Show error page and terminate script execution
     *
     * @param int    $code     Error code
     * @param string $message  Error message
     */
    public function raise_error($code, $message)
    {
        // STUB: to be overloaded by specific output classes
        fputs(STDERR, "Error $code: $message\n");
        exit(-1);
    }
    /**
     * Convert a variable into a javascript object notation
     *
     * @param mixed Input value
     *
     * @return string Serialized JSON string
     */
    public static function json_serialize($input)
    {
        $input = rcube_charset::clean($input);
        // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
        // that's why we have @ here
        return @json_encode($input);
    }
}
program/include/rcube_output_html.php
File was renamed from program/include/rcube_template.php
@@ -2,10 +2,10 @@
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_template.php                                    |
 | program/include/rcubeoutput_html.php                                  |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2006-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -13,7 +13,6 @@
 |                                                                       |
 | PURPOSE:                                                              |
 |   Class to handle HTML page output using a skin template.             |
 |   Extends rcube_html_page class from rcube_shared.inc                 |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
@@ -28,33 +27,32 @@
 * Class to create HTML page output using a skin template
 *
 * @package View
 * @todo Documentation
 * @uses rcube_html_page
 */
class rcube_template extends rcube_html_page
class rcube_output_html extends rcube_output
{
    private $app;
    private $config;
    private $pagetitle = '';
    private $message = null;
    private $js_env = array();
    private $js_labels = array();
    private $js_commands = array();
    private $object_handlers = array();
    private $plugin_skin_path;
    private $template_name;
    public $browser;
    public $framed = false;
    public $env = array();
    public $type = 'html';
    public $ajax_call = false;
    protected $message = null;
    protected $js_env = array();
    protected $js_labels = array();
    protected $js_commands = array();
    protected $plugin_skin_path;
    protected $template_name;
    protected $scripts_path = '';
    protected $script_files = array();
    protected $css_files = array();
    protected $scripts = array();
    protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
    protected $header = '';
    protected $footer = '';
    protected $body = '';
    protected $base_path = '';
    // deprecated names of templates used before 0.5
    private $deprecated_templates = array(
        'contact' => 'showcontact',
        'contactadd' => 'addcontact',
        'contactedit' => 'editcontact',
    protected $deprecated_templates = array(
        'contact'      => 'showcontact',
        'contactadd'   => 'addcontact',
        'contactedit'  => 'editcontact',
        'identityedit' => 'editidentity',
        'messageprint' => 'printmessage',
    );
@@ -64,20 +62,16 @@
     *
     * @todo   Replace $this->config with the real rcube_config object
     */
    public function __construct($task, $framed = false)
    public function __construct($task = null, $framed = false)
    {
        parent::__construct();
        $this->app = rcmail::get_instance();
        $this->config = $this->app->config->all();
        $this->browser = new rcube_browser();
        //$this->framed = $framed;
        $this->set_env('task', $task);
        $this->set_env('x_frame_options', $this->app->config->get('x_frame_options', 'sameorigin'));
        $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
        // load the correct skin (in case user-defined)
        $this->set_skin($this->config['skin']);
        $this->set_skin($this->config->get('skin'));
        // add common javascripts
        $this->add_script('var '.JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
@@ -101,6 +95,7 @@
        ));
    }
    /**
     * Set environment variable
     *
@@ -116,26 +111,22 @@
        }
    }
    /**
     * Set page title variable
     */
    public function set_pagetitle($title)
    {
        $this->pagetitle = $title;
    }
    /**
     * Getter for the current page title
     *
     * @return string The page title
     */
    public function get_pagetitle()
    protected function get_pagetitle()
    {
        if (!empty($this->pagetitle)) {
            $title = $this->pagetitle;
        }
        else if ($this->env['task'] == 'login') {
            $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
            $title = $this->app->gettext(array(
                'name' => 'welcome',
                'vars' => array('product' => $this->config->get('product_name')
            )));
        }
        else {
            $title = ucfirst($this->env['task']);
@@ -143,6 +134,7 @@
        return $title;
    }
    /**
     * Set skin
@@ -156,23 +148,18 @@
            $valid = true;
        }
        else {
            $skin_path = $this->config['skin_path'] ? $this->config['skin_path'] : 'skins/default';
            $skin_path = $this->config->get('skin_path');
            if (!$skin_path) {
                $skin_path = 'skins/default';
            }
            $valid = !$skin;
        }
        $this->app->config->set('skin_path', $skin_path);
        $this->config['skin_path'] = $skin_path;
        $this->config->set('skin_path', $skin_path);
        return $valid;
    }
    /**
     * Getter for the current skin path property
     */
    public function get_skin_path()
    {
        return $this->config['skin_path'];
    }
    /**
     * Check if a specific template exists
@@ -182,32 +169,10 @@
     */
    public function template_exists($name)
    {
        $filename = $this->config['skin_path'] . '/templates/' . $name . '.html';
        $filename = $this->config->get('skin_path') . '/templates/' . $name . '.html';
        return (is_file($filename) && is_readable($filename)) || ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]));
    }
    /**
     * Register a template object handler
     *
     * @param  string Object name
     * @param  string Function name to call
     * @return void
     */
    public function add_handler($obj, $func)
    {
        $this->object_handlers[$obj] = $func;
    }
    /**
     * Register a list of template object handlers
     *
     * @param  array Hash array with object=>handler pairs
     * @return void
     */
    public function add_handlers($arr)
    {
        $this->object_handlers = array_merge($this->object_handlers, $arr);
    }
    /**
     * Register a GUI object to the client script
@@ -220,6 +185,7 @@
    {
        $this->add_script(JS_OBJECT_NAME.".gui_object('$obj', '$id');");
    }
    /**
     * Call a client method
@@ -236,6 +202,7 @@
          $this->js_commands[] = $cmd;
    }
    /**
     * Add a localized label to the client environment
     */
@@ -246,9 +213,10 @@
          $args = $args[0];
        foreach ($args as $name) {
            $this->js_labels[$name] = rcube_label($name);
            $this->js_labels[$name] = $this->app->gettext($name);
        }
    }
    /**
     * Invoke display_message command
@@ -263,10 +231,10 @@
    public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
    {
        if ($override || !$this->message) {
            if (rcube_label_exists($message)) {
            if ($this->app->text_exists($message)) {
                if (!empty($vars))
                    $vars = array_map('Q', $vars);
                $msgtext = rcube_label(array('name' => $message, 'vars' => $vars));
                $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
            }
            else
                $msgtext = $message;
@@ -276,38 +244,37 @@
        }
    }
    /**
     * Delete all stored env variables and commands
     *
     * @return void
     * @uses   rcube_html::reset()
     * @uses   self::$env
     * @uses   self::$js_env
     * @uses   self::$js_commands
     * @uses   self::$object_handlers
     */
    public function reset()
    {
        $this->env = array();
        parent::reset();
        $this->js_env = array();
        $this->js_labels = array();
        $this->js_commands = array();
        $this->object_handlers = array();
        parent::reset();
        $this->script_files = array();
        $this->scripts      = array();
        $this->header       = '';
        $this->footer       = '';
        $this->body         = '';
    }
    /**
     * Redirect to a certain url
     *
     * @param mixed Either a string with the action or url parameters as key-value pairs
     * @see rcmail::url()
     * @param mixed $p     Either a string with the action or url parameters as key-value pairs
     * @param int   $delay Delay in seconds
     */
    public function redirect($p = array())
    public function redirect($p = array(), $delay = 1)
    {
        $location = $this->app->url($p);
        header('Location: ' . $location);
        exit;
    }
    /**
     * Send the request output to the client.
@@ -321,7 +288,7 @@
        if ($templ != 'iframe') {
            // prevent from endless loops
            if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
                raise_error(array('code' => 505, 'type' => 'php',
                rcube::raise_error(array('code' => 505, 'type' => 'php',
                  'file' => __FILE__, 'line' => __LINE__,
                  'message' => 'Recursion alert: ignoring output->send()'), true, false);
                return;
@@ -342,12 +309,11 @@
        }
    }
    /**
     * Process template and write to stdOut
     *
     * @param string HTML template
     * @see rcube_html_page::write()
     * @override
     * @param string $template HTML template content
     */
    public function write($template = '')
    {
@@ -374,8 +340,9 @@
            header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
        // call super method
        parent::write($template, $this->config['skin_path']);
        $this->_write($template, $this->config->get('skin_path'));
    }
    /**
     * Parse a specific skin template and deliver to stdout (or return)
@@ -388,7 +355,7 @@
     */
    function parse($name = 'main', $exit = true, $write = true)
    {
        $skin_path = $this->config['skin_path'];
        $skin_path = $this->config->get('skin_path');
        $plugin    = false;
        $realname  = $name;
        $temp      = explode('.', $name, 2);
@@ -399,7 +366,7 @@
        if (count($temp) > 1) {
            $plugin    = $temp[0];
            $name      = $temp[1];
            $skin_dir  = $plugin . '/skins/' . $this->config['skin'];
            $skin_dir  = $plugin . '/skins/' . $this->config->get('skin');
            $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir;
            // fallback to default skin
@@ -414,16 +381,16 @@
        if (!is_readable($path) && $this->deprecated_templates[$realname]) {
            $path = "$skin_path/templates/".$this->deprecated_templates[$realname].".html";
            if (is_readable($path))
                raise_error(array('code' => 502, 'type' => 'php',
                rcube::raise_error(array('code' => 502, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Using deprecated template '".$this->deprecated_templates[$realname]
                        ."' in ".$this->config['skin_path']."/templates. Please rename to '".$realname."'"),
                        ."' in $skin_path/templates. Please rename to '".$realname."'"),
                true, false);
        }
        // read template file
        if (($templ = @file_get_contents($path)) === false) {
            raise_error(array(
            rcube::raise_error(array(
                'code' => 501,
                'type' => 'php',
                'line' => __LINE__,
@@ -458,7 +425,7 @@
        if ($write) {
            // add debug console
            if ($realname != 'error' && ($this->config['debug_level'] & 8)) {
            if ($realname != 'error' && ($this->config->get('debug_level') & 8)) {
                $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;display:none">
                    <a href="#toggle" onclick="con=$(\'#dbgconsole\');con[con.is(\':visible\')?\'hide\':\'show\']();return false">console</a>
                    <textarea name="console" id="dbgconsole" rows="20" cols="40" style="display:none;width:400px;border:none;font-size:10px" spellcheck="false"></textarea></div>'
@@ -480,16 +447,17 @@
        }
    }
    /**
     * Return executable javascript code for all registered commands
     *
     * @return string $out
     */
    private function get_js_commands()
    protected function get_js_commands()
    {
        $out = '';
        if (!$this->framed && !empty($this->js_env)) {
            $out .= JS_OBJECT_NAME . '.set_env('.json_serialize($this->js_env).");\n";
            $out .= JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
        }
        if (!empty($this->js_labels)) {
            $this->command('add_label', $this->js_labels);
@@ -497,7 +465,7 @@
        foreach ($this->js_commands as $i => $args) {
            $method = array_shift($args);
            foreach ($args as $i => $arg) {
                $args[$i] = json_serialize($arg);
                $args[$i] = self::json_serialize($arg);
            }
            $parent = $this->framed || preg_match('/^parent\./', $method);
            $out .= sprintf(
@@ -511,6 +479,7 @@
        return $out;
    }
    /**
     * Make URLs starting with a slash point to skin directory
     *
@@ -520,9 +489,27 @@
    public function abs_url($str)
    {
        if ($str[0] == '/')
            return $this->config['skin_path'] . $str;
            return $this->config->get('skin_path') . $str;
        else
            return $str;
    }
    /**
     * Show error page and terminate script execution
     *
     * @param int    $code     Error code
     * @param string $message  Error message
     */
    public function raise_error($code, $message)
    {
        global $__page_content, $ERROR_CODE, $ERROR_MESSAGE;
        $ERROR_CODE    = $code;
        $ERROR_MESSAGE = $message;
        include INSTALL_PATH . 'program/steps/utils/error.inc';
        exit;
    }
@@ -532,29 +519,32 @@
     * Replace all strings ($varname)
     * with the content of the according global variable.
     */
    private function parse_with_globals($input)
    protected function parse_with_globals($input)
    {
        $GLOBALS['__version'] = Q(RCMAIL_VERSION);
        $GLOBALS['__comm_path'] = Q($this->app->comm_path);
        $GLOBALS['__skin_path'] = Q($this->config['skin_path']);
        $GLOBALS['__version']   = html::quote(RCMAIL_VERSION);
        $GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
        $GLOBALS['__skin_path'] = Q($this->config->get('skin_path'));
        return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
        array($this, 'globals_callback'), $input);
            array($this, 'globals_callback'), $input);
    }
    /**
     * Callback funtion for preg_replace_callback() in parse_with_globals()
     */
    private function globals_callback($matches)
    protected function globals_callback($matches)
    {
        return $GLOBALS[$matches[1]];
    }
    /**
     * Public wrapper to dipp into template parsing.
     *
     * @param  string $input
     * @return string
     * @uses   rcube_template::parse_xml()
     * @uses   rcube_output_html::parse_xml()
     * @since  0.1-rc1
     */
    public function just_parse($input)
@@ -562,20 +552,21 @@
        return $this->parse_xml($input);
    }
    /**
     * Parse for conditional tags
     *
     * @param  string $input
     * @return string
     */
    private function parse_conditions($input)
    protected function parse_conditions($input)
    {
        $matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>\n?/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE);
        if ($matches && count($matches) == 4) {
            if (preg_match('/^(else|endif)$/i', $matches[1])) {
                return $matches[0] . $this->parse_conditions($matches[3]);
            }
            $attrib = parse_attrib_string($matches[2]);
            $attrib = html::parse_attrib_string($matches[2]);
            if (isset($attrib['condition'])) {
                $condmet = $this->check_condition($attrib['condition']);
                $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>\n?/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
@@ -588,7 +579,7 @@
                }
                return $matches[0] . $this->parse_conditions($result);
            }
            raise_error(array(
            rcube::raise_error(array(
                'code' => 500,
                'type' => 'php',
                'line' => __LINE__,
@@ -608,7 +599,7 @@
     * @param  string Condition statement
     * @return boolean True if condition is met, False if not
     */
    private function check_condition($condition)
    protected function check_condition($condition)
    {
        return eval("return (".$this->parse_expression($condition).");");
    }
@@ -617,10 +608,10 @@
    /**
     * Inserts hidden field with CSRF-prevention-token into POST forms
     */
    private function alter_form_tag($matches)
    protected function alter_form_tag($matches)
    {
        $out = $matches[0];
        $attrib  = parse_attrib_string($matches[1]);
        $out    = $matches[0];
        $attrib = html::parse_attrib_string($matches[1]);
        if (strtolower($attrib['method']) == 'post') {
            $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
@@ -637,7 +628,7 @@
     * @param  string Expression statement
     * @return string Expression value
     */
    private function parse_expression($expression)
    protected function parse_expression($expression)
    {
        return preg_replace(
            array(
@@ -653,7 +644,7 @@
                "\$_SESSION['\\1']",
                "\$this->app->config->get('\\1',get_boolean('\\3'))",
                "\$this->env['\\1']",
                "get_input_value('\\1', RCUBE_INPUT_GPC)",
                "rcube_ui::get_input_value('\\1', rcube_ui::INPUT_GPC)",
                "\$_COOKIE['\\1']",
                "\$this->browser->{'\\1'}",
                $this->template_name,
@@ -671,7 +662,7 @@
     * @todo   Use DOM-parser to traverse template HTML
     * @todo   Maybe a cache.
     */
    private function parse_xml($input)
    protected function parse_xml($input)
    {
        return preg_replace_callback('/<roundcube:([-_a-z]+)\s+((?:[^>]|\\\\>)+)(?<!\\\\)>/Ui', array($this, 'xml_command'), $input);
    }
@@ -684,10 +675,10 @@
     * @param  array Matches array of preg_replace_callback
     * @return string Tag/Object content
     */
    private function xml_command($matches)
    protected function xml_command($matches)
    {
        $command = strtolower($matches[1]);
        $attrib  = parse_attrib_string($matches[2]);
        $attrib  = html::parse_attrib_string($matches[2]);
        // empty output if required condition is not met
        if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
@@ -706,20 +697,20 @@
            // show a label
            case 'label':
                if ($attrib['name'] || $attrib['command']) {
                    $vars = $attrib + array('product' => $this->config['product_name']);
                    $vars = $attrib + array('product' => $this->config->get('product_name'));
                    unset($vars['name'], $vars['command']);
                    $label = rcube_label($attrib + array('vars' => $vars));
                    return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : Q($label)) : '';
                    $label = $this->app->gettext($attrib + array('vars' => $vars));
                    return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : html::quote($label)) : '';
                }
                break;
            // include a file
            case 'include':
                if (!$this->plugin_skin_path || !is_file($path = realpath($this->plugin_skin_path . $attrib['file'])))
                    $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config['skin_path']).$attrib['file']);
                    $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config->get('skin_path')).$attrib['file']);
                if (is_readable($path)) {
                    if ($this->config['skin_include_php']) {
                    if ($this->config->get('skin_include_php')) {
                        $incl = $this->include_php($path);
                    }
                    else {
@@ -765,13 +756,13 @@
                }
                else if ($object == 'logo') {
                    $attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
                    if ($this->config['skin_logo'])
                        $attrib['src'] = $this->config['skin_logo'];
                    if ($logo = $this->config->get('skin_logo'))
                        $attrib['src'] = $logo;
                    $content = html::img($attrib);
                }
                else if ($object == 'productname') {
                    $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'Roundcube Webmail';
                    $content = Q($name);
                    $name = $this->config->get('product_name', 'Roundcube Webmail');
                    $content = html::quote($name);
                }
                else if ($object == 'version') {
                    $ver = (string)RCMAIL_VERSION;
@@ -779,20 +770,20 @@
                        if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
                          $ver .= ' [SVN r'.$regs[1].']';
                    }
                    $content = Q($ver);
                    $content = html::quote($ver);
                }
                else if ($object == 'steptitle') {
                  $content = Q($this->get_pagetitle());
                  $content = html::quote($this->get_pagetitle());
                }
                else if ($object == 'pagetitle') {
                    if (!empty($this->config['devel_mode']) && !empty($_SESSION['username']))
                      $title = $_SESSION['username'].' :: ';
                    else if (!empty($this->config['product_name']))
                      $title = $this->config['product_name'].' :: ';
                    if ($this->config->get('devel_mode') && !empty($_SESSION['username']))
                        $title = $_SESSION['username'].' :: ';
                    else if ($prod_name = $this->config->get('product_name'))
                        $title = $prod_name . ' :: ';
                    else
                      $title = '';
                        $title = '';
                    $title .= $this->get_pagetitle();
                    $content = Q($title);
                    $content = html::quote($title);
                }
                // exec plugin hooks for this template object
@@ -802,7 +793,7 @@
            // return code for a specified eval expression
            case 'exp':
                $value = $this->parse_expression($attrib['expression']);
                return eval("return Q($value);");
                return eval("return html::quote($value);");
            // return variable
            case 'var':
@@ -815,13 +806,13 @@
                        $value = $this->env[$name];
                        break;
                    case 'config':
                        $value = $this->config[$name];
                        $value = $this->config->get($name);
                        if (is_array($value) && $value[$_SESSION['storage_host']]) {
                            $value = $value[$_SESSION['storage_host']];
                        }
                        break;
                    case 'request':
                        $value = get_input_value($name, RCUBE_INPUT_GPC);
                        $value = rcube_ui::get_input_value($name, rcube_ui::INPUT_GPC);
                        break;
                    case 'session':
                        $value = $_SESSION[$name];
@@ -838,11 +829,12 @@
                    $value = implode(', ', $value);
                }
                return Q($value);
                return html::quote($value);
                break;
        }
        return '';
    }
    /**
     * Include a specific file and return it's contents
@@ -850,7 +842,7 @@
     * @param string File path
     * @return string Contents of the processed file
     */
    private function include_php($file)
    protected function include_php($file)
    {
        ob_start();
        include $file;
@@ -859,6 +851,7 @@
        return $out;
    }
    /**
     * Create and register a button
@@ -901,13 +894,13 @@
        }
        // get localized text for labels and titles
        if ($attrib['title']) {
            $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain']));
            $attrib['title'] = html::quote($this->app->gettext($attrib['title'], $attrib['domain']));
        }
        if ($attrib['label']) {
            $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain']));
            $attrib['label'] = html::quote($this->app->gettext($attrib['label'], $attrib['domain']));
        }
        if ($attrib['alt']) {
            $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain']));
            $attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain']));
        }
        // set title to alt attribute for IE browsers
@@ -935,14 +928,14 @@
            // make valid href to specific buttons
            if (in_array($attrib['command'], rcmail::$main_tasks)) {
                $attrib['href'] = rcmail_url(null, null, $attrib['command']);
                $attrib['href']    = $this->app->url(array('task' => $attrib['command']));
                $attrib['onclick'] = sprintf("%s.command('switch-task','%s');return false", JS_OBJECT_NAME, $attrib['command']);
            }
            else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
                $attrib['href'] = rcmail_url($attrib['command'], null, $attrib['task']);
                $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
            }
            else if (in_array($attrib['command'], $a_static_commands)) {
                $attrib['href'] = rcmail_url($attrib['command']);
                $attrib['href'] = $this->app->url(array('action' => $attrib['command']));
            }
            else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
              $attrib['href'] = $this->env['permaurl'];
@@ -969,7 +962,7 @@
        $out = '';
        // generate image tag
        if ($attrib['type']=='image') {
        if ($attrib['type'] == 'image') {
            $attrib_str = html::attrib_string(
                $attrib,
                array(
@@ -983,13 +976,13 @@
            }
            $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
        }
        else if ($attrib['type']=='link') {
        else if ($attrib['type'] == 'link') {
            $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
            $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target');
            if ($attrib['innerclass'])
                $btn_content = html::span($attrib['innerclass'], $btn_content);
        }
        else if ($attrib['type']=='input') {
        else if ($attrib['type'] == 'input') {
            $attrib['type'] = 'button';
            if ($attrib['label']) {
@@ -1009,6 +1002,244 @@
        }
        return $out;
    }
    /**
     * Link an external script file
     *
     * @param string File URL
     * @param string Target position [head|foot]
     */
    public function include_script($file, $position='head')
    {
        static $sa_files = array();
        if (!preg_match('|^https?://|i', $file) && $file[0] != '/') {
            $file = $this->scripts_path . $file;
            if ($fs = @filemtime($file)) {
                $file .= '?s=' . $fs;
            }
        }
        if (in_array($file, $sa_files)) {
            return;
        }
        $sa_files[] = $file;
        if (!is_array($this->script_files[$position])) {
            $this->script_files[$position] = array();
        }
        $this->script_files[$position][] = $file;
    }
    /**
     * Add inline javascript code
     *
     * @param string JS code snippet
     * @param string Target position [head|head_top|foot]
     */
    public function add_script($script, $position='head')
    {
        if (!isset($this->scripts[$position])) {
            $this->scripts[$position] = "\n" . rtrim($script);
        }
        else {
            $this->scripts[$position] .= "\n" . rtrim($script);
        }
    }
    /**
     * Link an external css file
     *
     * @param string File URL
     */
    public function include_css($file)
    {
        $this->css_files[] = $file;
    }
    /**
     * Add HTML code to the page header
     *
     * @param string $str HTML code
     */
    public function add_header($str)
    {
        $this->header .= "\n" . $str;
    }
    /**
     * Add HTML code to the page footer
     * To be added right befor </body>
     *
     * @param string $str HTML code
     */
    public function add_footer($str)
    {
        $this->footer .= "\n" . $str;
    }
    /**
     * Process template and write to stdOut
     *
     * @param string HTML template
     * @param string Base for absolute paths
     */
    public function _write($templ = '', $base_path = '')
    {
        $output = empty($templ) ? $this->default_template : trim($templ);
        // set default page title
        if (empty($this->pagetitle)) {
            $this->pagetitle = 'Roundcube Mail';
        }
        // replace specialchars in content
        $page_title  = html::quote($this->pagetitle);
        $page_header = '';
        $page_footer = '';
        // include meta tag with charset
        if (!empty($this->charset)) {
            if (!headers_sent()) {
                header('Content-Type: text/html; charset=' . $this->charset);
            }
            $page_header = '<meta http-equiv="content-type"';
            $page_header.= ' content="text/html; charset=';
            $page_header.= $this->charset . '" />'."\n";
        }
        // definition of the code to be placed in the document header and footer
        if (is_array($this->script_files['head'])) {
            foreach ($this->script_files['head'] as $file) {
                $page_header .= html::script($file);
            }
        }
        $head_script = $this->scripts['head_top'] . $this->scripts['head'];
        if (!empty($head_script)) {
            $page_header .= html::script(array(), $head_script);
        }
        if (!empty($this->header)) {
            $page_header .= $this->header;
        }
        // put docready commands into page footer
        if (!empty($this->scripts['docready'])) {
            $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
        }
        if (is_array($this->script_files['foot'])) {
            foreach ($this->script_files['foot'] as $file) {
                $page_footer .= html::script($file);
            }
        }
        if (!empty($this->footer)) {
            $page_footer .= $this->footer . "\n";
        }
        if (!empty($this->scripts['foot'])) {
            $page_footer .= html::script(array(), $this->scripts['foot']);
        }
        // find page header
        if ($hpos = stripos($output, '</head>')) {
            $page_header .= "\n";
        }
        else {
            if (!is_numeric($hpos)) {
                $hpos = stripos($output, '<body');
            }
            if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) {
                while ($output[$hpos] != '>') {
                    $hpos++;
                }
                $hpos++;
            }
            $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\n";
        }
        // add page hader
        if ($hpos) {
            $output = substr_replace($output, $page_header, $hpos, 0);
        }
        else {
            $output = $page_header . $output;
        }
        // add page footer
        if (($fpos = strripos($output, '</body>')) || ($fpos = strripos($output, '</html>'))) {
            $output = substr_replace($output, $page_footer."\n", $fpos, 0);
        }
        else {
            $output .= "\n".$page_footer;
        }
        // add css files in head, before scripts, for speed up with parallel downloads
        if (!empty($this->css_files) &&
            (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>')))
        ) {
            $css = '';
            foreach ($this->css_files as $file) {
                $css .= html::tag('link', array('rel' => 'stylesheet',
                    'type' => 'text/css', 'href' => $file, 'nl' => true));
            }
            $output = substr_replace($output, $css, $pos, 0);
        }
        $this->base_path = $base_path;
        // correct absolute paths in images and other tags
        // add timestamp to .js and .css filename
        $output = preg_replace_callback(
            '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
            array($this, 'file_callback'), $output);
        // trigger hook with final HTML content to be sent
        $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output));
        if (!$hook['abort']) {
            if ($this->charset != RCMAIL_CHARSET) {
                echo rcube_charset::convert($hook['content'], RCMAIL_CHARSET, $this->charset);
            }
            else {
                echo $hook['content'];
            }
        }
    }
    /**
     * Callback function for preg_replace_callback in write()
     *
     * @return string Parsed string
     */
    protected function file_callback($matches)
    {
        $file = $matches[3];
        // correct absolute paths
        if ($file[0] == '/') {
            $file = $this->base_path . $file;
        }
        // add file modification timestamp
        if (preg_match('/\.(js|css)$/', $file)) {
            if ($fs = @filemtime($file)) {
                $file .= '?s=' . $fs;
            }
        }
        return $matches[1] . '=' . $matches[2] . $file . $matches[4];
    }
@@ -1108,15 +1339,15 @@
     * @param array Named parameters
     * @return string HTML code for the gui object
     */
    private function login_form($attrib)
    protected function login_form($attrib)
    {
        $default_host = $this->config['default_host'];
        $autocomplete = (int) $this->config['login_autocomplete'];
        $default_host = $this->config->get('default_host');
        $autocomplete = (int) $this->config->get('login_autocomplete');
        $_SESSION['temp'] = true;
        // save original url
        $url = get_input_value('_url', RCUBE_INPUT_POST);
        $url = rcube_ui::get_input_value('_url', rcube_ui::INPUT_POST);
        if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
            $url = $_SERVER['QUERY_STRING'];
@@ -1165,16 +1396,16 @@
        // create HTML table with two cols
        $table = new html_table(array('cols' => 2));
        $table->add('title', html::label('rcmloginuser', Q(rcube_label('username'))));
        $table->add('input', $input_user->show(get_input_value('_user', RCUBE_INPUT_GPC)));
        $table->add('title', html::label('rcmloginuser', html::quote($this->app->gettext('username'))));
        $table->add('input', $input_user->show(rcube_ui::get_input_value('_user', rcube_ui::INPUT_GPC)));
        $table->add('title', html::label('rcmloginpwd', Q(rcube_label('password'))));
        $table->add('title', html::label('rcmloginpwd', html::quote($this->app->gettext('password'))));
        $table->add('input', $input_pass->show());
        // add host selection row
        if (is_object($input_host) && !$hide_host) {
            $table->add('title', html::label('rcmloginhost', Q(rcube_label('server'))));
            $table->add('input', $input_host->show(get_input_value('_host', RCUBE_INPUT_GPC)));
            $table->add('title', html::label('rcmloginhost', html::quote($this->app->gettext('server'))));
            $table->add('input', $input_host->show(rcube_ui::get_input_value('_host', rcube_ui::INPUT_GPC)));
        }
        $out  = $input_task->show();
@@ -1204,7 +1435,7 @@
     * @param array Named parameters
     * @return void
     */
    private function preloader($attrib)
    protected function preloader($attrib)
    {
        $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
        $images = array_map(array($this, 'abs_url'), $images);
@@ -1212,7 +1443,7 @@
        if (empty($images) || $this->app->task == 'logout')
            return;
        $this->add_script('var images = ' . json_serialize($images) .';
        $this->add_script('var images = ' . self::json_serialize($images) .';
            for (var i=0; i<images.length; i++) {
                img = new Image();
                img.src = images[i];
@@ -1227,7 +1458,7 @@
     * @param array Named parameters
     * @return string HTML code for the gui object
     */
    private function search_form($attrib)
    protected function search_form($attrib)
    {
        // add some labels to client
        $this->add_label('searching');
@@ -1265,14 +1496,15 @@
     * @param array Named tag parameters
     * @return string HTML code for the gui object
     */
    private function message_container($attrib)
    protected function message_container($attrib)
    {
        if (isset($attrib['id']) === false) {
            $attrib['id'] = 'rcmMessageContainer';
        }
        $this->add_gui_object('message', $attrib['id']);
        return html::div($attrib, "");
        return html::div($attrib, '');
    }
@@ -1282,7 +1514,7 @@
     * @param array Named parameters for the select tag
     * @return string HTML code for the gui object
     */
    function charset_selector($attrib)
    public function charset_selector($attrib)
    {
        // pass the following attributes to the form class
        $field_attrib = array('name' => '_charset');
@@ -1293,39 +1525,39 @@
        }
        $charsets = array(
            'UTF-8'        => 'UTF-8 ('.rcube_label('unicode').')',
            'US-ASCII'     => 'ASCII ('.rcube_label('english').')',
            'ISO-8859-1'   => 'ISO-8859-1 ('.rcube_label('westerneuropean').')',
            'ISO-8859-2'   => 'ISO-8859-2 ('.rcube_label('easterneuropean').')',
            'ISO-8859-4'   => 'ISO-8859-4 ('.rcube_label('baltic').')',
            'ISO-8859-5'   => 'ISO-8859-5 ('.rcube_label('cyrillic').')',
            'ISO-8859-6'   => 'ISO-8859-6 ('.rcube_label('arabic').')',
            'ISO-8859-7'   => 'ISO-8859-7 ('.rcube_label('greek').')',
            'ISO-8859-8'   => 'ISO-8859-8 ('.rcube_label('hebrew').')',
            'ISO-8859-9'   => 'ISO-8859-9 ('.rcube_label('turkish').')',
            'ISO-8859-10'   => 'ISO-8859-10 ('.rcube_label('nordic').')',
            'ISO-8859-11'   => 'ISO-8859-11 ('.rcube_label('thai').')',
            'ISO-8859-13'   => 'ISO-8859-13 ('.rcube_label('baltic').')',
            'ISO-8859-14'   => 'ISO-8859-14 ('.rcube_label('celtic').')',
            'ISO-8859-15'   => 'ISO-8859-15 ('.rcube_label('westerneuropean').')',
            'ISO-8859-16'   => 'ISO-8859-16 ('.rcube_label('southeasterneuropean').')',
            'WINDOWS-1250' => 'Windows-1250 ('.rcube_label('easterneuropean').')',
            'WINDOWS-1251' => 'Windows-1251 ('.rcube_label('cyrillic').')',
            'WINDOWS-1252' => 'Windows-1252 ('.rcube_label('westerneuropean').')',
            'WINDOWS-1253' => 'Windows-1253 ('.rcube_label('greek').')',
            'WINDOWS-1254' => 'Windows-1254 ('.rcube_label('turkish').')',
            'WINDOWS-1255' => 'Windows-1255 ('.rcube_label('hebrew').')',
            'WINDOWS-1256' => 'Windows-1256 ('.rcube_label('arabic').')',
            'WINDOWS-1257' => 'Windows-1257 ('.rcube_label('baltic').')',
            'WINDOWS-1258' => 'Windows-1258 ('.rcube_label('vietnamese').')',
            'ISO-2022-JP'  => 'ISO-2022-JP ('.rcube_label('japanese').')',
            'ISO-2022-KR'  => 'ISO-2022-KR ('.rcube_label('korean').')',
            'ISO-2022-CN'  => 'ISO-2022-CN ('.rcube_label('chinese').')',
            'EUC-JP'       => 'EUC-JP ('.rcube_label('japanese').')',
            'EUC-KR'       => 'EUC-KR ('.rcube_label('korean').')',
            'EUC-CN'       => 'EUC-CN ('.rcube_label('chinese').')',
            'BIG5'         => 'BIG5 ('.rcube_label('chinese').')',
            'GB2312'       => 'GB2312 ('.rcube_label('chinese').')',
            'UTF-8'        => 'UTF-8 ('.$this->app->gettext('unicode').')',
            'US-ASCII'     => 'ASCII ('.$this->app->gettext('english').')',
            'ISO-8859-1'   => 'ISO-8859-1 ('.$this->app->gettext('westerneuropean').')',
            'ISO-8859-2'   => 'ISO-8859-2 ('.$this->app->gettext('easterneuropean').')',
            'ISO-8859-4'   => 'ISO-8859-4 ('.$this->app->gettext('baltic').')',
            'ISO-8859-5'   => 'ISO-8859-5 ('.$this->app->gettext('cyrillic').')',
            'ISO-8859-6'   => 'ISO-8859-6 ('.$this->app->gettext('arabic').')',
            'ISO-8859-7'   => 'ISO-8859-7 ('.$this->app->gettext('greek').')',
            'ISO-8859-8'   => 'ISO-8859-8 ('.$this->app->gettext('hebrew').')',
            'ISO-8859-9'   => 'ISO-8859-9 ('.$this->app->gettext('turkish').')',
            'ISO-8859-10'   => 'ISO-8859-10 ('.$this->app->gettext('nordic').')',
            'ISO-8859-11'   => 'ISO-8859-11 ('.$this->app->gettext('thai').')',
            'ISO-8859-13'   => 'ISO-8859-13 ('.$this->app->gettext('baltic').')',
            'ISO-8859-14'   => 'ISO-8859-14 ('.$this->app->gettext('celtic').')',
            'ISO-8859-15'   => 'ISO-8859-15 ('.$this->app->gettext('westerneuropean').')',
            'ISO-8859-16'   => 'ISO-8859-16 ('.$this->app->gettext('southeasterneuropean').')',
            'WINDOWS-1250' => 'Windows-1250 ('.$this->app->gettext('easterneuropean').')',
            'WINDOWS-1251' => 'Windows-1251 ('.$this->app->gettext('cyrillic').')',
            'WINDOWS-1252' => 'Windows-1252 ('.$this->app->gettext('westerneuropean').')',
            'WINDOWS-1253' => 'Windows-1253 ('.$this->app->gettext('greek').')',
            'WINDOWS-1254' => 'Windows-1254 ('.$this->app->gettext('turkish').')',
            'WINDOWS-1255' => 'Windows-1255 ('.$this->app->gettext('hebrew').')',
            'WINDOWS-1256' => 'Windows-1256 ('.$this->app->gettext('arabic').')',
            'WINDOWS-1257' => 'Windows-1257 ('.$this->app->gettext('baltic').')',
            'WINDOWS-1258' => 'Windows-1258 ('.$this->app->gettext('vietnamese').')',
            'ISO-2022-JP'  => 'ISO-2022-JP ('.$this->app->gettext('japanese').')',
            'ISO-2022-KR'  => 'ISO-2022-KR ('.$this->app->gettext('korean').')',
            'ISO-2022-CN'  => 'ISO-2022-CN ('.$this->app->gettext('chinese').')',
            'EUC-JP'       => 'EUC-JP ('.$this->app->gettext('japanese').')',
            'EUC-KR'       => 'EUC-KR ('.$this->app->gettext('korean').')',
            'EUC-CN'       => 'EUC-CN ('.$this->app->gettext('chinese').')',
            'BIG5'         => 'BIG5 ('.$this->app->gettext('chinese').')',
            'GB2312'       => 'GB2312 ('.$this->app->gettext('chinese').')',
        );
        if (!empty($_POST['_charset']))
@@ -1348,7 +1580,7 @@
    /**
     * Include content from config/about.<LANG>.html if available
     */
    private function about_content($attrib)
    protected function about_content($attrib)
    {
        $content = '';
        $filenames = array(
@@ -1369,6 +1601,4 @@
        return $content;
    }
}  // end class rcube_template
}
program/include/rcube_output_json.php
File was renamed from program/include/rcube_json_output.php
@@ -2,21 +2,20 @@
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_json_output.php                                 |
 | program/include/rcube_output_json.php                                 |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2010, The Roundcube Dev Team                       |
 | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Class to handle HTML page output using a skin template.             |
 |   Extends rcube_html_page class from rcube_shared.inc                 |
 |                                                                       |
 |   Class to handle JSON (AJAX) output                                  |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
 $Id$
@@ -29,34 +28,15 @@
 *
 * @package View
 */
class rcube_json_output
class rcube_output_json extends rcube_output
{
    /**
     * Stores configuration object.
     *
     * @var rcube_config
     */
    private $config;
    private $charset = RCMAIL_CHARSET;
    private $texts = array();
    private $commands = array();
    private $callbacks = array();
    private $message = null;
    protected $texts = array();
    protected $commands = array();
    protected $callbacks = array();
    protected $message = null;
    public $browser;
    public $env = array();
    public $type = 'js';
    public $ajax_call = true;
    /**
     * Constructor
     */
    public function __construct($task=null)
    {
        $this->config  = rcmail::get_instance()->config;
        $this->browser = new rcube_browser();
    }
    /**
@@ -88,31 +68,10 @@
    /**
     * @ignore
     */
    function set_charset($charset)
    {
        // ignore: $this->charset = $charset;
    }
    /**
     * Get charset for output
     *
     * @return string Output charset
     */
    function get_charset()
    {
        return $this->charset;
    }
    /**
     * Register a template object handler
     *
     * @param  string $obj Object name
     * @param  string $func Function name to call
     * @return void
     */
    public function add_handler($obj, $func)
    {
@@ -124,7 +83,6 @@
     * Register a list of template object handlers
     *
     * @param  array $arr Hash array with object=>handler pairs
     * @return void
     */
    public function add_handlers($arr)
    {
@@ -159,7 +117,7 @@
            $args = $args[0];
        foreach ($args as $name) {
            $this->texts[$name] = rcube_label($name);
            $this->texts[$name] = $this->app->gettext($name);
        }
    }
@@ -177,10 +135,11 @@
    public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
    {
        if ($override || !$this->message) {
            if (rcube_label_exists($message)) {
                if (!empty($vars))
                    $vars = array_map('Q', $vars);
                $msgtext = rcube_label(array('name' => $message, 'vars' => $vars));
            if ($this->app->text_exists($message)) {
                if (!empty($vars)) {
                    $vars = array_map(array('rcube_ui', 'Q'), $vars);
                }
                $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
            }
            else
                $msgtext = $message;
@@ -196,7 +155,7 @@
     */
    public function reset()
    {
        $this->env = array();
        parent::reset();
        $this->texts = array();
        $this->commands = array();
    }
@@ -228,6 +187,20 @@
    /**
     * Show error page and terminate script execution
     *
     * @param int    $code     Error code
     * @param string $message  Error message
     */
    public function raise_error($code, $message)
    {
        $this->show_message("Application Error ($code): $message", 'error');
        $this->remote_response();
        exit;
    }
    /**
     * Send an AJAX response with executable JS code
     *
     * @param  string  $add Additional JS code
@@ -235,13 +208,13 @@
     * @return void
     * @deprecated
     */
    public function remote_response($add='')
    protected function remote_response($add='')
    {
        static $s_header_sent = false;
        if (!$s_header_sent) {
            $s_header_sent = true;
            send_nocacheing_headers();
            $this->nocacheing_headers();
            header('Content-Type: text/plain; charset=' . $this->get_charset());
        }
@@ -251,7 +224,7 @@
        $rcmail = rcmail::get_instance();
        $response['action'] = $rcmail->action;
        if ($unlock = get_input_value('_unlock', RCUBE_INPUT_GPC)) {
        if ($unlock = rcube_ui::get_input_value('_unlock', rcube_ui::INPUT_GPC)) {
            $response['unlock'] = $unlock;
        }
@@ -267,7 +240,7 @@
        if (!empty($this->callbacks))
            $response['callbacks'] = $this->callbacks;
        echo json_serialize($response);
        echo self::json_serialize($response);
    }
@@ -276,14 +249,14 @@
     *
     * @return string $out
     */
    private function get_js_commands()
    protected function get_js_commands()
    {
        $out = '';
        foreach ($this->commands as $i => $args) {
            $method = array_shift($args);
            foreach ($args as $i => $arg) {
                $args[$i] = json_serialize($arg);
                $args[$i] = self::json_serialize($arg);
            }
            $out .= sprintf(
program/include/rcube_plugin.php
@@ -110,9 +110,10 @@
  public function load_config($fname = 'config.inc.php')
  {
    $fpath = $this->home.'/'.$fname;
    $rcmail = rcmail::get_instance();
    $rcmail = rcube::get_instance();
    if (is_file($fpath) && !$rcmail->config->load_from_file($fpath)) {
      raise_error(array('code' => 527, 'type' => 'php',
      rcube::raise_error(array(
        'code' => 527, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Failed to load config from $fpath"), true, false);
      return false;
@@ -176,7 +177,7 @@
      foreach ($texts as $key => $value)
        $add[$domain.'.'.$key] = $value;
      $rcmail = rcmail::get_instance();
      $rcmail = rcube::get_instance();
      $rcmail->load_language($lang, $add);
      // add labels to client
@@ -196,7 +197,7 @@
   */
  public function gettext($p)
  {
    return rcmail::get_instance()->gettext($p, $this->ID);
    return rcube::get_instance()->gettext($p, $this->ID);
  }
  /**
@@ -309,9 +310,10 @@
   */
  public function local_skin_path()
  {
      $skin_path = 'skins/'.$this->api->config->get('skin');
      if (!is_dir(realpath(slashify($this->home) . $skin_path)))
        $skin_path = 'skins/default';
    $rcmail = rcube::get_instance();
    $skin_path = 'skins/' . $rcmail->config->get('skin');
    if (!is_dir(realpath(slashify($this->home) . $skin_path)))
      $skin_path = 'skins/default';
    return $skin_path;
  }
program/include/rcube_plugin_api.php
@@ -22,6 +22,11 @@
*/
// location where plugins are loade from
if (!defined('RCMAIL_PLUGINS_DIR'))
  define('RCMAIL_PLUGINS_DIR', INSTALL_PATH . 'plugins/');
/**
 * The plugin loader and global API
 *
@@ -33,8 +38,8 @@
  
  public $dir;
  public $url = 'plugins/';
  public $task = '';
  public $output;
  public $config;
  
  public $handlers = array();
  private $plugins = array();
@@ -43,7 +48,6 @@
  private $actionmap = array();
  private $objectsmap = array();
  private $template_contents = array();
  private $required_plugins = array('filesystem_attachments', 'jqueryui');
  private $active_hook = false;
  // Deprecated names of hooks, will be removed after 0.5-stable release
@@ -99,7 +103,28 @@
   */
  private function __construct()
  {
    $this->dir = INSTALL_PATH . $this->url;
    $this->dir = slashify(RCMAIL_PLUGINS_DIR);
  }
  /**
   * Initialize plugin engine
   *
   * This has to be done after rcmail::load_gui() or rcmail::json_init()
   * was called because plugins need to have access to rcmail->output
   *
   * @param object rcube Instance of the rcube base class
   * @param string Current application task (used for conditional plugin loading)
   */
  public function init($app, $task = '')
  {
    $this->task = $task;
    $this->output = $app->output;
    // register an internal hook
    $this->register_hook('template_container', array($this, 'template_container_hook'));
    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
  }
@@ -108,20 +133,18 @@
   *
   * This has to be done after rcmail::load_gui() or rcmail::json_init()
   * was called because plugins need to have access to rcmail->output
   *
   * @param array List of configured plugins to load
   * @param array List of plugins required by the application
   */
  public function init()
  public function load_plugins($plugins_enabled, $required_plugins = array())
  {
    $rcmail = rcmail::get_instance();
    $this->output = $rcmail->output;
    $this->config = $rcmail->config;
    $plugins_enabled = (array)$rcmail->config->get('plugins', array());
    foreach ($plugins_enabled as $plugin_name) {
      $this->load_plugin($plugin_name);
    }
    // check existance of all required core plugins
    foreach ($this->required_plugins as $plugin_name) {
    foreach ($required_plugins as $plugin_name) {
      $loaded = false;
      foreach ($this->plugins as $plugin) {
        if ($plugin instanceof $plugin_name) {
@@ -136,18 +159,12 @@
      // trigger fatal error if still not loaded
      if (!$loaded) {
        raise_error(array('code' => 520, 'type' => 'php',
        rcube::raise_error(array('code' => 520, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Requried plugin $plugin_name was not loaded"), true, true);
      }
    }
    // register an internal hook
    $this->register_hook('template_container', array($this, 'template_container_hook'));
    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
  }
  /**
   * Load the specified plugin
@@ -158,8 +175,6 @@
  public function load_plugin($plugin_name)
  {
    static $plugins_dir;
    $rcmail = rcmail::get_instance();
    if (!$plugins_dir) {
      $dir = dir($this->dir);
@@ -181,8 +196,8 @@
        // check inheritance...
        if (is_subclass_of($plugin, 'rcube_plugin')) {
          // ... task, request type and framed mode
          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task))
              && (!$plugin->noajax || (is_object($rcmail->output) && is_a($rcmail->output, 'rcube_template')))
          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
              && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
              && (!$plugin->noframe || empty($_REQUEST['_framed']))
          ) {
            $plugin->init();
@@ -192,13 +207,13 @@
        }
      }
      else {
        raise_error(array('code' => 520, 'type' => 'php',
        rcube::raise_error(array('code' => 520, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "No plugin class $plugin_name found in $fn"), true, false);
      }
    }
    else {
      raise_error(array('code' => 520, 'type' => 'php',
      rcube::raise_error(array('code' => 520, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Failed to load plugin file $fn"), true, false);
    }
@@ -217,7 +232,7 @@
  {
    if (is_callable($callback)) {
      if (isset($this->deprecated_hooks[$hook])) {
        raise_error(array('code' => 522, 'type' => 'php',
        rcube::raise_error(array('code' => 522, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false);
        $hook = $this->deprecated_hooks[$hook];
@@ -225,7 +240,7 @@
      $this->handlers[$hook][] = $callback;
    }
    else
      raise_error(array('code' => 521, 'type' => 'php',
      rcube::raise_error(array('code' => 521, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Invalid callback function for $hook"), true, false);
  }
@@ -297,7 +312,7 @@
      $this->actionmap[$action] = $owner;
    }
    else {
      raise_error(array('code' => 523, 'type' => 'php',
      rcube::raise_error(array('code' => 523, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Cannot register action $action; already taken by another plugin"), true, false);
    }
@@ -316,7 +331,7 @@
      call_user_func($this->actions[$action]);
    }
    else {
      raise_error(array('code' => 524, 'type' => 'php',
      rcube::raise_error(array('code' => 524, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "No handler found for action $action"), true, true);
    }
@@ -337,14 +352,14 @@
      $name = 'plugin.'.$name;
    // can register handler only if it's not taken or registered by myself
    if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) {
    if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) {
      $this->output->add_handler($name, $callback);
      $this->objectsmap[$name] = $owner;
    }
    else {
      raise_error(array('code' => 525, 'type' => 'php',
      rcube::raise_error(array('code' => 525, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
        'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false);
    }
  }
@@ -358,12 +373,12 @@
  public function register_task($task, $owner)
  {
    if ($task != asciiwords($task)) {
      raise_error(array('code' => 526, 'type' => 'php',
      rcube::raise_error(array('code' => 526, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
    }
    else if (in_array($task, rcmail::$main_tasks)) {
      raise_error(array('code' => 526, 'type' => 'php',
      rcube::raise_error(array('code' => 526, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
    }
@@ -408,7 +423,7 @@
   */
  public function include_script($fn)
  {
    if ($this->output->type == 'html') {
    if (is_object($this->output) && $this->output->type == 'html') {
      $src = $this->resource_url($fn);
      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
    }
@@ -422,7 +437,7 @@
   */
  public function include_stylesheet($fn)
  {
    if ($this->output->type == 'html') {
    if (is_object($this->output) && $this->output->type == 'html') {
      $src = $this->resource_url($fn);
      $this->output->include_css($src);
    }
program/include/rcube_session.php
@@ -78,7 +78,7 @@
          array($this, 'gc'));
      }
      else {
        raise_error(array('code' => 604, 'type' => 'db',
        rcube::raise_error(array('code' => 604, 'type' => 'db',
          'line' => __LINE__, 'file' => __FILE__,
          'message' => "Failed to connect to memcached. Please check configuration"),
          true, true);
@@ -129,7 +129,7 @@
  public function db_read($key)
  {
    $sql_result = $this->db->query(
      "SELECT vars, ip, changed FROM ".get_table_name('session')
      "SELECT vars, ip, changed FROM ".$this->db->table_name('session')
      ." WHERE sess_id = ?", $key);
    if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
@@ -177,18 +177,18 @@
      if ($newvars !== $oldvars) {
        $this->db->query(
          sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
            get_table_name('session'), $now),
            $this->db->table_name('session'), $now),
          base64_encode($newvars), $key);
      }
      else if ($ts - $this->changed > $this->lifetime / 2) {
        $this->db->query("UPDATE ".get_table_name('session')." SET changed=$now WHERE sess_id=?", $key);
        $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key);
      }
    }
    else {
      $this->db->query(
        sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
          "VALUES (?, ?, ?, %s, %s)",
          get_table_name('session'), $now, $now),
          $this->db->table_name('session'), $now, $now),
        $key, base64_encode($vars), (string)$this->ip);
    }
@@ -228,7 +228,7 @@
  public function db_destroy($key)
  {
    $this->db->query(
      sprintf("DELETE FROM %s WHERE sess_id = ?", get_table_name('session')),
      sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')),
      $key);
    return true;
@@ -246,7 +246,7 @@
    // just delete all expired sessions
    $this->db->query(
      sprintf("DELETE FROM %s WHERE changed < %s",
        get_table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
        $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
    $this->gc();
@@ -322,8 +322,9 @@
   */
  public function gc()
  {
    foreach ($this->gc_handlers as $fct)
    foreach ($this->gc_handlers as $fct) {
      call_user_func($fct);
    }
  }
@@ -624,14 +625,14 @@
    $auth_string = "$this->key,$this->secret,$timeslot";
    return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
  }
  /**
   * 
   */
  function log($line)
  {
    if ($this->logging)
      write_log('session', $line);
      rcmail::write_log('session', $line);
  }
}
program/include/rcube_shared.inc
@@ -2,17 +2,17 @@
/*
 +-----------------------------------------------------------------------+
 | rcube_shared.inc                                                      |
 | program/include/rcube_shared.inc                                      |
 |                                                                       |
 | This file is part of the Roundcube PHP suite                          |
 | Copyright (C) 2005-2007, The Roundcube Dev Team                       |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | CONTENTS:                                                             |
 |   Shared functions and classes used in PHP projects                   |
 |   Shared functions used by Roundcube Framework                        |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
@@ -31,213 +31,95 @@
/**
 * Send HTTP headers to prevent caching this page
 */
function send_nocacheing_headers()
{
  global $OUTPUT;
  if (headers_sent())
    return;
  header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
  header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
  // Request browser to disable DNS prefetching (CVE-2010-0464)
  header("X-DNS-Prefetch-Control: off");
  // We need to set the following headers to make downloads work using IE in HTTPS mode.
  if ($OUTPUT->browser->ie && rcube_https_check()) {
    header('Pragma: private');
    header("Cache-Control: private, must-revalidate");
  } else {
    header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
    header("Pragma: no-cache");
  }
}
/**
 * Send header with expire date 30 days in future
 *
 * @param int Expiration time in seconds
 */
function send_future_expire_header($offset=2600000)
{
  if (headers_sent())
    return;
  header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+$offset)." GMT");
  header("Cache-Control: max-age=$offset");
  header("Pragma: ");
}
/**
 * Similar function as in_array() but case-insensitive
 *
 * @param mixed Needle value
 * @param array Array to search in
 * @param string $needle    Needle value
 * @param array  $heystack  Array to search in
 *
 * @return boolean True if found, False if not
 */
function in_array_nocase($needle, $haystack)
{
  $needle = mb_strtolower($needle);
  foreach ($haystack as $value)
    if ($needle===mb_strtolower($value))
      return true;
    $needle = mb_strtolower($needle);
    foreach ($haystack as $value) {
        if ($needle === mb_strtolower($value)) {
            return true;
        }
    }
  return false;
    return false;
}
/**
 * Find out if the string content means TRUE or FALSE
 * Find out if the string content means true or false
 *
 * @param string Input value
 * @return boolean Imagine what!
 * @param string $str  Input value
 *
 * @return boolean Boolean value
 */
function get_boolean($str)
{
  $str = strtolower($str);
  if (in_array($str, array('false', '0', 'no', 'off', 'nein', ''), TRUE))
    return FALSE;
  else
    return TRUE;
    $str = strtolower($str);
    return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
}
/**
 * Parse a human readable string for a number of bytes
 * Parse a human readable string for a number of bytes.
 *
 * @param string Input string
 * @param string $str  Input string
 *
 * @return float Number of bytes
 */
function parse_bytes($str)
{
  if (is_numeric($str))
    return floatval($str);
  if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs))
  {
    $bytes = floatval($regs[1]);
    switch (strtolower($regs[2]))
    {
      case 'g':
      case 'gb':
        $bytes *= 1073741824;
        break;
      case 'm':
      case 'mb':
        $bytes *= 1048576;
        break;
      case 'k':
      case 'kb':
        $bytes *= 1024;
        break;
    if (is_numeric($str)) {
        return floatval($str);
    }
  }
  return floatval($bytes);
}
/**
 * Create a human readable string for a number of bytes
 *
 * @param int Number of bytes
 * @return string Byte string
 */
function show_bytes($bytes)
{
  if ($bytes >= 1073741824)
  {
    $gb = $bytes/1073741824;
    $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . rcube_label('GB');
  }
  else if ($bytes >= 1048576)
  {
    $mb = $bytes/1048576;
    $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . rcube_label('MB');
  }
  else if ($bytes >= 1024)
    $str = sprintf("%d ",  round($bytes/1024)) . rcube_label('KB');
  else
    $str = sprintf('%d ', $bytes) . rcube_label('B');
  return $str;
}
/**
 * Wrapper function for wordwrap
 */
function rc_wordwrap($string, $width=75, $break="\n", $cut=false)
{
  $para = explode($break, $string);
  $string = '';
  while (count($para)) {
    $line = array_shift($para);
    if ($line[0] == '>') {
      $string .= $line.$break;
      continue;
    }
    $list = explode(' ', $line);
    $len = 0;
    while (count($list)) {
      $line = array_shift($list);
      $l = mb_strlen($line);
      $newlen = $len + $l + ($len ? 1 : 0);
      if ($newlen <= $width) {
        $string .= ($len ? ' ' : '').$line;
        $len += (1 + $l);
      } else {
        if ($l > $width) {
          if ($cut) {
            $start = 0;
            while ($l) {
              $str = mb_substr($line, $start, $width);
              $strlen = mb_strlen($str);
              $string .= ($len ? $break : '').$str;
              $start += $strlen;
              $l -= $strlen;
              $len = $strlen;
            }
          } else {
                $string .= ($len ? $break : '').$line;
            if (count($list)) $string .= $break;
            $len = 0;
          }
        } else {
          $string .= $break.$line;
          $len = $l;
    if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) {
        $bytes = floatval($regs[1]);
        switch (strtolower($regs[2])) {
        case 'g':
        case 'gb':
            $bytes *= 1073741824;
            break;
        case 'm':
        case 'mb':
            $bytes *= 1048576;
            break;
        case 'k':
        case 'kb':
            $bytes *= 1024;
            break;
        }
      }
    }
    if (count($para)) $string .= $break;
  }
  return $string;
    return floatval($bytes);
}
/**
 * Read a specific HTTP request header
 * Read a specific HTTP request header.
 *
 * @access static
 * @param  string $name Header name
 *
 * @return mixed  Header value or null if not available
 */
function rc_request_header($name)
function rcube_request_header($name)
{
  if (function_exists('getallheaders'))
  {
    $hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
    $key  = strtoupper($name);
  }
  else
  {
    $key  = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
    $hdrs = array_change_key_case($_SERVER, CASE_UPPER);
  }
    if (function_exists('getallheaders')) {
        $hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
        $key  = strtoupper($name);
    }
    else {
        $key  = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
        $hdrs = array_change_key_case($_SERVER, CASE_UPPER);
    }
  return $hdrs[$key];
    return $hdrs[$key];
}
@@ -263,219 +145,251 @@
 * Delete all files within a folder
 *
 * @param string Path to directory
 *
 * @return boolean True on success, False if directory was not found
 */
function clear_directory($dir_path)
{
  $dir = @opendir($dir_path);
  if(!$dir) return FALSE;
    $dir = @opendir($dir_path);
    if (!$dir) {
        return false;
    }
  while ($file = readdir($dir))
    if (strlen($file)>2)
      unlink("$dir_path/$file");
    while ($file = readdir($dir)) {
        if (strlen($file) > 2) {
            unlink("$dir_path/$file");
        }
    }
  closedir($dir);
  return TRUE;
    closedir($dir);
    return true;
}
/**
 * Create a unix timestamp with a specified offset from now
 * Create a unix timestamp with a specified offset from now.
 *
 * @param string String representation of the offset (e.g. 20min, 5h, 2days)
 * @param int Factor to multiply with the offset
 * @param string $offset_str  String representation of the offset (e.g. 20min, 5h, 2days)
 * @param int    $factor      Factor to multiply with the offset
 *
 * @return int Unix timestamp
 */
function get_offset_time($offset_str, $factor=1)
{
  if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs))
  {
    $amount = (int)$regs[1];
    $unit = strtolower($regs[2]);
  }
  else
  {
    $amount = (int)$offset_str;
    $unit = 's';
  }
  $ts = mktime();
  switch ($unit)
  {
    case 'w':
      $amount *= 7;
    case 'd':
      $amount *= 24;
    case 'h':
      $amount *= 60;
    case 'm':
      $amount *= 60;
    case 's':
      $ts += $amount * $factor;
  }
  return $ts;
}
/**
 * Truncate string if it is longer than the allowed length
 * Replace the middle or the ending part of a string with a placeholder
 *
 * @param string Input string
 * @param int    Max. length
 * @param string Replace removed chars with this
 * @param bool   Set to True if string should be truncated from the end
 * @return string Abbreviated string
 */
function abbreviate_string($str, $maxlength, $place_holder='...', $ending=false)
{
  $length = mb_strlen($str);
  if ($length > $maxlength)
  {
    if ($ending)
      return mb_substr($str, 0, $maxlength) . $place_holder;
    $place_holder_length = mb_strlen($place_holder);
    $first_part_length = floor(($maxlength - $place_holder_length)/2);
    $second_starting_location = $length - $maxlength + $first_part_length + $place_holder_length;
    $str = mb_substr($str, 0, $first_part_length) . $place_holder . mb_substr($str, $second_starting_location);
  }
  return $str;
}
/**
 * A method to guess the mime_type of an attachment.
 *
 * @param string $path      Path to the file.
 * @param string $name      File name (with suffix)
 * @param string $failover  Mime type supplied for failover.
 * @param string $is_stream Set to True if $path contains file body
 *
 * @return string
 * @author Till Klampaeckel <till@php.net>
 * @see    http://de2.php.net/manual/en/ref.fileinfo.php
 * @see    http://de2.php.net/mime_content_type
 */
function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false)
{
    $mime_type = null;
    $mime_magic = rcmail::get_instance()->config->get('mime_magic');
    $mime_ext = @include(RCMAIL_CONFIG_DIR . '/mimetypes.php');
    // use file name suffix with hard-coded mime-type map
    if (is_array($mime_ext) && $name) {
        if ($suffix = substr($name, strrpos($name, '.')+1)) {
            $mime_type = $mime_ext[strtolower($suffix)];
        }
    }
    // try fileinfo extension if available
    if (!$mime_type && function_exists('finfo_open')) {
        if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) {
            if ($is_stream)
                $mime_type = finfo_buffer($finfo, $path);
            else
                $mime_type = finfo_file($finfo, $path);
            finfo_close($finfo);
        }
    }
    // try PHP's mime_content_type
    if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
      $mime_type = @mime_content_type($path);
    }
    // fall back to user-submitted string
    if (!$mime_type) {
        $mime_type = $failover;
    if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs)) {
        $amount = (int)$regs[1];
        $unit   = strtolower($regs[2]);
    }
    else {
        // Sometimes (PHP-5.3?) content-type contains charset definition,
        // Remove it (#1487122) also "charset=binary" is useless
        $mime_type = array_shift(preg_split('/[; ]/', $mime_type));
        $amount = (int)$offset_str;
        $unit   = 's';
    }
    return $mime_type;
    $ts = mktime();
    switch ($unit) {
    case 'w':
        $amount *= 7;
    case 'd':
        $amount *= 24;
    case 'h':
        $amount *= 60;
    case 'm':
        $amount *= 60;
    case 's':
        $ts += $amount * $factor;
    }
    return $ts;
}
/**
 * Detect image type of the given binary data by checking magic numbers
 * Truncate string if it is longer than the allowed length.
 * Replace the middle or the ending part of a string with a placeholder.
 *
 * @param string  Binary file content
 * @return string Detected mime-type or jpeg as fallback
 * @param string $str         Input string
 * @param int    $maxlength   Max. length
 * @param string $placeholder Replace removed chars with this
 * @param bool   $ending      Set to True if string should be truncated from the end
 *
 * @return string Abbreviated string
 */
function rc_image_content_type($data)
function abbreviate_string($str, $maxlength, $placeholder='...', $ending=false)
{
    $type = 'jpeg';
    if      (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png';
    else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif';
    else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico';
//  else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg';
    $length = mb_strlen($str);
    return 'image/' . $type;
    if ($length > $maxlength) {
        if ($ending) {
            return mb_substr($str, 0, $maxlength) . $placeholder;
        }
        $placeholder_length = mb_strlen($placeholder);
        $first_part_length  = floor(($maxlength - $placeholder_length)/2);
        $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length;
        $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location);
    }
    return $str;
}
/**
 * Explode quoted string
 *
 *
 * @param string Delimiter expression string for preg_match()
 * @param string Input string
 */
function rcube_explode_quoted_string($delimiter, $string)
{
  $result = array();
  $strlen = strlen($string);
    $result = array();
    $strlen = strlen($string);
  for ($q=$p=$i=0; $i < $strlen; $i++) {
    if ($string[$i] == "\"" && $string[$i-1] != "\\") {
      $q = $q ? false : true;
    }
    else if (!$q && preg_match("/$delimiter/", $string[$i])) {
      $result[] = substr($string, $p, $i - $p);
      $p = $i + 1;
    for ($q=$p=$i=0; $i < $strlen; $i++) {
        if ($string[$i] == "\"" && $string[$i-1] != "\\") {
            $q = $q ? false : true;
        }
        else if (!$q && preg_match("/$delimiter/", $string[$i])) {
            $result[] = substr($string, $p, $i - $p);
            $p = $i + 1;
        }
    }
  }
  $result[] = substr($string, $p);
  return $result;
    $result[] = substr($string, $p);
    return $result;
}
/**
 * Get all keys from array (recursive)
 *
 * @param array Input array
 * @return array
 * Get all keys from array (recursive).
 *
 * @param array $array  Input array
 *
 * @return array List of array keys
 */
function array_keys_recursive($array)
{
  $keys = array();
    $keys = array();
  if (!empty($array))
    foreach ($array as $key => $child) {
      $keys[] = $key;
      foreach (array_keys_recursive($child) as $val)
        $keys[] = $val;
    if (!empty($array)) {
        foreach ($array as $key => $child) {
            $keys[] = $key;
            foreach (array_keys_recursive($child) as $val) {
                $keys[] = $val;
            }
        }
    }
  return $keys;
    return $keys;
}
/**
 * Remove all non-ascii and non-word chars except ., -, _
 */
function asciiwords($str, $css_id = false, $replace_with = '')
{
    $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
    return preg_replace("/[^$allowed]/i", $replace_with, $str);
}
/**
 * Remove single and double quotes from given string
 *
 * @param string Input value
 *
 * @return string Dequoted string
 */
function strip_quotes($str)
{
    return str_replace(array("'", '"'), '', $str);
}
/**
 * Remove new lines characters from given string
 *
 * @param string $str  Input value
 *
 * @return string Stripped string
 */
function strip_newlines($str)
{
    return preg_replace('/[\r\n]/', '', $str);
}
/**
 * Improved equivalent to strtotime()
 *
 * @param string $date  Date string
 *
 * @return int Unix timestamp
 */
function rcube_strtotime($date)
{
    // check for MS Outlook vCard date format YYYYMMDD
    if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
        return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
    }
    else if (is_numeric($date)) {
        return $date;
    }
    // support non-standard "GMTXXXX" literal
    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
    // if date parsing fails, we have a date in non-rfc format.
    // remove token from the end and try again
    while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
        $d = explode(' ', $date);
        array_pop($d);
        if (!$d) {
            break;
        }
        $date = implode(' ', $d);
    }
    return $ts;
}
/**
 * Compose a valid representation of name and e-mail address
 *
 * @param string $email  E-mail address
 * @param string $name   Person name
 *
 * @return string Formatted string
 */
function format_email_recipient($email, $name = '')
{
    $email = trim($email);
    if ($name && $name != $email) {
        // Special chars as defined by RFC 822 need to in quoted string (or escaped).
        if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) {
            $name = '"'.addcslashes($name, '"').'"';
        }
        return "$name <$email>";
    }
    return $email;
}
/**
 * mbstring replacement functions
 */
if (!extension_loaded('mbstring'))
{
    function mb_strlen($str)
    {
    return strlen($str);
        return strlen($str);
    }
    function mb_strtolower($str)
@@ -552,3 +466,89 @@
    }
}
/*
 * Idn_to_ascii wrapper.
 * Intl/Idn modules version of this function doesn't work with e-mail address
 */
function rcube_idn_to_ascii($str)
{
    return rcube_idn_convert($str, true);
}
/*
 * Idn_to_ascii wrapper.
 * Intl/Idn modules version of this function doesn't work with e-mail address
 */
function rcube_idn_to_utf8($str)
{
    return rcube_idn_convert($str, false);
}
function rcube_idn_convert($input, $is_utf=false)
{
    if ($at = strpos($input, '@')) {
        $user   = substr($input, 0, $at);
        $domain = substr($input, $at+1);
    }
    else {
        $domain = $input;
    }
    $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
    if ($domain === false) {
        return '';
    }
    return $at ? $user . '@' . $domain : $domain;
}
/**
 * Use PHP5 autoload for dynamic class loading
 *
 * @todo Make Zend, PEAR etc play with this
 * @todo Make our classes conform to a more straight forward CS.
 */
function rcube_autoload($classname)
{
    $filename = preg_replace(
        array(
            '/MDB2_(.+)/',
            '/Mail_(.+)/',
            '/Net_(.+)/',
            '/Auth_(.+)/',
            '/^html_.+/',
            '/^utf8$/',
        ),
        array(
            'MDB2/\\1',
            'Mail/\\1',
            'Net/\\1',
            'Auth/\\1',
            'html',
            'utf8.class',
        ),
        $classname
    );
    if ($fp = @fopen("$filename.php", 'r', true)) {
        fclose($fp);
        include_once("$filename.php");
        return true;
    }
    return false;
}
/**
 * Local callback function for PEAR errors
 */
function rcube_pear_error($err)
{
    error_log(sprintf("%s (%s): %s",
        $err->getMessage(),
        $err->getCode(),
        $err->getUserinfo()), 0);
}
program/include/rcube_smtp.php
@@ -52,7 +52,7 @@
   */
  public function connect($host=null, $port=null, $user=null, $pass=null)
  {
    $RCMAIL = rcmail::get_instance();
    $RCMAIL = rcube::get_instance();
    // disconnect/destroy $this->conn
    $this->disconnect();
@@ -74,7 +74,7 @@
      'smtp_auth_callbacks' => array(),
    ));
    $smtp_host = rcube_parse_host($CONFIG['smtp_server']);
    $smtp_host = rcmail::parse_host($CONFIG['smtp_server']);
    // when called from Installer it's possible to have empty $smtp_host here
    if (!$smtp_host) $smtp_host = 'localhost';
    $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
@@ -338,7 +338,7 @@
   */
  public function debug_handler(&$smtp, $message)
  {
    write_log('smtp', preg_replace('/\r\n$/', '', $message));
    rcmail::write_log('smtp', preg_replace('/\r\n$/', '', $message));
  }
program/include/rcube_spellchecker.php
@@ -61,7 +61,7 @@
        $this->lang   = $lang ? $lang : 'en';
        if ($this->engine == 'pspell' && !extension_loaded('pspell')) {
            raise_error(array(
            rcube::raise_error(array(
                'code' => 500, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Pspell extension not available"), true, true);
@@ -535,7 +535,7 @@
    private function update_dict()
    {
        if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
            $userid = (int) $this->rc->user->ID;
            $userid = $this->rc->get_user_id();
        }
        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array(
@@ -548,24 +548,24 @@
        if ($this->have_dict) {
            if (!empty($this->dict)) {
                $this->rc->db->query(
                    "UPDATE ".get_table_name('dictionary')
                    "UPDATE ".$this->rc->db->table_name('dictionary')
                    ." SET data = ?"
                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                        ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
                    implode(' ', $plugin['dictionary']), $plugin['language']);
            }
            // don't store empty dict
            else {
                $this->rc->db->query(
                    "DELETE FROM " . get_table_name('dictionary')
                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
                    "DELETE FROM " . $this->rc->db->table_name('dictionary')
                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                        ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
                    $plugin['language']);
            }
        }
        else if (!empty($this->dict)) {
            $this->rc->db->query(
                "INSERT INTO " .get_table_name('dictionary')
                "INSERT INTO " .$this->rc->db->table_name('dictionary')
                ." (user_id, " . $this->rc->db->quoteIdentifier('language') . ", data) VALUES (?, ?, ?)",
                $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary']));
        }
@@ -582,7 +582,7 @@
        }
        if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
            $userid = (int) $this->rc->user->ID;
            $userid = $this->rc->get_user_id();
        }
        $plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array(
@@ -591,8 +591,8 @@
        if (empty($plugin['abort'])) {
            $dict = array();
            $this->rc->db->query(
                "SELECT data FROM ".get_table_name('dictionary')
                ." WHERE user_id ". ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
                "SELECT data FROM ".$this->rc->db->table_name('dictionary')
                ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                    ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
                $plugin['language']);
program/include/rcube_sqlite.inc
File was deleted
program/include/rcube_storage.php
@@ -434,7 +434,7 @@
     * @param int     $uid     Message UID to fetch
     * @param string  $folder  Folder to read from
     *
     * @return object rcube_mail_header Message data
     * @return object rcube_message_header Message data
     */
    abstract function get_message($uid, $folder = null);
@@ -446,7 +446,7 @@
     * @param string  $folder   Folder to read from
     * @param bool    $force    True to skip cache
     *
     * @return rcube_mail_header Message headers
     * @return rcube_message_header Message headers
     */
    abstract function get_message_headers($uid, $folder = null, $force = false);
@@ -477,7 +477,7 @@
    public function get_body($uid, $part = 1)
    {
        $headers = $this->get_message_headers($uid);
        return rcube_charset_convert($this->get_message_part($uid, $part, null),
        return rcube_charset::convert($this->get_message_part($uid, $part, null),
            $headers->charset ? $headers->charset : $this->default_charset);
    }
@@ -970,6 +970,7 @@
     */
    abstract function clear_cache($key = null, $prefix_mode = false);
    /**
     * Returns cached value
     *
@@ -979,93 +980,10 @@
     */
    abstract function get_cache($key);
    /**
     * Delete outdated cache entries
     */
    abstract function expunge_cache();
}  // end class rcube_storage
/**
 * Class representing a message part
 *
 * @package Mail
 */
class rcube_message_part
{
    var $mime_id = '';
    var $ctype_primary = 'text';
    var $ctype_secondary = 'plain';
    var $mimetype = 'text/plain';
    var $disposition = '';
    var $filename = '';
    var $encoding = '8bit';
    var $charset = '';
    var $size = 0;
    var $headers = array();
    var $d_parameters = array();
    var $ctype_parameters = array();
    function __clone()
    {
        if (isset($this->parts)) {
            foreach ($this->parts as $idx => $part) {
                if (is_object($part)) {
                    $this->parts[$idx] = clone $part;
                }
            }
        }
    }
}
/**
 * Class for sorting an array of rcube_mail_header objects in a predetermined order.
 *
 * @package Mail
 * @author Eric Stadtherr
 */
class rcube_header_sorter
{
    private $uids = array();
    /**
     * Set the predetermined sort order.
     *
     * @param array $index  Numerically indexed array of IMAP UIDs
     */
    function set_index($index)
    {
        $index = array_flip($index);
        $this->uids = $index;
    }
    /**
     * Sort the array of header objects
     *
     * @param array $headers Array of rcube_mail_header objects indexed by UID
     */
    function sort_headers(&$headers)
    {
        uksort($headers, array($this, "compare_uids"));
    }
    /**
     * Sort method called by uksort()
     *
     * @param int $a Array key (UID)
     * @param int $b Array key (UID)
     */
    function compare_uids($a, $b)
    {
        // then find each sequence number in my ordered list
        $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
        $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1;
        // return the relative position as the comparison value
        return $posa - $posb;
    }
}
program/include/rcube_string_replacer.php
@@ -98,7 +98,7 @@
      $i = $this->add($prefix . html::a(array(
          'href' => $url_prefix . $url,
          'target' => '_blank'
        ), Q($url)) . $suffix);
        ), rcube_ui::Q($url)) . $suffix);
    }
    // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
@@ -118,8 +118,8 @@
    $i = $this->add(html::a(array(
        'href' => 'mailto:' . $href,
        'onclick' => "return ".JS_OBJECT_NAME.".command('compose','".JQ($href)."',this)",
      ), Q($href)) . $suffix);
        'onclick' => "return ".JS_OBJECT_NAME.".command('compose','".rcube_ui::JQ($href)."',this)",
      ), rcube_ui::Q($href)) . $suffix);
    return $i >= 0 ? $this->get_replacement($i) : '';
  }
program/include/rcube_ui.php
New file
@@ -0,0 +1,1468 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_ui.php                                          |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Provide basic functions for the webmail user interface              |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
 $Id$
*/
/**
 * Roundcube Webmail functions for user interface
 *
 * @package Core
 * @author Thomas Bruederli <roundcube@gmail.com>
 * @author Aleksander Machniak <alec@alec.pl>
 */
class rcube_ui
{
    // define constants for input reading
    const INPUT_GET  = 0x0101;
    const INPUT_POST = 0x0102;
    const INPUT_GPC  = 0x0103;
    /**
     * Get localized text in the desired language
     * It's a global wrapper for rcube::gettext()
     *
     * @param mixed  $p      Named parameters array or label name
     * @param string $domain Domain to search in (e.g. plugin name)
     *
     * @return string Localized text
     * @see rcube::gettext()
     */
    public static function label($p, $domain = null)
    {
        return rcube::get_instance()->gettext($p, $domain);
    }
    /**
     * Global wrapper of rcube::text_exists()
     * to check whether a text label is defined
     *
     * @see rcube::text_exists()
     */
    public static function label_exists($name, $domain = null, &$ref_domain = null)
    {
        return rcube::get_instance()->text_exists($name, $domain, $ref_domain);
    }
    /**
     * Compose an URL for a specific action
     *
     * @param string  Request action
     * @param array   More URL parameters
     * @param string  Request task (omit if the same)
     *
     * @return The application URL
     */
    public static function url($action, $p = array(), $task = null)
    {
        return rcube::get_instance()->url((array)$p + array('_action' => $action, 'task' => $task));
    }
    /**
     * Replacing specials characters to a specific encoding type
     *
     * @param  string  Input string
     * @param  string  Encoding type: text|html|xml|js|url
     * @param  string  Replace mode for tags: show|replace|remove
     * @param  boolean Convert newlines
     *
     * @return string  The quoted string
     */
    public static function rep_specialchars_output($str, $enctype = '', $mode = '', $newlines = true)
    {
        static $html_encode_arr = false;
        static $js_rep_table = false;
        static $xml_rep_table = false;
        // encode for HTML output
        if ($enctype == 'html') {
            if (!$html_encode_arr) {
                $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
                unset($html_encode_arr['?']);
            }
            $encode_arr = $html_encode_arr;
            // don't replace quotes and html tags
            if ($mode == 'show' || $mode == '') {
                $ltpos = strpos($str, '<');
                if ($ltpos !== false && strpos($str, '>', $ltpos) !== false) {
                    unset($encode_arr['"']);
                    unset($encode_arr['<']);
                    unset($encode_arr['>']);
                    unset($encode_arr['&']);
                }
            }
            else if ($mode == 'remove') {
                $str = strip_tags($str);
            }
            $out = strtr($str, $encode_arr);
            // avoid douple quotation of &
            $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
            return $newlines ? nl2br($out) : $out;
        }
        // if the replace tables for XML and JS are not yet defined
        if ($js_rep_table === false) {
            $js_rep_table = $xml_rep_table = array();
            $xml_rep_table['&'] = '&amp;';
            // can be increased to support more charsets
            for ($c=160; $c<256; $c++) {
                $xml_rep_table[chr($c)] = "&#$c;";
            }
            $xml_rep_table['"'] = '&quot;';
            $js_rep_table['"']  = '\\"';
            $js_rep_table["'"]  = "\\'";
            $js_rep_table["\\"] = "\\\\";
            // Unicode line and paragraph separators (#1486310)
            $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
            $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
        }
        // encode for javascript use
        if ($enctype == 'js') {
            return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
        }
        // encode for plaintext
        if ($enctype == 'text') {
            return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
        }
        if ($enctype == 'url') {
            return rawurlencode($str);
        }
        // encode for XML
        if ($enctype == 'xml') {
            return strtr($str, $xml_rep_table);
        }
        // no encoding given -> return original string
        return $str;
    }
    /**
     * Quote a given string.
     * Shortcut function for self::rep_specialchars_output()
     *
     * @return string HTML-quoted string
     * @see self::rep_specialchars_output()
     */
    public static function Q($str, $mode = 'strict', $newlines = true)
    {
        return self::rep_specialchars_output($str, 'html', $mode, $newlines);
    }
    /**
     * Quote a given string for javascript output.
     * Shortcut function for self::rep_specialchars_output()
     *
     * @return string JS-quoted string
     * @see self::rep_specialchars_output()
     */
    public static function JQ($str)
    {
        return self::rep_specialchars_output($str, 'js');
    }
    /**
     * Read input value and convert it for internal use
     * Performs stripslashes() and charset conversion if necessary
     *
     * @param  string   Field name to read
     * @param  int      Source to get value from (GPC)
     * @param  boolean  Allow HTML tags in field value
     * @param  string   Charset to convert into
     *
     * @return string   Field value or NULL if not available
     */
    public static function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
    {
        $value = NULL;
        if ($source == self::INPUT_GET) {
            if (isset($_GET[$fname])) {
                $value = $_GET[$fname];
            }
        }
        else if ($source == self::INPUT_POST) {
            if (isset($_POST[$fname])) {
                $value = $_POST[$fname];
            }
        }
        else if ($source == self::INPUT_GPC) {
            if (isset($_POST[$fname])) {
                $value = $_POST[$fname];
            }
            else if (isset($_GET[$fname])) {
                $value = $_GET[$fname];
            }
            else if (isset($_COOKIE[$fname])) {
                $value = $_COOKIE[$fname];
            }
        }
        return self::parse_input_value($value, $allow_html, $charset);
    }
    /**
     * Parse/validate input value. See self::get_input_value()
     * Performs stripslashes() and charset conversion if necessary
     *
     * @param  string   Input value
     * @param  boolean  Allow HTML tags in field value
     * @param  string   Charset to convert into
     *
     * @return string   Parsed value
     */
    public static function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
    {
        global $OUTPUT;
        if (empty($value)) {
            return $value;
        }
        if (is_array($value)) {
            foreach ($value as $idx => $val) {
                $value[$idx] = self::parse_input_value($val, $allow_html, $charset);
            }
            return $value;
        }
        // strip single quotes if magic_quotes_sybase is enabled
        if (ini_get('magic_quotes_sybase')) {
            $value = str_replace("''", "'", $value);
        }
        // strip slashes if magic_quotes enabled
        else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
            $value = stripslashes($value);
        }
        // remove HTML tags if not allowed
        if (!$allow_html) {
            $value = strip_tags($value);
        }
        $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
        // remove invalid characters (#1488124)
        if ($output_charset == 'UTF-8') {
            $value = rcube_charset::clean($value);
        }
        // convert to internal charset
        if ($charset && $output_charset) {
            $value = rcube_charset::convert($value, $output_charset, $charset);
        }
        return $value;
    }
    /**
     * Convert array of request parameters (prefixed with _)
     * to a regular array with non-prefixed keys.
     *
     * @param int    $mode   Source to get value from (GPC)
     * @param string $ignore PCRE expression to skip parameters by name
     *
     * @return array Hash array with all request parameters
     */
    public static function request2param($mode = null, $ignore = 'task|action')
    {
        $out = array();
        $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
        foreach ($src as $key => $value) {
            $fname = $key[0] == '_' ? substr($key, 1) : $key;
            if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
                $out[$fname] = self::get_input_value($key, $mode);
            }
        }
        return $out;
    }
    /**
     * Convert the given string into a valid HTML identifier
     * Same functionality as done in app.js with rcube_webmail.html_identifier()
     */
    public static function html_identifier($str, $encode=false)
    {
        if ($encode) {
            return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
        }
        else {
            return asciiwords($str, true, '_');
        }
    }
    /**
     * Create a HTML table based on the given data
     *
     * @param  array  Named table attributes
     * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
     * @param  array  List of cols to show
     * @param  string Name of the identifier col
     *
     * @return string HTML table code
     */
    public static function table_output($attrib, $table_data, $a_show_cols, $id_col)
    {
        global $RCMAIL;
        $table = new html_table(/*array('cols' => count($a_show_cols))*/);
        // add table header
        if (!$attrib['noheader']) {
            foreach ($a_show_cols as $col) {
                $table->add_header($col, self::Q(self::label($col)));
            }
        }
        if (!is_array($table_data)) {
            $db = $RCMAIL->get_dbh();
            while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
                $table->add_row(array('id' => 'rcmrow' . self::html_identifier($sql_arr[$id_col])));
                // format each col
                foreach ($a_show_cols as $col) {
                    $table->add($col, self::Q($sql_arr[$col]));
                }
            }
        }
        else {
            foreach ($table_data as $row_data) {
                $class = !empty($row_data['class']) ? $row_data['class'] : '';
                $rowid = 'rcmrow' . self::html_identifier($row_data[$id_col]);
                $table->add_row(array('id' => $rowid, 'class' => $class));
                // format each col
                foreach ($a_show_cols as $col) {
                    $table->add($col, self::Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
                }
            }
        }
        return $table->show($attrib);
    }
    /**
     * Create an edit field for inclusion on a form
     *
     * @param string col field name
     * @param string value field value
     * @param array attrib HTML element attributes for field
     * @param string type HTML element type (default 'text')
     *
     * @return string HTML field definition
     */
    public static function get_edit_field($col, $value, $attrib, $type = 'text')
    {
        static $colcounts = array();
        $fname = '_'.$col;
        $attrib['name']  = $fname . ($attrib['array'] ? '[]' : '');
        $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
        if ($type == 'checkbox') {
            $attrib['value'] = '1';
            $input = new html_checkbox($attrib);
        }
        else if ($type == 'textarea') {
            $attrib['cols'] = $attrib['size'];
            $input = new html_textarea($attrib);
        }
        else if ($type == 'select') {
            $input = new html_select($attrib);
            $input->add('---', '');
            $input->add(array_values($attrib['options']), array_keys($attrib['options']));
        }
        else if ($attrib['type'] == 'password') {
            $input = new html_passwordfield($attrib);
        }
        else {
            if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden') {
                $attrib['type'] = 'text';
            }
            $input = new html_inputfield($attrib);
        }
        // use value from post
        if (isset($_POST[$fname])) {
            $postvalue = self::get_input_value($fname, self::INPUT_POST, true);
            $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
        }
        $out = $input->show($value);
        return $out;
    }
    /**
     * Replace all css definitions with #container [def]
     * and remove css-inlined scripting
     *
     * @param string CSS source code
     * @param string Container ID to use as prefix
     *
     * @return string Modified CSS source
     * @todo I'm not sure this should belong to rcube_ui class
     */
    public static function mod_css_styles($source, $container_id, $allow_remote=false)
    {
        $last_pos = 0;
        $replacements = new rcube_string_replacer;
        // ignore the whole block if evil styles are detected
        $source   = self::xss_entity_decode($source);
        $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
        $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
        if (preg_match("/$evilexpr/i", $stripped)) {
            return '/* evil! */';
        }
        // cut out all contents between { and }
        while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
            $styles = substr($source, $pos+1, $pos2-($pos+1));
            // check every line of a style block...
            if ($allow_remote) {
                $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
                foreach ($a_styles as $line) {
                    $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
                    // ... and only allow strict url() values
                    $regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims';
                    if (stripos($stripped, 'url(') && !preg_match($regexp, $line)) {
                        $a_styles = array('/* evil! */');
                        break;
                    }
                }
                $styles = join(";\n", $a_styles);
            }
            $key = $replacements->add($styles);
            $source = substr($source, 0, $pos+1)
                . $replacements->get_replacement($key)
                . substr($source, $pos2, strlen($source)-$pos2);
            $last_pos = $pos+2;
        }
        // remove html comments and add #container to each tag selector.
        // also replace body definition because we also stripped off the <body> tag
        $styles = preg_replace(
            array(
                '/(^\s*<!--)|(-->\s*$)/',
                '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
                '/'.preg_quote($container_id, '/').'\s+body/i',
            ),
            array(
                '',
                "\\1#$container_id \\2",
                $container_id,
            ),
            $source);
        // put block contents back in
        $styles = $replacements->resolve($styles);
        return $styles;
    }
    /**
     * Convert the given date to a human readable form
     * This uses the date formatting properties from config
     *
     * @param mixed  Date representation (string, timestamp or DateTime object)
     * @param string Date format to use
     * @param bool   Enables date convertion according to user timezone
     *
     * @return string Formatted date string
     */
    public static function format_date($date, $format = null, $convert = true)
    {
        global $RCMAIL, $CONFIG;
        if (is_object($date) && is_a($date, 'DateTime')) {
            $timestamp = $date->format('U');
        }
        else {
            if (!empty($date)) {
                $timestamp = rcube_strtotime($date);
            }
            if (empty($timestamp)) {
                return '';
            }
            try {
                $date = new DateTime("@".$timestamp);
            }
            catch (Exception $e) {
                return '';
            }
        }
        if ($convert) {
            try {
                // convert to the right timezone
                $stz = date_default_timezone_get();
                $tz = new DateTimeZone($RCMAIL->config->get('timezone'));
                $date->setTimezone($tz);
                date_default_timezone_set($tz->getName());
                $timestamp = $date->format('U');
            }
            catch (Exception $e) {
            }
        }
        // define date format depending on current time
        if (!$format) {
            $now         = time();
            $now_date    = getdate($now);
            $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
            $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
            if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
                $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
                $today  = true;
            }
            else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now) {
                $format = $RCMAIL->config->get('date_short', 'D H:i');
            }
            else {
                $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
            }
        }
        // strftime() format
        if (preg_match('/%[a-z]+/i', $format)) {
            $format = strftime($format, $timestamp);
            if ($stz) {
                date_default_timezone_set($stz);
            }
            return $today ? (self::label('today') . ' ' . $format) : $format;
        }
        // parse format string manually in order to provide localized weekday and month names
        // an alternative would be to convert the date() format string to fit with strftime()
        $out = '';
        for ($i=0; $i<strlen($format); $i++) {
            if ($format[$i] == "\\") {  // skip escape chars
                continue;
            }
            // write char "as-is"
            if ($format[$i] == ' ' || $format[$i-1] == "\\") {
                $out .= $format[$i];
            }
            // weekday (short)
            else if ($format[$i] == 'D') {
                $out .= self::label(strtolower(date('D', $timestamp)));
            }
            // weekday long
            else if ($format[$i] == 'l') {
                $out .= self::label(strtolower(date('l', $timestamp)));
            }
            // month name (short)
            else if ($format[$i] == 'M') {
                $out .= self::label(strtolower(date('M', $timestamp)));
            }
            // month name (long)
            else if ($format[$i] == 'F') {
                $out .= self::label('long'.strtolower(date('M', $timestamp)));
            }
            else if ($format[$i] == 'x') {
                $out .= strftime('%x %X', $timestamp);
            }
            else {
                $out .= date($format[$i], $timestamp);
            }
        }
        if ($today) {
            $label = self::label('today');
            // replcae $ character with "Today" label (#1486120)
            if (strpos($out, '$') !== false) {
                $out = preg_replace('/\$/', $label, $out, 1);
            }
            else {
                $out = $label . ' ' . $out;
            }
        }
        if ($stz) {
            date_default_timezone_set($stz);
        }
        return $out;
    }
    /**
     * Return folders list in HTML
     *
     * @param array $attrib Named parameters
     *
     * @return string HTML code for the gui object
     */
    public static function folder_list($attrib)
    {
        global $RCMAIL;
        static $a_mailboxes;
        $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
        // add some labels to client
        $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
        $type = $attrib['type'] ? $attrib['type'] : 'ul';
        unset($attrib['type']);
        if ($type == 'ul' && !$attrib['id']) {
            $attrib['id'] = 'rcmboxlist';
        }
        if (empty($attrib['folder_name'])) {
            $attrib['folder_name'] = '*';
        }
        // get current folder
        $mbox_name = $RCMAIL->storage->get_folder();
        // build the folders tree
        if (empty($a_mailboxes)) {
            // get mailbox list
            $a_folders = $RCMAIL->storage->list_folders_subscribed(
                '', $attrib['folder_name'], $attrib['folder_filter']);
            $delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
            $a_mailboxes = array();
            foreach ($a_folders as $folder) {
                self::build_folder_tree($a_mailboxes, $folder, $delimiter);
            }
        }
        // allow plugins to alter the folder tree or to localize folder names
        $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array(
            'list'      => $a_mailboxes,
            'delimiter' => $delimiter,
            'type'      => $type,
            'attribs'   => $attrib,
        ));
        $a_mailboxes = $hook['list'];
        $attrib      = $hook['attribs'];
        if ($type == 'select') {
            $select = new html_select($attrib);
            // add no-selection option
            if ($attrib['noselection']) {
                $select->add(self::label($attrib['noselection']), '');
            }
            self::render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
            $out = $select->show($attrib['default']);
        }
        else {
            $js_mailboxlist = array();
            $out = html::tag('ul', $attrib, self::render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
            $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
            $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
            $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']);
            $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
        }
        return $out;
    }
    /**
     * Return folders list as html_select object
     *
     * @param array $p  Named parameters
     *
     * @return html_select HTML drop-down object
     */
    public static function folder_selector($p = array())
    {
        global $RCMAIL;
        $p += array('maxlength' => 100, 'realnames' => false);
        $a_mailboxes = array();
        $storage = $RCMAIL->get_storage();
        if (empty($p['folder_name'])) {
            $p['folder_name'] = '*';
        }
        if ($p['unsubscribed']) {
            $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
        }
        else {
            $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
        }
        $delimiter = $storage->get_hierarchy_delimiter();
        foreach ($list as $folder) {
            if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) {
                self::build_folder_tree($a_mailboxes, $folder, $delimiter);
            }
        }
        $select = new html_select($p);
        if ($p['noselection']) {
            $select->add($p['noselection'], '');
        }
        self::render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
        return $select;
    }
    /**
     * Create a hierarchical array of the mailbox list
     */
    private static function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
    {
        global $RCMAIL;
        // Handle namespace prefix
        $prefix = '';
        if (!$path) {
            $n_folder = $folder;
            $folder = $RCMAIL->storage->mod_folder($folder);
            if ($n_folder != $folder) {
                $prefix = substr($n_folder, 0, -strlen($folder));
            }
        }
        $pos = strpos($folder, $delm);
        if ($pos !== false) {
            $subFolders    = substr($folder, $pos+1);
            $currentFolder = substr($folder, 0, $pos);
            // sometimes folder has a delimiter as the last character
            if (!strlen($subFolders)) {
                $virtual = false;
            }
            else if (!isset($arrFolders[$currentFolder])) {
                $virtual = true;
            }
            else {
                $virtual = $arrFolders[$currentFolder]['virtual'];
            }
        }
        else {
            $subFolders    = false;
            $currentFolder = $folder;
            $virtual       = false;
        }
        $path .= $prefix . $currentFolder;
        if (!isset($arrFolders[$currentFolder])) {
            $arrFolders[$currentFolder] = array(
                'id' => $path,
                'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
                'virtual' => $virtual,
                'folders' => array());
        }
        else {
            $arrFolders[$currentFolder]['virtual'] = $virtual;
        }
        if (strlen($subFolders)) {
            self::build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
        }
    }
    /**
     * Return html for a structured list &lt;ul&gt; for the mailbox tree
     */
    private static function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
    {
        global $RCMAIL;
        $maxlength = intval($attrib['maxlength']);
        $realnames = (bool)$attrib['realnames'];
        $msgcounts = $RCMAIL->storage->get_cache('messagecount');
        $collapsed = $RCMAIL->config->get('collapsed_folders');
        $out = '';
        foreach ($arrFolders as $key => $folder) {
            $title        = null;
            $folder_class = self::folder_classname($folder['id']);
            $collapsed    = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
            $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
            if ($folder_class && !$realnames) {
                $foldername = $RCMAIL->gettext($folder_class);
            }
            else {
                $foldername = $folder['name'];
                // shorten the folder name to a given length
                if ($maxlength && $maxlength > 1) {
                    $fname = abbreviate_string($foldername, $maxlength);
                    if ($fname != $foldername) {
                        $title = $foldername;
                    }
                    $foldername = $fname;
                }
            }
            // make folder name safe for ids and class names
            $folder_id = self::html_identifier($folder['id'], true);
            $classes   = array('mailbox');
            // set special class for Sent, Drafts, Trash and Junk
            if ($folder_class) {
                $classes[] = $folder_class;
            }
            if ($folder['id'] == $mbox_name) {
                $classes[] = 'selected';
            }
            if ($folder['virtual']) {
                $classes[] = 'virtual';
            }
            else if ($unread) {
                $classes[] = 'unread';
            }
            $js_name = self::JQ($folder['id']);
            $html_name = self::Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
            $link_attrib = $folder['virtual'] ? array() : array(
                'href' => self::url('', array('_mbox' => $folder['id'])),
                'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
                'rel' => $folder['id'],
                'title' => $title,
            );
            $out .= html::tag('li', array(
                'id' => "rcmli".$folder_id,
                'class' => join(' ', $classes),
                'noclose' => true),
                html::a($link_attrib, $html_name) .
                (!empty($folder['folders']) ? html::div(array(
                    'class' => ($collapsed ? 'collapsed' : 'expanded'),
                    'style' => "position:absolute",
                    'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
                ), '&nbsp;') : ''));
            $jslist[$folder_id] = array(
                'id'      => $folder['id'],
                'name'    => $foldername,
                'virtual' => $folder['virtual']
            );
            if (!empty($folder['folders'])) {
                $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
                    self::render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
            }
            $out .= "</li>\n";
        }
        return $out;
    }
    /**
     * Return html for a flat list <select> for the mailbox tree
     */
    private static function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
    {
        global $RCMAIL;
        $out = '';
        foreach ($arrFolders as $key => $folder) {
            // skip exceptions (and its subfolders)
            if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
                continue;
            }
            // skip folders in which it isn't possible to create subfolders
            if (!empty($opts['skip_noinferiors'])) {
                $attrs = $RCMAIL->storage->folder_attributes($folder['id']);
                if ($attrs && in_array('\\Noinferiors', $attrs)) {
                    continue;
                }
            }
            if (!$realnames && ($folder_class = self::folder_classname($folder['id']))) {
                $foldername = self::label($folder_class);
            }
            else {
                $foldername = $folder['name'];
                // shorten the folder name to a given length
                if ($maxlength && $maxlength > 1) {
                    $foldername = abbreviate_string($foldername, $maxlength);
                }
                 $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
                if (!empty($folder['folders'])) {
                    $out .= self::render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
                        $select, $realnames, $nestLevel+1, $opts);
                }
            }
        }
        return $out;
    }
    /**
     * Return internal name for the given folder if it matches the configured special folders
     */
    private static function folder_classname($folder_id)
    {
        global $CONFIG;
        if ($folder_id == 'INBOX') {
            return 'inbox';
        }
        // for these mailboxes we have localized labels and css classes
        foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
        {
            if ($folder_id == $CONFIG[$smbx.'_mbox']) {
                return $smbx;
            }
        }
    }
    /**
     * Try to localize the given IMAP folder name.
     * UTF-7 decode it in case no localized text was found
     *
     * @param string $name  Folder name
     *
     * @return string Localized folder name in UTF-8 encoding
     */
    public static function localize_foldername($name)
    {
        if ($folder_class = self::folder_classname($name)) {
            return self::label($folder_class);
        }
        else {
            return rcube_charset::convert($name, 'UTF7-IMAP');
        }
    }
    public static function localize_folderpath($path)
    {
        global $RCMAIL;
        $protect_folders = $RCMAIL->config->get('protect_default_folders');
        $default_folders = (array) $RCMAIL->config->get('default_folders');
        $delimiter       = $RCMAIL->storage->get_hierarchy_delimiter();
        $path            = explode($delimiter, $path);
        $result          = array();
        foreach ($path as $idx => $dir) {
            $directory = implode($delimiter, array_slice($path, 0, $idx+1));
            if ($protect_folders && in_array($directory, $default_folders)) {
                unset($result);
                $result[] = self::localize_foldername($directory);
            }
            else {
                $result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
            }
        }
        return implode($delimiter, $result);
    }
    public static function quota_display($attrib)
    {
        global $OUTPUT;
        if (!$attrib['id']) {
            $attrib['id'] = 'rcmquotadisplay';
        }
        $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
        $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
        $quota = self::quota_content($attrib);
        $OUTPUT->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
        return html::span($attrib, '');
    }
    public static function quota_content($attrib = null)
    {
        global $RCMAIL;
        $quota = $RCMAIL->storage->get_quota();
        $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
        $quota_result = (array) $quota;
        $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
        if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
            $quota_result['title']   = self::label('unlimited');
            $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));
            }
            $title = sprintf('%s / %s (%.0f%%)',
                self::show_bytes($quota['used'] * 1024), self::show_bytes($quota['total'] * 1024),
                $quota_result['percent']);
            $quota_result['title'] = $title;
            if ($attrib['width']) {
                $quota_result['width'] = $attrib['width'];
            }
            if ($attrib['height']) {
                $quota_result['height']    = $attrib['height'];
            }
        }
        else {
            $quota_result['title']   = self::label('unknown');
            $quota_result['percent'] = 0;
        }
        return $quota_result;
    }
    /**
     * Outputs error message according to server error/response codes
     *
     * @param string $fallback       Fallback message label
     * @param array  $fallback_args  Fallback message label arguments
     */
    public static function display_server_error($fallback = null, $fallback_args = null)
    {
        global $RCMAIL;
        $err_code = $RCMAIL->storage->get_error_code();
        $res_code = $RCMAIL->storage->get_response_code();
        if ($err_code < 0) {
            $RCMAIL->output->show_message('storageerror', 'error');
        }
        else if ($res_code == rcube_storage::NOPERM) {
            $RCMAIL->output->show_message('errornoperm', 'error');
        }
        else if ($res_code == rcube_storage::READONLY) {
            $RCMAIL->output->show_message('errorreadonly', 'error');
        }
        else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) {
            // try to detect access rights problem and display appropriate message
            if (stripos($err_str, 'Permission denied') !== false) {
                $RCMAIL->output->show_message('errornoperm', 'error');
            }
            else {
                $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
            }
        }
        else if ($fallback) {
            $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
        }
    }
    /**
     * Generate CSS classes from mimetype and filename extension
     *
     * @param string $mimetype  Mimetype
     * @param string $filename  Filename
     *
     * @return string CSS classes separated by space
     */
    public static function file2class($mimetype, $filename)
    {
        list($primary, $secondary) = explode('/', $mimetype);
        $classes = array($primary ? $primary : 'unknown');
        if ($secondary) {
            $classes[] = $secondary;
        }
        if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
            $classes[] = $m[1];
        }
        return strtolower(join(" ", $classes));
    }
    /**
     * Output HTML editor scripts
     *
     * @param string $mode  Editor mode
     */
    public static function html_editor($mode = '')
    {
        global $RCMAIL;
        $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
        if ($hook['abort']) {
            return;
        }
        $lang = strtolower($_SESSION['language']);
        // TinyMCE uses two-letter lang codes, with exception of Chinese
        if (strpos($lang, 'zh_') === 0) {
            $lang = str_replace('_', '-', $lang);
        }
        else {
            $lang = substr($lang, 0, 2);
        }
        if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) {
            $lang = 'en';
        }
        $script = json_encode(array(
            'mode'       => $mode,
            'lang'       => $lang,
            'skin_path'  => $RCMAIL->output->get_skin_path(),
            'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
            'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary'))
        ));
        $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
        $RCMAIL->output->include_script('editor.js');
        $RCMAIL->output->add_script("rcmail_editor_init($script)", 'docready');
    }
    /**
     * Replaces TinyMCE's emoticon images with plain-text representation
     *
     * @param string $html  HTML content
     *
     * @return string HTML content
     */
    public static function replace_emoticons($html)
    {
        $emoticons = array(
            '8-)' => 'smiley-cool',
            ':-#' => 'smiley-foot-in-mouth',
            ':-*' => 'smiley-kiss',
            ':-X' => 'smiley-sealed',
            ':-P' => 'smiley-tongue-out',
            ':-@' => 'smiley-yell',
            ":'(" => 'smiley-cry',
            ':-(' => 'smiley-frown',
            ':-D' => 'smiley-laughing',
            ':-)' => 'smiley-smile',
            ':-S' => 'smiley-undecided',
            ':-$' => 'smiley-embarassed',
            'O:-)' => 'smiley-innocent',
            ':-|' => 'smiley-money-mouth',
            ':-O' => 'smiley-surprised',
            ';-)' => 'smiley-wink',
        );
        foreach ($emoticons as $idx => $file) {
            // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
            $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
            $replace[] = $idx;
        }
        return preg_replace($search, $replace, $html);
    }
    /**
     * File upload progress handler.
     */
    public static function upload_progress()
    {
        global $RCMAIL;
        $prefix = ini_get('apc.rfc1867_prefix');
        $params = array(
            'action' => $RCMAIL->action,
            'name' => self::get_input_value('_progress', self::INPUT_GET),
        );
        if (function_exists('apc_fetch')) {
            $status = apc_fetch($prefix . $params['name']);
            if (!empty($status)) {
                $status['percent'] = round($status['current']/$status['total']*100);
                $params = array_merge($status, $params);
            }
        }
        if (isset($params['percent']))
            $params['text'] = self::label(array('name' => 'uploadprogress', 'vars' => array(
                'percent' => $params['percent'] . '%',
                'current' => self::show_bytes($params['current']),
                'total'   => self::show_bytes($params['total'])
        )));
        $RCMAIL->output->command('upload_progress_update', $params);
        $RCMAIL->output->send();
    }
    /**
     * Initializes file uploading interface.
     */
    public static function upload_init()
    {
        global $RCMAIL;
        // Enable upload progress bar
        if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
            if ($field_name = ini_get('apc.rfc1867_name')) {
                $RCMAIL->output->set_env('upload_progress_name', $field_name);
                $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
            }
        }
        // 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;
        }
        $RCMAIL->output->set_env('max_filesize', $max_filesize);
        $max_filesize = self::show_bytes($max_filesize);
        $RCMAIL->output->set_env('filesizeerror', self::label(array(
            'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
        return $max_filesize;
    }
    /**
     * Initializes client-side autocompletion.
     */
    public static function autocomplete_init()
    {
        global $RCMAIL;
        static $init;
        if ($init) {
            return;
        }
        $init = 1;
        if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
            $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
            if (count($book_types) > 1) {
                $RCMAIL->output->set_env('autocomplete_threads', $threads);
                $RCMAIL->output->set_env('autocomplete_sources', $book_types);
            }
        }
        $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
        $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
        $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
    }
    /**
     * Returns supported font-family specifications
     *
     * @param string $font  Font name
     *
     * @param string|array Font-family specification array or string (if $font is used)
     */
    public static function font_defs($font = null)
    {
        $fonts = array(
            'Andale Mono'   => '"Andale Mono",Times,monospace',
            'Arial'         => 'Arial,Helvetica,sans-serif',
            'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
            'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
            'Courier New'   => '"Courier New",Courier,monospace',
            'Georgia'       => 'Georgia,Palatino,serif',
            'Helvetica'     => 'Helvetica,Arial,sans-serif',
            'Impact'        => 'Impact,Chicago,sans-serif',
            'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
            'Terminal'      => 'Terminal,Monaco,monospace',
            'Times New Roman' => '"Times New Roman",Times,serif',
            'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
            'Verdana'       => 'Verdana,Geneva,sans-serif',
        );
        if ($font) {
            return $fonts[$font];
        }
        return $fonts;
    }
    /**
     * Create a human readable string for a number of bytes
     *
     * @param int Number of bytes
     *
     * @return string Byte string
     */
    public static function show_bytes($bytes)
    {
        if ($bytes >= 1073741824) {
            $gb  = $bytes/1073741824;
            $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . self::label('GB');
        }
        else if ($bytes >= 1048576) {
            $mb  = $bytes/1048576;
            $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . self::label('MB');
        }
        else if ($bytes >= 1024) {
            $str = sprintf("%d ",  round($bytes/1024)) . self::label('KB');
        }
        else {
            $str = sprintf('%d ', $bytes) . self::label('B');
        }
        return $str;
    }
    /**
     * Decode escaped entities used by known XSS exploits.
     * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
     *
     * @param string CSS content to decode
     *
     * @return string Decoded string
     * @todo I'm not sure this should belong to rcube_ui class
     */
    public static function xss_entity_decode($content)
    {
        $out = html_entity_decode(html_entity_decode($content));
        $out = preg_replace_callback('/\\\([0-9a-f]{4})/i',
            array(self, 'xss_entity_decode_callback'), $out);
        $out = preg_replace('#/\*.*\*/#Ums', '', $out);
        return $out;
    }
    /**
     * preg_replace_callback callback for xss_entity_decode
     *
     * @param array $matches Result from preg_replace_callback
     *
     * @return string Decoded entity
     */
    public static function xss_entity_decode_callback($matches)
    {
        return chr(hexdec($matches[1]));
    }
    /**
     * Check if we can process not exceeding memory_limit
     *
     * @param integer Required amount of memory
     *
     * @return boolean True if memory won't be exceeded, False otherwise
     */
    public static function mem_check($need)
    {
        $mem_limit = parse_bytes(ini_get('memory_limit'));
        $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
        return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
    }
    /**
     * Check if working in SSL mode
     *
     * @param integer $port      HTTPS port number
     * @param boolean $use_https Enables 'use_https' option checking
     *
     * @return boolean
     */
    public static function https_check($port=null, $use_https=true)
    {
        global $RCMAIL;
        if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
            return true;
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') {
            return true;
        }
        if ($port && $_SERVER['SERVER_PORT'] == $port) {
            return true;
        }
        if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https')) {
            return true;
        }
        return false;
    }
}
program/include/rcube_user.php
@@ -66,7 +66,7 @@
        if ($id && !$sql_arr) {
            $sql_result = $this->db->query(
                "SELECT * FROM ".get_table_name('users')." WHERE user_id = ?", $id);
                "SELECT * FROM ".$this->db->table_name('users')." WHERE user_id = ?", $id);
            $sql_arr = $this->db->fetch_assoc($sql_result);
        }
@@ -127,9 +127,9 @@
            if (!empty($_SESSION['preferences'])) {
                // Check last write attempt time, try to write again (every 5 minutes)
                if ($_SESSION['preferences_time'] < time() - 5 * 60) {
            $saved_prefs = unserialize($_SESSION['preferences']);
                    $saved_prefs = unserialize($_SESSION['preferences']);
                    $this->rc->session->remove('preferences');
                $this->rc->session->remove('preferences_time');
                    $this->rc->session->remove('preferences_time');
                    $this->save_prefs($saved_prefs);
                }
                else {
@@ -173,7 +173,7 @@
        $save_prefs = serialize($save_prefs);
        $this->db->query(
            "UPDATE ".get_table_name('users').
            "UPDATE ".$this->db->table_name('users').
            " SET preferences = ?".
                ", language = ?".
            " WHERE user_id = ?",
@@ -232,7 +232,7 @@
        $result = array();
        $sql_result = $this->db->query(
            "SELECT * FROM ".get_table_name('identities').
            "SELECT * FROM ".$this->db->table_name('identities').
            " WHERE del <> 1 AND user_id = ?".
            ($sql_add ? " ".$sql_add : "").
            " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
@@ -267,7 +267,7 @@
        $query_params[] = $iid;
        $query_params[] = $this->ID;
        $sql = "UPDATE ".get_table_name('identities').
        $sql = "UPDATE ".$this->db->table_name('identities').
            " SET changed = ".$this->db->now().", ".join(', ', $query_cols).
            " WHERE identity_id = ?".
                " AND user_id = ?".
@@ -301,7 +301,7 @@
        $insert_cols[]   = 'user_id';
        $insert_values[] = $this->ID;
        $sql = "INSERT INTO ".get_table_name('identities').
        $sql = "INSERT INTO ".$this->db->table_name('identities').
            " (changed, ".join(', ', $insert_cols).")".
            " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
@@ -324,7 +324,7 @@
            return false;
        $sql_result = $this->db->query(
            "SELECT count(*) AS ident_count FROM ".get_table_name('identities').
            "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities').
            " WHERE user_id = ? AND del <> 1",
            $this->ID);
@@ -335,7 +335,7 @@
            return -1;
        $this->db->query(
            "UPDATE ".get_table_name('identities').
            "UPDATE ".$this->db->table_name('identities').
            " SET del = 1, changed = ".$this->db->now().
            " WHERE user_id = ?".
                " AND identity_id = ?",
@@ -355,7 +355,7 @@
    {
        if ($this->ID && $iid) {
            $this->db->query(
                "UPDATE ".get_table_name('identities').
                "UPDATE ".$this->db->table_name('identities').
                " SET ".$this->db->quoteIdentifier('standard')." = '0'".
                " WHERE user_id = ?".
                    " AND identity_id <> ?".
@@ -373,7 +373,7 @@
    {
        if ($this->ID) {
            $this->db->query(
                "UPDATE ".get_table_name('users').
                "UPDATE ".$this->db->table_name('users').
                " SET last_login = ".$this->db->now().
                " WHERE user_id = ?",
                $this->ID);
@@ -403,7 +403,7 @@
        $dbh = rcmail::get_instance()->get_dbh();
        // query for matching user name
        $query = "SELECT * FROM ".get_table_name('users')." WHERE mail_host = ? AND %s = ?";
        $query = "SELECT * FROM ".$dbh->table_name('users')." WHERE mail_host = ? AND %s = ?";
        $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user);
        // query for matching alias
@@ -451,7 +451,7 @@
        $dbh = $rcmail->get_dbh();
        $dbh->query(
            "INSERT INTO ".get_table_name('users').
            "INSERT INTO ".$dbh->table_name('users').
            " (created, last_login, username, mail_host, alias, language)".
            " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)",
            strip_newlines($user),
@@ -507,7 +507,7 @@
            }
        }
        else {
            raise_error(array(
            rcube::raise_error(array(
                'code' => 500,
                'type' => 'php',
                'line' => __LINE__,
@@ -573,7 +573,7 @@
        $sql_result = $this->db->query(
            "SELECT search_id AS id, ".$this->db->quoteIdentifier('name')
            ." FROM ".get_table_name('searches')
            ." FROM ".$this->db->table_name('searches')
            ." WHERE user_id = ?"
                ." AND ".$this->db->quoteIdentifier('type')." = ?"
            ." ORDER BY ".$this->db->quoteIdentifier('name'),
@@ -607,7 +607,7 @@
            "SELECT ".$this->db->quoteIdentifier('name')
                .", ".$this->db->quoteIdentifier('data')
                .", ".$this->db->quoteIdentifier('type')
            ." FROM ".get_table_name('searches')
            ." FROM ".$this->db->table_name('searches')
            ." WHERE user_id = ?"
                ." AND search_id = ?",
            (int) $this->ID, (int) $id);
@@ -638,7 +638,7 @@
            return false;
        $this->db->query(
            "DELETE FROM ".get_table_name('searches')
            "DELETE FROM ".$this->db->table_name('searches')
            ." WHERE user_id = ?"
                ." AND search_id = ?",
            (int) $this->ID, $sid);
@@ -668,7 +668,7 @@
        $insert_cols[]   = $this->db->quoteIdentifier('data');
        $insert_values[] = serialize($data['data']);
        $sql = "INSERT INTO ".get_table_name('searches')
        $sql = "INSERT INTO ".$this->db->table_name('searches')
            ." (".join(', ', $insert_cols).")"
            ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
program/include/rcube_vcard.php
@@ -389,7 +389,7 @@
        if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
          foreach ($subnode as $j => $value) {
            if (is_numeric($j) && is_string($value))
              $card[$key][$i][$j] = rcube_charset_convert($value, $charset);
              $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
          }
          unset($card[$key][$i]['charset']);
        }
@@ -425,7 +425,7 @@
      $charset = null;
    // detect charset and convert to utf-8
    else if (($charset = self::detect_encoding($data)) && $charset != RCMAIL_CHARSET) {
      $data = rcube_charset_convert($data, $charset);
      $data = rcube_charset::convert($data, $charset);
      $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
      $charset = RCMAIL_CHARSET;
    }
@@ -780,7 +780,7 @@
        )*\z/xs', substr($string, 0, 2048)))
      return 'UTF-8';
    return rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1
    return rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1
  }
}
program/steps/addressbook/func.inc
@@ -203,7 +203,7 @@
            'rel' => '%s',
            'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
    $sources = (array) $OUTPUT->env['address_sources'];
    $sources = (array) $OUTPUT->get_env('address_sources');
    reset($sources);
    // currently selected source
program/steps/mail/compose.inc
@@ -1162,10 +1162,22 @@
    $data = $message->get_part_content($pid);
  }
  $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
  $filename = $part->filename;
  if (!strlen($filename)) {
    if ($mimetype == 'text/html') {
      $filename = rcube_label('htmlmessage');
    }
    else {
      $filename = 'Part_'.$pid;
    }
    $filename .= '.' . $part->ctype_secondary;
  }
  $attachment = array(
    'group' => $COMPOSE['id'],
    'name' => $part->filename ? $part->filename : 'Part_'.$pid.'.'.$part->ctype_secondary,
    'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
    'name' => $filename,
    'mimetype' => $mimetype,
    'content_id' => $part->content_id,
    'data' => $data,
    'path' => $path,
program/steps/mail/func.inc
@@ -230,7 +230,7 @@
  // Make sure there are no duplicated columns (#1486999)
  $a_show_cols = array_unique($a_show_cols);
  // Plugins may set header's list_cols/list_flags and other rcube_mail_header variables
  // Plugins may set header's list_cols/list_flags and other rcube_message_header variables
  // and list columns
  $plugin = $RCMAIL->plugins->exec_hook('messages_list',
    array('messages' => $a_headers, 'cols' => $a_show_cols));
@@ -1024,10 +1024,20 @@
    foreach ($MESSAGE->parts as $i => $part) {
      if ($part->type == 'headers')
        $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
      else if ($part->type == 'content' && $part->size) {
      else if ($part->type == 'content') {
        // unsapported
        if ($part->realtype) {
          if ($part->realtype == 'multipart/encrypted') {
            $out .= html::span('part-notice', rcube_label('encryptedmessage'));
          }
          continue;
        }
        else if (!$part->size) {
          continue;
        }
        // Check if we have enough memory to handle the message in it
        // #1487424: we need up to 10x more memory than the body
        if (!rcmail_mem_check($part->size * 10)) {
        else if (!rcmail_mem_check($part->size * 10)) {
          $out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
            . html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part='.$part->mime_id
              .'&_mbox='. urlencode($RCMAIL->storage->get_folder()), rcube_label('download')));
@@ -1438,9 +1448,14 @@
  $part = $MESSAGE->mime_parts[$part];
  $table = new html_table(array('cols' => 3));
  if (!empty($part->filename)) {
  $filename = $part->filename;
  if (empty($filename) && $attach_prop->mimetype == 'text/html') {
    $filename = rcube_label('htmlmessage');
  }
  if (!empty($filename)) {
    $table->add('title', Q(rcube_label('filename')));
    $table->add('header', Q($part->filename));
    $table->add('header', Q($filename));
    $table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
  }
program/steps/mail/get.inc
@@ -69,8 +69,15 @@
// show part page
if (!empty($_GET['_frame'])) {
  if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id]) && $part->filename)
    $OUTPUT->set_pagetitle($part->filename);
  if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id])) {
    $filename = $part->filename;
    if (empty($filename) && $part->mimetype == 'text/html') {
      $filename = rcube_label('htmlmessage');
    }
    if (!empty($filename)) {
      $OUTPUT->set_pagetitle($filename);
    }
  }
  $OUTPUT->send('messagepart');
  exit;
@@ -130,15 +137,25 @@
        $out = rcmail_print_body($part, array('safe' => $MESSAGE->is_safe, 'inline_html' => false));
      }
      $OUTPUT = new rcube_html_page();
      $OUTPUT = new rcube_output_html();
      $OUTPUT->write($out);
    }
    else {
      // don't kill the connection if download takes more than 30 sec.
      @set_time_limit(0);
      if ($part->filename) {
        $filename = $part->filename;
      }
      else if ($part->mimetype == 'text/html') {
        $filename = rcube_label('htmlmessage');
      }
      else {
        $filename = ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube');
      }
      $ext      = '.' . ($mimetype == 'text/plain' ? 'txt' : $ctype_secondary);
      $filename = $part->filename ? $part->filename : ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube') . $ext;
      $filename .= $ext;
      $filename = preg_replace('[\r\n]', '', $filename);
      if ($browser->ie && $browser->ver < 7)
program/steps/mail/show.inc
@@ -126,20 +126,24 @@
  if (sizeof($MESSAGE->attachments)) {
    foreach ($MESSAGE->attachments as $attach_prop) {
      if ($PRINT_MODE) {
        $ol .= html::tag('li', null, sprintf("%s (%s)", Q($attach_prop->filename), Q(show_bytes($attach_prop->size))));
      }
      else {
        if (mb_strlen($attach_prop->filename) > 50) {
          $filename = abbreviate_string($attach_prop->filename, 50);
          $title = $attach_prop->filename;
      }
      else {
        $filename = $attach_prop->filename;
        $title = '';
      $filename = $attach_prop->filename;
      if (empty($filename) && $attach_prop->mimetype == 'text/html') {
        $filename = rcube_label('htmlmessage');
      }
        $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $attach_prop->filename),
      if ($PRINT_MODE) {
        $ol .= html::tag('li', null, sprintf("%s (%s)", Q($filename), Q(show_bytes($attach_prop->size))));
      }
      else {
        if (mb_strlen($filename) > 50) {
          $filename = abbreviate_string($filename, 50);
          $title = $filename;
        }
        else {
          $title = '';
        }
        $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $filename),
          html::a(array(
            'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
            'onclick' => sprintf(
program/steps/utils/error.inc
@@ -22,6 +22,7 @@
*/
$rcmail = rcmail::get_instance();
// browser is not compatible with this application
if ($ERROR_CODE==409) {
@@ -88,7 +89,7 @@
  $__error_title = "SERVICE CURRENTLY NOT AVAILABLE!";
  $__error_text  = "Please contact your server-administrator.";
  if (($CONFIG['debug_level'] & 4) && $ERROR_MESSAGE)
  if (($rcmail->config->get('debug_level') & 4) && $ERROR_MESSAGE)
    $__error_text = $ERROR_MESSAGE;
  else
    $__error_text = sprintf('Error No. [%s]', $ERROR_CODE);
@@ -97,7 +98,7 @@
$HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500;
// Ajax request
if ($OUTPUT && ($OUTPUT instanceof rcube_json_output)) {
if ($rcmail->output && $rcmail->output->type == 'js') {
  header("HTTP/1.0 $HTTP_ERR_CODE $__error_title");
  die;
}
@@ -110,13 +111,13 @@
</div>
EOF;
if ($OUTPUT && $OUTPUT->template_exists('error')) {
  $OUTPUT->reset();
  $OUTPUT->send('error');
if ($rcmail->output && $rcmail->output->template_exists('error')) {
  $rcmail->output->reset();
  $rcmail->output->send('error');
}
$__skin = $CONFIG->skin ? $CONFIG->skin : 'default';
$__productname = $CONFIG['product_name'] ? $CONFIG['product_name'] : 'Roundcube Webmail';
$__skin = $rcmail->config->get('skin', 'default');
$__productname = $rcmail->config->get('product_name', 'Roundcube Webmail');
// print system error page
print <<<EOF