summaryrefslogtreecommitdiff
path: root/program/lib/Roundcube
diff options
context:
space:
mode:
Diffstat (limited to 'program/lib/Roundcube')
-rw-r--r--program/lib/Roundcube/bootstrap.php4
-rw-r--r--program/lib/Roundcube/html.php14
-rw-r--r--program/lib/Roundcube/rcube.php121
-rw-r--r--program/lib/Roundcube/rcube_browser.php16
-rw-r--r--program/lib/Roundcube/rcube_cache.php35
-rw-r--r--program/lib/Roundcube/rcube_charset.php11
-rw-r--r--program/lib/Roundcube/rcube_config.php14
-rw-r--r--program/lib/Roundcube/rcube_csv2vcard.php2
-rw-r--r--program/lib/Roundcube/rcube_db.php83
-rw-r--r--program/lib/Roundcube/rcube_db_mssql.php20
-rw-r--r--program/lib/Roundcube/rcube_db_mysql.php11
-rw-r--r--program/lib/Roundcube/rcube_db_pgsql.php25
-rw-r--r--program/lib/Roundcube/rcube_db_sqlsrv.php121
-rw-r--r--program/lib/Roundcube/rcube_html2text.php9
-rw-r--r--program/lib/Roundcube/rcube_image.php120
-rw-r--r--program/lib/Roundcube/rcube_imap.php459
-rw-r--r--program/lib/Roundcube/rcube_imap_cache.php5
-rw-r--r--program/lib/Roundcube/rcube_imap_generic.php113
-rw-r--r--program/lib/Roundcube/rcube_imap_search.php231
-rw-r--r--program/lib/Roundcube/rcube_ldap.php19
-rw-r--r--program/lib/Roundcube/rcube_ldap_generic.php14
-rw-r--r--program/lib/Roundcube/rcube_message.php5
-rw-r--r--program/lib/Roundcube/rcube_message_header.php9
-rw-r--r--program/lib/Roundcube/rcube_mime.php30
-rw-r--r--program/lib/Roundcube/rcube_output.php12
-rw-r--r--program/lib/Roundcube/rcube_plugin.php8
-rw-r--r--program/lib/Roundcube/rcube_plugin_api.php6
-rw-r--r--program/lib/Roundcube/rcube_result_index.php21
-rw-r--r--program/lib/Roundcube/rcube_result_multifolder.php337
-rw-r--r--program/lib/Roundcube/rcube_result_set.php30
-rw-r--r--program/lib/Roundcube/rcube_result_thread.php4
-rw-r--r--program/lib/Roundcube/rcube_session.php12
-rw-r--r--program/lib/Roundcube/rcube_smtp.php14
-rw-r--r--program/lib/Roundcube/rcube_spellcheck_googie.php4
-rw-r--r--program/lib/Roundcube/rcube_spellchecker.php2
-rw-r--r--program/lib/Roundcube/rcube_storage.php98
-rw-r--r--program/lib/Roundcube/rcube_string_replacer.php2
-rw-r--r--program/lib/Roundcube/rcube_text2html.php307
-rw-r--r--program/lib/Roundcube/rcube_user.php5
-rw-r--r--program/lib/Roundcube/rcube_utils.php75
-rw-r--r--program/lib/Roundcube/rcube_vcard.php5
-rw-r--r--program/lib/Roundcube/rcube_washtml.php111
42 files changed, 2050 insertions, 494 deletions
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 6e5143382..65ef98ebd 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube PHP suite |
- | Copyright (C) 2005-2013, The Roundcube Dev Team |
+ | Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -54,7 +54,7 @@ foreach ($config as $optname => $optval) {
}
// framework constants
-define('RCUBE_VERSION', '1.0-git');
+define('RCUBE_VERSION', '1.1-git');
define('RCUBE_CHARSET', 'UTF-8');
if (!defined('RCUBE_LIB_DIR')) {
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index 33517fbcd..a88570d75 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -269,19 +269,27 @@ class html
return '';
}
- $allowed_f = array_flip((array)$allowed);
+ $allowed_f = array_flip((array)$allowed);
$attrib_arr = array();
+
foreach ($attrib as $key => $value) {
// skip size if not numeric
if ($key == 'size' && !is_numeric($value)) {
continue;
}
- // ignore "internal" or not allowed attributes
- if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) {
+ // ignore "internal" or empty attributes
+ if ($key == 'nl' || $value === null) {
continue;
}
+ // ignore not allowed attributes, except data-*
+ if (!empty($allowed)) {
+ if (!isset($allowed_f[$key]) && @substr_compare($key, 'data-', 0, 5) !== 0) {
+ continue;
+ }
+ }
+
// skip empty eventhandlers
if (preg_match('/^on[a-z]+/', $key) && !$value) {
continue;
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index d58eb087b..d618fb64d 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -3,8 +3,8 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
+ | Copyright (C) 2008-2014, The Roundcube Dev Team |
+ | Copyright (C) 2011-2014, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -94,6 +94,13 @@ class rcube
*/
public $plugins;
+ /**
+ * Instance of rcube_user class.
+ *
+ * @var rcube_user
+ */
+ public $user;
+
/* private/protected vars */
protected $texts;
@@ -348,29 +355,6 @@ class rcube
// 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'),
@@ -405,22 +389,65 @@ class rcube
/**
* Set storage parameters.
- * This must be done AFTER connecting to the server!
*/
protected function set_storage_prop()
{
$storage = $this->get_storage();
+ // set pagesize from config
+ $pagesize = $this->config->get('mail_pagesize');
+ if (!$pagesize) {
+ $pagesize = $this->config->get('pagesize', 50);
+ }
+
+ $storage->set_pagesize($pagesize);
$storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET));
- if ($default_folders = $this->config->get('default_folders')) {
- $storage->set_default_folders($default_folders);
+ // enable caching of mail data
+ $driver = $this->config->get('storage_driver', 'imap');
+ $storage_cache = $this->config->get("{$driver}_cache");
+ $messages_cache = $this->config->get('messages_cache');
+ // for backward compatybility
+ if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
+ $storage_cache = 'db';
+ $messages_cache = true;
}
- if (isset($_SESSION['mbox'])) {
- $storage->set_folder($_SESSION['mbox']);
+
+ if ($storage_cache) {
+ $storage->set_caching($storage_cache);
}
- if (isset($_SESSION['page'])) {
- $storage->set_page($_SESSION['page']);
+ if ($messages_cache) {
+ $storage->set_messages_caching(true);
+ }
+ }
+
+
+ /**
+ * Set special folders type association.
+ * This must be done AFTER connecting to the server!
+ */
+ protected function set_special_folders()
+ {
+ $storage = $this->get_storage();
+ $folders = $storage->get_special_folders(true);
+ $prefs = array();
+
+ // check SPECIAL-USE flags on IMAP folders
+ foreach ($folders as $type => $folder) {
+ $idx = $type . '_mbox';
+ if ($folder !== $this->config->get($idx)) {
+ $prefs[$idx] = $folder;
+ }
+ }
+
+ // Some special folders differ, update user preferences
+ if (!empty($prefs) && $this->user) {
+ $this->user->save_prefs($prefs);
+ }
+
+ // create default folders (on login)
+ if ($this->config->get('create_default_folders')) {
+ $storage->create_default_folders();
}
}
@@ -1105,6 +1132,11 @@ class rcube
return true;
}
+ // add session ID to the log
+ if ($sess = session_id()) {
+ $line = '<' . substr($sess, 0, 8) . '> ' . $line;
+ }
+
if ($log_driver == 'syslog') {
$prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
syslog($prio, $line);
@@ -1180,8 +1212,8 @@ class rcube
}
// installer
- if (class_exists('rcube_install', false)) {
- $rci = rcube_install::get_instance();
+ if (class_exists('rcmail_install', false)) {
+ $rci = rcmail_install::get_instance();
$rci->raise_error($arg);
return;
}
@@ -1302,6 +1334,20 @@ class rcube
self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
}
+ /**
+ * Setter for system user object
+ *
+ * @param rcube_user Current user instance
+ */
+ public function set_user($user)
+ {
+ if (is_object($user)) {
+ $this->user = $user;
+
+ // overwrite config with user preferences
+ $this->config->set_user_prefs((array)$this->user->get_prefs());
+ }
+ }
/**
* Getter for logged user ID.
@@ -1438,6 +1484,13 @@ class rcube
));
if ($plugin['abort']) {
+ if (!empty($plugin['error'])) {
+ $error = $plugin['error'];
+ }
+ if (!empty($plugin['body_file'])) {
+ $body_file = $plugin['body_file'];
+ }
+
return isset($plugin['result']) ? $plugin['result'] : false;
}
diff --git a/program/lib/Roundcube/rcube_browser.php b/program/lib/Roundcube/rcube_browser.php
index e53e31200..b9642d8f9 100644
--- a/program/lib/Roundcube/rcube_browser.php
+++ b/program/lib/Roundcube/rcube_browser.php
@@ -34,14 +34,20 @@ class rcube_browser
$this->linux = strpos($HTTP_USER_AGENT, 'linux') != false;
$this->unix = strpos($HTTP_USER_AGENT, 'unix') != false;
- $this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false;
+ $this->webkit = strpos($HTTP_USER_AGENT, 'applewebkit') !== false;
+ $this->opera = strpos($HTTP_USER_AGENT, 'opera') !== false || ($this->webkit && strpos($HTTP_USER_AGENT, 'opr/') !== false);
$this->ns = strpos($HTTP_USER_AGENT, 'netscape') !== false;
- $this->chrome = strpos($HTTP_USER_AGENT, 'chrome') !== false;
+ $this->chrome = !$this->opera && strpos($HTTP_USER_AGENT, 'chrome') !== false;
$this->ie = !$this->opera && (strpos($HTTP_USER_AGENT, 'compatible; msie') !== false || strpos($HTTP_USER_AGENT, 'trident/') !== false);
- $this->safari = !$this->chrome && (strpos($HTTP_USER_AGENT, 'safari') !== false || strpos($HTTP_USER_AGENT, 'applewebkit') !== false);
- $this->mz = !$this->ie && !$this->safari && !$this->chrome && !$this->ns && strpos($HTTP_USER_AGENT, 'mozilla') !== false;
+ $this->safari = !$this->opera && !$this->chrome && ($this->webkit || strpos($HTTP_USER_AGENT, 'safari') !== false);
+ $this->mz = !$this->ie && !$this->safari && !$this->chrome && !$this->ns && !$this->opera && strpos($HTTP_USER_AGENT, 'mozilla') !== false;
- if (preg_match('/(chrome|msie|opera|version|khtml)(\s*|\/)([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
+ if ($this->opera) {
+ if (preg_match('/(opera|opr)\/([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
+ $this->ver = (float) $regs[2];
+ }
+ }
+ else if (preg_match('/(chrome|msie|version|khtml)(\s*|\/)([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
$this->ver = (float) $regs[3];
}
else if (preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs)) {
diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php
index a708cb292..0017dcacc 100644
--- a/program/lib/Roundcube/rcube_cache.php
+++ b/program/lib/Roundcube/rcube_cache.php
@@ -45,6 +45,7 @@ class rcube_cache
private $cache = array();
private $cache_changes = array();
private $cache_sums = array();
+ private $max_packet = -1;
/**
@@ -319,7 +320,7 @@ class rcube_cache
* Writes single cache record into DB.
*
* @param string $key Cache key name
- * @param mxied $data Serialized cache data
+ * @param mixed $data Serialized cache data
*
* @param boolean True on success, False on failure
*/
@@ -329,6 +330,12 @@ class rcube_cache
return false;
}
+ // don't attempt to write too big data sets
+ if (strlen($data) > $this->max_packet_size()) {
+ trigger_error("rcube_cache: max_packet_size ($this->max_packet) exceeded for key $key. Tried to write " . strlen($data) . " bytes", E_USER_WARNING);
+ return false;
+ }
+
if ($this->type == 'memcache' || $this->type == 'apc') {
return $this->add_record($this->ckey($key), $data);
}
@@ -591,4 +598,30 @@ class rcube_cache
return $this->packed ? @unserialize($data) : $data;
}
+
+ /**
+ * Determine the maximum size for cache data to be written
+ */
+ private function max_packet_size()
+ {
+ if ($this->max_packet < 0) {
+ $this->max_packet = 2097152; // default/max is 2 MB
+
+ if ($this->type == 'db') {
+ $value = $this->db->get_variable('max_allowed_packet', 1048500);
+ $this->max_packet = min($value, $this->max_packet) - 2000;
+ }
+ else if ($this->type == 'memcache') {
+ $stats = $this->db->getStats();
+ $remaining = $stats['limit_maxbytes'] - $stats['bytes'];
+ $this->max_packet = min($remaining / 5, $this->max_packet);
+ }
+ else if ($this->type == 'apc' && function_exists('apc_sma_info')) {
+ $stats = apc_sma_info();
+ $this->max_packet = min($stats['avail_mem'] / 5, $this->max_packet);
+ }
+ }
+
+ return $this->max_packet;
+ }
}
diff --git a/program/lib/Roundcube/rcube_charset.php b/program/lib/Roundcube/rcube_charset.php
index 8612e7fca..ffec67376 100644
--- a/program/lib/Roundcube/rcube_charset.php
+++ b/program/lib/Roundcube/rcube_charset.php
@@ -759,7 +759,12 @@ class rcube_charset
// iconv/mbstring are much faster (especially with long strings)
if (function_exists('mb_convert_encoding')) {
- if (($res = mb_convert_encoding($input, 'UTF-8', 'UTF-8')) !== false) {
+ $msch = mb_substitute_character('none');
+ mb_substitute_character('none');
+ $res = mb_convert_encoding($input, 'UTF-8', 'UTF-8');
+ mb_substitute_character($msch);
+
+ if ($res !== false) {
return $res;
}
}
@@ -795,8 +800,8 @@ class rcube_charset
}
$seq = '';
$out .= $chr;
- // first (or second) byte of multibyte sequence
}
+ // first (or second) byte of multibyte sequence
else if ($ord >= 0xC0) {
if (strlen($seq) > 1) {
$out .= preg_match($regexp, $seq) ? $seq : '';
@@ -806,8 +811,8 @@ class rcube_charset
$seq = '';
}
$seq .= $chr;
- // next byte of multibyte sequence
}
+ // next byte of multibyte sequence
else if ($seq) {
$seq .= $chr;
}
diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php
index 0352e4772..afe13e879 100644
--- a/program/lib/Roundcube/rcube_config.php
+++ b/program/lib/Roundcube/rcube_config.php
@@ -63,7 +63,7 @@ class rcube_config
$this->paths = explode(PATH_SEPARATOR, $paths);
// make all paths absolute
foreach ($this->paths as $i => $path) {
- if (!$this->_is_absolute($path)) {
+ if (!rcube_utils::is_absolute_path($path)) {
if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) {
$this->paths[$i] = unslashify($realpath) . '/';
}
@@ -243,8 +243,8 @@ class rcube_config
*/
public function resolve_paths($file, $use_env = true)
{
- $files = array();
- $abs_path = $this->_is_absolute($file);
+ $files = array();
+ $abs_path = rcube_utils::is_absolute_path($file);
foreach ($this->paths as $basepath) {
$realpath = $abs_path ? $file : realpath($basepath . '/' . $file);
@@ -270,14 +270,6 @@ class rcube_config
}
/**
- * Determine whether the given file path is absolute or relative
- */
- private function _is_absolute($path)
- {
- return $path[0] == DIRECTORY_SEPARATOR || preg_match('!^[a-z]:[\\\\/]!i', $path);
- }
-
- /**
* Getter for a specific config parameter
*
* @param string $name Parameter name
diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index aa385dce4..06bc387d5 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -56,7 +56,7 @@ class rcube_csv2vcard
//'email_2_type' => '',
//'email_3_address' => '', //@TODO
//'email_3_type' => '',
- 'email_address' => 'email:main',
+ 'email_address' => 'email:pref',
//'email_type' => '',
'first_name' => 'firstname',
'gender' => 'gender',
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 2828f26ee..a46df97d3 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -31,7 +31,6 @@ class rcube_db
protected $db_dsnr; // DSN for read operations
protected $db_connected = false; // Already connected ?
protected $db_mode; // Connection mode
- protected $db_table_dsn_map = array();
protected $dbh; // Connection handle
protected $dbhs = array();
protected $table_connections = array();
@@ -100,12 +99,15 @@ class rcube_db
$this->db_dsnw = $db_dsnw;
$this->db_dsnr = $db_dsnr;
$this->db_pconn = $pconn;
- $this->db_dsnw_noread = rcube::get_instance()->config->get('db_dsnw_noread', false);
$this->db_dsnw_array = self::parse_dsn($db_dsnw);
$this->db_dsnr_array = self::parse_dsn($db_dsnr);
- $this->db_table_dsn_map = array_map(array($this, 'table_name'), rcube::get_instance()->config->get('db_table_dsn', array()));
+ $config = rcube::get_instance()->config;
+
+ $this->options['table_prefix'] = $config->get('db_prefix');
+ $this->options['dsnw_noread'] = $config->get('db_dsnw_noread', false);
+ $this->options['table_dsn_map'] = array_map(array($this, 'table_name'), $config->get('db_table_dsn', array()));
}
/**
@@ -206,7 +208,7 @@ class rcube_db
// Already connected
if ($this->db_connected) {
// connected to db with the same or "higher" mode (if allowed)
- if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->db_dsnw_noread) {
+ if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->options['dsnw_noread']) {
return;
}
}
@@ -241,14 +243,14 @@ class rcube_db
$table = $m[2];
// always use direct mapping
- if ($this->db_table_dsn_map[$table]) {
- $mode = $this->db_table_dsn_map[$table];
+ if ($this->options['table_dsn_map'][$table]) {
+ $mode = $this->options['table_dsn_map'][$table];
break; // primary table rules
}
else if ($mode == 'r') {
// connected to db with the same or "higher" mode for this table
$db_mode = $this->table_connections[$table];
- if ($db_mode == 'w' && !$this->db_dsnw_noread) {
+ if ($db_mode == 'w' && !$this->options['dsnw_noread']) {
$mode = $db_mode;
}
}
@@ -920,14 +922,8 @@ class rcube_db
*/
public function table_name($table)
{
- static $rcube;
-
- if (!$rcube) {
- $rcube = rcube::get_instance();
- }
-
// add prefix to the table name if configured
- if (($prefix = $rcube->config->get('db_prefix')) && strpos($table, $prefix) !== 0) {
+ if (($prefix = $this->options['table_prefix']) && strpos($table, $prefix) !== 0) {
return $prefix . $table;
}
@@ -953,7 +949,7 @@ class rcube_db
*/
public function set_table_dsn($table, $mode)
{
- $this->db_table_dsn_map[$this->table_name($table)] = $mode;
+ $this->options['table_dsn_map'][$this->table_name($table)] = $mode;
}
/**
@@ -1129,4 +1125,61 @@ class rcube_db
return $result;
}
+
+ /**
+ * Execute the given SQL script
+ *
+ * @param string SQL queries to execute
+ *
+ * @return boolen True on success, False on error
+ */
+ public function exec_script($sql)
+ {
+ $sql = $this->fix_table_names($sql);
+ $buff = '';
+
+ foreach (explode("\n", $sql) as $line) {
+ if (preg_match('/^--/', $line) || trim($line) == '')
+ continue;
+
+ $buff .= $line . "\n";
+ if (preg_match('/(;|^GO)$/', trim($line))) {
+ $this->query($buff);
+ $buff = '';
+ if ($this->db_error) {
+ break;
+ }
+ }
+ }
+
+ return !$this->db_error;
+ }
+
+ /**
+ * Parse SQL file and fix table names according to table prefix
+ */
+ protected function fix_table_names($sql)
+ {
+ if (!$this->options['table_prefix']) {
+ return $sql;
+ }
+
+ $sql = preg_replace_callback(
+ '/((TABLE|TRUNCATE|(?<!ON )UPDATE|INSERT INTO|FROM'
+ . '| ON(?! (DELETE|UPDATE))|REFERENCES|CONSTRAINT|FOREIGN KEY|INDEX)'
+ . '\s+(IF (NOT )?EXISTS )?[`"]*)([^`"\( \r\n]+)/',
+ array($this, 'fix_table_names_callback'),
+ $sql
+ );
+
+ return $sql;
+ }
+
+ /**
+ * Preg_replace callback for fix_table_names()
+ */
+ protected function fix_table_names_callback($matches)
+ {
+ return $matches[1] . $this->options['table_prefix'] . $matches[count($matches)-1];
+ }
}
diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php
index 726e4b421..4138b1489 100644
--- a/program/lib/Roundcube/rcube_db_mssql.php
+++ b/program/lib/Roundcube/rcube_db_mssql.php
@@ -167,4 +167,24 @@ class rcube_db_mssql extends rcube_db
return $result;
}
+
+ /**
+ * Parse SQL file and fix table names according to table prefix
+ */
+ protected function fix_table_names($sql)
+ {
+ if (!$this->options['table_prefix']) {
+ return $sql;
+ }
+
+ // replace sequence names, and other postgres-specific commands
+ $sql = preg_replace_callback(
+ '/((TABLE|(?<!ON )UPDATE|INSERT INTO|FROM(?! deleted)| ON(?! (DELETE|UPDATE|\[PRIMARY\]))'
+ . '|REFERENCES|CONSTRAINT|TRIGGER|INDEX)\s+(\[dbo\]\.)?[\[\]]*)([^\[\]\( \r\n]+)/',
+ array($this, 'fix_table_names_callback'),
+ $sql
+ );
+
+ return $sql;
+ }
}
diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php
index d3d0ac5c8..400813dcc 100644
--- a/program/lib/Roundcube/rcube_db_mysql.php
+++ b/program/lib/Roundcube/rcube_db_mysql.php
@@ -38,13 +38,6 @@ class rcube_db_mysql extends rcube_db
*/
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
{
- if (version_compare(PHP_VERSION, '5.3.0', '<')) {
- rcube::raise_error(array('code' => 600, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "MySQL driver requires PHP >= 5.3, current version is " . PHP_VERSION),
- true, true);
- }
-
parent::__construct($db_dsnw, $db_dsnr, $pconn);
// SQL identifiers quoting
@@ -128,11 +121,11 @@ class rcube_db_mysql extends rcube_db
$result = array();
if (!empty($dsn['key'])) {
- $result[PDO::MYSQL_ATTR_KEY] = $dsn['key'];
+ $result[PDO::MYSQL_ATTR_SSL_KEY] = $dsn['key'];
}
if (!empty($dsn['cipher'])) {
- $result[PDO::MYSQL_ATTR_CIPHER] = $dsn['cipher'];
+ $result[PDO::MYSQL_ATTR_SSL_CIPHER] = $dsn['cipher'];
}
if (!empty($dsn['cert'])) {
diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php
index 68bf6d85d..a92d3cf36 100644
--- a/program/lib/Roundcube/rcube_db_pgsql.php
+++ b/program/lib/Roundcube/rcube_db_pgsql.php
@@ -73,10 +73,9 @@ class rcube_db_pgsql extends rcube_db
// Note: we support only one sequence per table
// Note: The sequence name must be <table_name>_seq
$sequence = $table . '_seq';
- $rcube = rcube::get_instance();
- // return sequence name if configured
- if ($prefix = $rcube->config->get('db_prefix')) {
+ // modify sequence name if prefix is configured
+ if ($prefix = $this->options['table_prefix']) {
return $prefix . $sequence;
}
@@ -190,4 +189,24 @@ class rcube_db_pgsql extends rcube_db
return $result;
}
+ /**
+ * Parse SQL file and fix table names according to table prefix
+ */
+ protected function fix_table_names($sql)
+ {
+ if (!$this->options['table_prefix']) {
+ return $sql;
+ }
+
+ $sql = parent::fix_table_names($sql);
+
+ // replace sequence names, and other postgres-specific commands
+ $sql = preg_replace_callback(
+ '/((SEQUENCE |RENAME TO |nextval\()["\']*)([^"\' \r\n]+)/',
+ array($this, 'fix_table_names_callback'),
+ $sql
+ );
+
+ return $sql;
+ }
}
diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php
index 4339f3dfd..7b64ccea2 100644
--- a/program/lib/Roundcube/rcube_db_sqlsrv.php
+++ b/program/lib/Roundcube/rcube_db_sqlsrv.php
@@ -24,126 +24,8 @@
* @package Framework
* @subpackage Database
*/
-class rcube_db_sqlsrv extends rcube_db
+class rcube_db_sqlsrv extends rcube_db_mssql
{
- public $db_provider = 'mssql';
-
- /**
- * Object constructor
- *
- * @param string $db_dsnw DSN for read/write operations
- * @param string $db_dsnr Optional DSN for read only operations
- * @param bool $pconn Enables persistent connections
- */
- public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
- {
- parent::__construct($db_dsnw, $db_dsnr, $pconn);
-
- $this->options['identifier_start'] = '[';
- $this->options['identifier_end'] = ']';
- }
-
- /**
- * Driver-specific configuration of database connection
- *
- * @param array $dsn DSN for DB connections
- * @param PDO $dbh Connection handler
- */
- protected function conn_configure($dsn, $dbh)
- {
- // Set date format in case of non-default language (#1488918)
- $dbh->query("SET DATEFORMAT ymd");
- }
-
- /**
- * Return SQL function for current time and date
- *
- * @param int $interval Optional interval (in seconds) to add/subtract
- *
- * @return string SQL function to use in query
- */
- public function now($interval = 0)
- {
- if ($interval) {
- $interval = intval($interval);
- return "dateadd(second, $interval, getdate())";
- }
-
- return "getdate()";
- }
-
- /**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * This method is deprecated and should not be used anymore due to limitations
- * of timestamp functions in Mysql (year 2038 problem)
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @deprecated
- */
- public function unixtimestamp($field)
- {
- return "DATEDIFF(second, '19700101', $field) + DATEDIFF(second, GETDATE(), GETUTCDATE())";
- }
-
- /**
- * Abstract SQL statement for value concatenation
- *
- * @return string SQL statement to be used in query
- */
- public function concat(/* col1, col2, ... */)
- {
- $args = func_get_args();
-
- if (is_array($args[0])) {
- $args = $args[0];
- }
-
- return '(' . join('+', $args) . ')';
- }
-
- /**
- * Adds TOP (LIMIT,OFFSET) clause to the query
- *
- * @param string $query SQL query
- * @param int $limit Number of rows
- * @param int $offset Offset
- *
- * @return string SQL query
- */
- protected function set_limit($query, $limit = 0, $offset = 0)
- {
- $limit = intval($limit);
- $offset = intval($offset);
- $end = $offset + $limit;
-
- // query without OFFSET
- if (!$offset) {
- $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
- return $query;
- }
-
- $orderby = stristr($query, 'ORDER BY');
- $offset += 1;
-
- if ($orderby !== false) {
- $query = trim(substr($query, 0, -1 * strlen($orderby)));
- }
- else {
- // it shouldn't happen, paging without sorting has not much sense
- // @FIXME: I don't know how to build paging query without ORDER BY
- $orderby = "ORDER BY 1";
- }
-
- $query = preg_replace('/^SELECT\s/i', '', $query);
- $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
- . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
-
- return $query;
- }
-
/**
* Returns PDO DSN string from DSN array
*/
@@ -158,6 +40,7 @@ class rcube_db_sqlsrv extends rcube_db
if ($dsn['port']) {
$host .= ',' . $dsn['port'];
}
+
$params[] = 'Server=' . $host;
}
diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
index 01362e6fb..499c4b05c 100644
--- a/program/lib/Roundcube/rcube_html2text.php
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -423,7 +423,7 @@ class rcube_html2text
// Variables used for building the link list
$this->_link_list = array();
- $text = trim(stripslashes($this->html));
+ $text = $this->html;
// Convert HTML to TXT
$this->_converter($text);
@@ -473,6 +473,9 @@ class rcube_html2text
// Replace known html entities
$text = html_entity_decode($text, ENT_QUOTES, $this->charset);
+ // Replace unicode nbsp to regular spaces
+ $text = preg_replace('/\xC2\xA0/', ' ', $text);
+
// Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
$text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
@@ -616,6 +619,10 @@ class rcube_html2text
break;
}
+ // abort on invalid tag structure (e.g. no closing tag found)
+ else {
+ break;
+ }
}
while ($end || $next);
}
diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php
index 4e4caae93..a15368a7e 100644
--- a/program/lib/Roundcube/rcube_image.php
+++ b/program/lib/Roundcube/rcube_image.php
@@ -102,10 +102,10 @@ class rcube_image
}
// use Imagemagick
- if ($convert) {
- $p['out'] = $filename;
- $p['in'] = $this->image_file;
- $type = $props['type'];
+ if ($convert || class_exists('Imagick', false)) {
+ $p['out'] = $filename;
+ $p['in'] = $this->image_file;
+ $type = $props['type'];
if (!$type && ($data = $this->identify())) {
$type = $data[0];
@@ -129,26 +129,49 @@ class rcube_image
$result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false;
}
else {
- if ($scale >= 1) {
- $width = $props['width'];
- $height = $props['height'];
- }
- else {
- $width = intval($props['width'] * $scale);
- $height = intval($props['height'] * $scale);
- }
-
$valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif";
- $p += array(
- 'type' => $type,
- 'quality' => 75,
- 'size' => $width . 'x' . $height,
- );
-
if (in_array($type, explode(',', $valid_types))) { // Valid type?
- $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
- . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
+ if ($scale >= 1) {
+ $width = $props['width'];
+ $height = $props['height'];
+ }
+ else {
+ $width = intval($props['width'] * $scale);
+ $height = intval($props['height'] * $scale);
+ }
+
+ // use ImageMagick in command line
+ if ($convert) {
+ $p += array(
+ 'type' => $type,
+ 'quality' => 75,
+ 'size' => $width . 'x' . $height,
+ );
+
+ $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
+ . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
+ }
+ // use PHP's Imagick class
+ else {
+ try {
+ $image = new Imagick($this->image_file);
+ $image = $image->flattenImages();
+
+ $image->setImageColorspace(Imagick::COLORSPACE_SRGB);
+ $image->setImageCompressionQuality(75);
+ $image->setImageFormat($type);
+ $image->stripImage();
+ $image->scaleImage($width, $height);
+
+ if ($image->writeImage($filename)) {
+ $result = '';
+ }
+ }
+ catch (Exception $e) {
+ rcube::raise_error($e, true, false);
+ }
+ }
}
}
@@ -249,7 +272,7 @@ class rcube_image
}
}
- // use ImageMagick
+ // use ImageMagick in command line
if ($convert) {
$p['in'] = $this->image_file;
$p['out'] = $filename;
@@ -258,11 +281,31 @@ class rcube_image
$result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p);
if ($result === '') {
- @chmod($filename, 0600);
+ chmod($filename, 0600);
return true;
}
}
+ // use PHP's Imagick class
+ if (class_exists('Imagick', false)) {
+ try {
+ $image = new Imagick($this->image_file);
+
+ $image->setImageColorspace(Imagick::COLORSPACE_SRGB);
+ $image->setImageCompressionQuality(75);
+ $image->setImageFormat(self::$extensions[$type]);
+ $image->stripImage();
+
+ if ($image->writeImage($filename)) {
+ @chmod($filename, 0600);
+ return true;
+ }
+ }
+ catch (Exception $e) {
+ rcube::raise_error($e, true, false);
+ }
+ }
+
// use GD extension (TIFF isn't supported)
$props = $this->props();
@@ -302,12 +345,26 @@ class rcube_image
}
/**
- * Identify command handler.
+ * Checks if image format conversion is supported
+ *
+ * @return boolean True if specified format can be converted to another format
+ */
+ public static function is_convertable($mimetype = null)
+ {
+ $rcube = rcube::get_instance();
+
+ // @TODO: check if specified mimetype is really supported
+ return class_exists('Imagick', false) || $rcube->config->get('im_convert_path');
+ }
+
+ /**
+ * ImageMagick based image properties read.
*/
private function identify()
{
$rcube = rcube::get_instance();
+ // use ImageMagick in command line
if ($cmd = $rcube->config->get('im_identify_path')) {
$args = array('in' => $this->image_file, 'format' => "%m %[fx:w] %[fx:h]");
$id = rcube::exec($cmd. ' 2>/dev/null -format {format} {in}', $args);
@@ -316,6 +373,19 @@ class rcube_image
return explode(' ', strtolower($id));
}
}
- }
+ // use PHP's Imagick class
+ if (class_exists('Imagick', false)) {
+ try {
+ $image = new Imagick($this->image_file);
+
+ return array(
+ strtolower($image->getImageFormat()),
+ $image->getImageWidth(),
+ $image->getImageHeight(),
+ );
+ }
+ catch (Exception $e) {}
+ }
+ }
}
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 432227091..78073abd6 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -332,6 +332,10 @@ class rcube_imap extends rcube_storage
$this->search_sort_field = $set[3];
$this->search_sorted = $set[4];
$this->search_threads = is_a($this->search_set, 'rcube_result_thread');
+
+ if (is_a($this->search_set, 'rcube_result_multifolder')) {
+ $this->set_threading(false);
+ }
}
@@ -945,6 +949,75 @@ class rcube_imap extends rcube_storage
return array();
}
+ // gather messages from a multi-folder search
+ if ($this->search_set->multi) {
+ $page_size = $this->page_size;
+ $sort_field = $this->sort_field;
+ $search_set = $this->search_set;
+
+ // prepare paging
+ $cnt = $search_set->count();
+ $from = ($page-1) * $page_size;
+ $to = $from + $page_size;
+ $slice_length = min($page_size, $cnt - $from);
+
+ // fetch resultset headers, sort and slice them
+ if (!empty($sort_field)) {
+ $this->sort_field = null;
+ $this->page_size = 1000; // fetch up to 1000 matching messages per folder
+ $this->threading = false;
+
+ $a_msg_headers = array();
+ foreach ($search_set->sets as $resultset) {
+ if (!$resultset->is_empty()) {
+ $this->search_set = $resultset;
+ $this->search_threads = $resultset instanceof rcube_result_thread;
+ $a_msg_headers = array_merge($a_msg_headers, $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1));
+ }
+ }
+
+ // sort headers
+ if (!empty($a_msg_headers)) {
+ $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order);
+ }
+
+ // store (sorted) message index
+ $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order);
+
+ // only return the requested part of the set
+ $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
+ }
+ else {
+ if ($this->sort_order != $search_set->get_parameters('ORDER')) {
+ $search_set->revert();
+ }
+
+ // slice resultset first...
+ $fetch = array();
+ foreach (array_slice($search_set->get(), $from, $slice_length) as $msg_id) {
+ list($uid, $folder) = explode('-', $msg_id, 2);
+ $fetch[$folder][] = $uid;
+ }
+
+ // ... and fetch the requested set of headers
+ $a_msg_headers = array();
+ foreach ($fetch as $folder => $a_index) {
+ $a_msg_headers = array_merge($a_msg_headers, array_values($this->fetch_headers($folder, $a_index)));
+ }
+ }
+
+ if ($slice) {
+ $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
+ }
+
+ // restore members
+ $this->sort_field = $sort_field;
+ $this->page_size = $page_size;
+ $this->search_set = $search_set;
+
+ return $a_msg_headers;
+ }
+
// use saved messages from searching
if ($this->threading) {
return $this->list_search_thread_messages($folder, $page, $slice);
@@ -1111,6 +1184,7 @@ class rcube_imap extends rcube_storage
}
foreach ($headers as $h) {
+ $h->folder = $folder;
$a_msg_headers[$h->uid] = $h;
}
@@ -1234,8 +1308,13 @@ class rcube_imap extends rcube_storage
return new rcube_result_index($folder, '* SORT');
}
+ if ($this->search_set instanceof rcube_result_multifolder) {
+ $index = $this->search_set;
+ $index->folder = $folder;
+ // TODO: handle changed sorting
+ }
// search result is an index with the same sorting?
- if (($this->search_set instanceof rcube_result_index)
+ else if (($this->search_set instanceof rcube_result_index)
&& ((!$this->sort_field && !$this->search_sorted) ||
($this->search_sorted && $this->search_sort_field == $this->sort_field))
) {
@@ -1410,26 +1489,75 @@ class rcube_imap extends rcube_storage
* Invoke search request to IMAP server
*
* @param string $folder Folder name to search in
- * @param string $str Search criteria
+ * @param string $search Search criteria
* @param string $charset Search charset
* @param string $sort_field Header field to sort by
*
+ * @return rcube_result_index Search result object
* @todo: Search criteria should be provided in non-IMAP format, eg. array
*/
- public function search($folder='', $str='ALL', $charset=NULL, $sort_field=NULL)
+ public function search($folder = '', $search = 'ALL', $charset = null, $sort_field = null)
{
- if (!$str) {
- $str = 'ALL';
+ if (!$search) {
+ $search = 'ALL';
}
- if (!strlen($folder)) {
+ if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) {
$folder = $this->folder;
}
- $results = $this->search_index($folder, $str, $charset, $sort_field);
+ $plugin = rcube::get_instance()->plugins->exec_hook('imap_search_before', array(
+ 'folder' => $folder,
+ 'search' => $search,
+ 'charset' => $charset,
+ 'sort_field' => $sort_field,
+ 'threading' => $this->threading,
+ ));
+
+ $folder = $plugin['folder'];
+ $search = $plugin['search'];
+ $charset = $plugin['charset'];
+ $sort_field = $plugin['sort_field'];
+ $results = $plugin['result'];
+
+ // multi-folder search
+ if (!$results && is_array($folder) && count($folder) > 1 && $search != 'ALL') {
+ // connect IMAP to have all the required classes and settings loaded
+ $this->check_connection();
+
+ // disable threading
+ $this->threading = false;
+
+ $searcher = new rcube_imap_search($this->options, $this->conn);
+
+ // set limit to not exceed the client's request timeout
+ $searcher->set_timelimit(60);
+
+ // continue existing incomplete search
+ if (!empty($this->search_set) && $this->search_set->incomplete && $search == $this->search_string) {
+ $searcher->set_results($this->search_set);
+ }
+
+ // execute the search
+ $results = $searcher->exec(
+ $folder,
+ $search,
+ $charset ? $charset : $this->default_charset,
+ $sort_field && $this->get_capability('SORT') ? $sort_field : null,
+ $this->threading
+ );
+ }
+ else if (!$results) {
+ $folder = is_array($folder) ? $folder[0] : $folder;
+ $search = is_array($search) ? $search[$folder] : $search;
+ $results = $this->search_index($folder, $search, $charset, $sort_field);
+ }
+
+ $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false;
- $this->set_search_set(array($str, $results, $charset, $sort_field,
- $this->threading || $this->search_sorted ? true : false));
+ $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted));
+
+ return $results;
}
@@ -1443,20 +1571,27 @@ class rcube_imap extends rcube_storage
*/
public function search_once($folder = null, $str = 'ALL')
{
+ if (!$this->check_connection()) {
+ return new rcube_result_index();
+ }
+
if (!$str) {
$str = 'ALL';
}
- if (!strlen($folder)) {
- $folder = $this->folder;
+ // multi-folder search
+ if (is_array($folder) && count($folder) > 1) {
+ $searcher = new rcube_imap_search($this->options, $this->conn);
+ $index = $searcher->exec($folder, $str, $this->default_charset);
}
-
- if (!$this->check_connection()) {
- return new rcube_result_index();
+ else {
+ $folder = is_array($folder) ? $folder[0] : $folder;
+ if (!strlen($folder)) {
+ $folder = $this->folder;
+ }
+ $index = $this->conn->search($folder, $str, true);
}
- $index = $this->conn->search($folder, $str, true);
-
return $index;
}
@@ -1500,7 +1635,7 @@ class rcube_imap extends rcube_storage
// but I've seen that Courier doesn't support UTF-8)
if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
$threads = $this->conn->thread($folder, $this->threading,
- $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
+ self::convert_criteria($criteria, $charset), true, 'US-ASCII');
}
return $threads;
@@ -1514,7 +1649,7 @@ class rcube_imap extends rcube_storage
// but I've seen Courier with disabled UTF-8 support)
if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
$messages = $this->conn->sort($folder, $sort_field,
- $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
+ self::convert_criteria($criteria, $charset), true, 'US-ASCII');
}
if (!$messages->is_error()) {
@@ -1529,7 +1664,7 @@ class rcube_imap extends rcube_storage
// Error, try with US-ASCII (some servers may support only US-ASCII)
if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
$messages = $this->conn->search($folder,
- $this->convert_criteria($criteria, $charset), true);
+ self::convert_criteria($criteria, $charset), true);
}
$this->search_sorted = false;
@@ -1547,7 +1682,7 @@ class rcube_imap extends rcube_storage
*
* @return string Search string
*/
- protected function convert_criteria($str, $charset, $dest_charset='US-ASCII')
+ public static function convert_criteria($str, $charset, $dest_charset='US-ASCII')
{
// convert strings to US_ASCII
if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
@@ -1556,12 +1691,15 @@ class rcube_imap extends rcube_storage
$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);
- if ($string === false) {
+
+ if ($string === false || !strlen($string)) {
continue;
}
+
$res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
$last = $m[0] + $string_offset - 1;
}
+
if ($last < strlen($str)) {
$res .= substr($str, $last, strlen($str)-$last);
}
@@ -1583,12 +1721,30 @@ class rcube_imap extends rcube_storage
public function refresh_search()
{
if (!empty($this->search_string)) {
- $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
+ $this->search(
+ is_object($this->search_set) ? $this->search_set->get_parameters('MAILBOX') : '',
+ $this->search_string,
+ $this->search_charset,
+ $this->search_sort_field
+ );
}
return $this->get_search_set();
}
+ /**
+ * Flag certain result subsets as 'incomplete'.
+ * For subsequent refresh_search() calls to only refresh the updated parts.
+ */
+ protected function set_search_dirty($folder)
+ {
+ if ($this->search_set && is_a($this->search_set, 'rcube_result_multifolder')) {
+ if ($subset = $this->search_set->get_set($folder)) {
+ $subset->incomplete = $this->search_set->incomplete = true;
+ }
+ }
+ }
+
/**
* Return message headers object of a specific message
@@ -1601,6 +1757,11 @@ class rcube_imap extends rcube_storage
*/
public function get_message_headers($uid, $folder = null, $force = false)
{
+ // decode combined UID-folder identifier
+ if (preg_match('/^\d+-.+/', $uid)) {
+ list($uid, $folder) = explode('-', $uid, 2);
+ }
+
if (!strlen($folder)) {
$folder = $this->folder;
}
@@ -1615,6 +1776,9 @@ class rcube_imap extends rcube_storage
else {
$headers = $this->conn->fetchHeader(
$folder, $uid, true, true, $this->get_fetch_headers());
+
+ if (is_object($headers))
+ $headers->folder = $folder;
}
return $headers;
@@ -1636,6 +1800,11 @@ class rcube_imap extends rcube_storage
$folder = $this->folder;
}
+ // decode combined UID-folder identifier
+ if (preg_match('/^\d+-.+/', $uid)) {
+ list($uid, $folder) = explode('-', $uid, 2);
+ }
+
// Check internal cache
if (!empty($this->icache['message'])) {
if (($headers = $this->icache['message']) && $headers->uid == $uid) {
@@ -1679,7 +1848,7 @@ class rcube_imap extends rcube_storage
$this->struct_charset = $this->structure_charset($structure);
}
- $headers->ctype = strtolower($headers->ctype);
+ $headers->ctype = @strtolower($headers->ctype);
// Here we can recognize malformed BODYSTRUCTURE and
// 1. [@TODO] parse the message in other way to create our own message structure
@@ -2282,6 +2451,8 @@ class rcube_imap extends rcube_storage
$this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
}
}
+
+ $this->set_search_dirty($folder);
}
return $result;
@@ -2329,6 +2500,17 @@ class rcube_imap extends rcube_storage
if ($saved) {
// increase messagecount of the target folder
$this->set_messagecount($folder, 'ALL', 1);
+
+ rcube::get_instance()->plugins->exec_hook('message_saved', array(
+ 'folder' => $folder,
+ 'message' => $message,
+ 'headers' => $headers,
+ 'is_file' => $is_file,
+ 'flags' => $flags,
+ 'date' => $date,
+ 'binary' => $binary,
+ 'result' => $saved,
+ ));
}
return $saved;
@@ -2365,19 +2547,7 @@ class rcube_imap extends rcube_storage
return false;
}
- // make sure folder exists
- if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
- if (in_array($to_mbox, $this->default_folders)) {
- if (!$this->create_folder($to_mbox, true)) {
- return false;
- }
- }
- else {
- return false;
- }
- }
-
- $config = rcube::get_instance()->config;
+ $config = rcube::get_instance()->config;
$to_trash = $to_mbox == $config->get('trash_mbox');
// flag messages as read before moving them
@@ -2392,6 +2562,9 @@ class rcube_imap extends rcube_storage
if ($moved) {
$this->clear_messagecount($from_mbox);
$this->clear_messagecount($to_mbox);
+
+ $this->set_search_dirty($from_mbox);
+ $this->set_search_dirty($to_mbox);
}
// moving failed
else if ($to_trash && $config->get('delete_always', false)) {
@@ -2408,8 +2581,8 @@ class rcube_imap extends rcube_storage
if ($this->search_threads || $all_mode) {
$this->refresh_search();
}
- else {
- $this->search_set->filter(explode(',', $uids));
+ else if (!$this->search_set->incomplete) {
+ $this->search_set->filter(explode(',', $uids), $this->folder);
}
}
@@ -2448,18 +2621,6 @@ class rcube_imap extends rcube_storage
return false;
}
- // make sure folder exists
- if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
- if (in_array($to_mbox, $this->default_folders)) {
- if (!$this->create_folder($to_mbox, true)) {
- return false;
- }
- }
- else {
- return false;
- }
- }
-
// copy messages
$copied = $this->conn->copy($uids, $from_mbox, $to_mbox);
@@ -2508,13 +2669,15 @@ class rcube_imap extends rcube_storage
// unset threads internal cache
unset($this->icache['threads']);
+ $this->set_search_dirty($folder);
+
// remove message ids from search set
if ($this->search_set && $folder == $this->folder) {
// threads are too complicated to just remove messages from set
if ($this->search_threads || $all_mode) {
$this->refresh_search();
}
- else {
+ else if (!$this->search_set->incomplete) {
$this->search_set->filter(explode(',', $uids));
}
}
@@ -2975,16 +3138,17 @@ class rcube_imap extends rcube_storage
*
* @param string $folder New folder name
* @param boolean $subscribe True if the new folder should be subscribed
+ * @param string $type Optional folder type (junk, trash, drafts, sent, archive)
*
* @return boolean True on success
*/
- public function create_folder($folder, $subscribe=false)
+ public function create_folder($folder, $subscribe = false, $type = null)
{
if (!$this->check_connection()) {
return false;
}
- $result = $this->conn->createFolder($folder);
+ $result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null);
// try to subscribe it
if ($result) {
@@ -3109,19 +3273,84 @@ class rcube_imap extends rcube_storage
/**
- * Create all folders specified as default
+ * Detect special folder associations stored in storage backend
*/
- public function create_default_folders()
+ public function get_special_folders($forced = false)
{
- // create default folders if they do not exist
- foreach ($this->default_folders as $folder) {
- if (!$this->folder_exists($folder)) {
- $this->create_folder($folder, true);
+ $result = parent::get_special_folders();
+
+ if (isset($this->icache['special-use'])) {
+ return array_merge($result, $this->icache['special-use']);
+ }
+
+ if (!$forced || !$this->get_capability('SPECIAL-USE')) {
+ return $result;
+ }
+
+ if (!$this->check_connection()) {
+ return $result;
+ }
+
+ $types = array_map(function($value) { return "\\" . ucfirst($value); }, rcube_storage::$folder_types);
+ $special = array();
+
+ // request \Subscribed flag in LIST response as performance improvement for folder_exists()
+ $folders = $this->conn->listMailboxes('', '*', array('SUBSCRIBED'), array('SPECIAL-USE'));
+
+ foreach ($folders as $folder) {
+ if ($flags = $this->conn->data['LIST'][$folder]) {
+ foreach ($types as $type) {
+ if (in_array($type, $flags)) {
+ $type = strtolower(substr($type, 1));
+ $special[$type] = $folder;
+ }
+ }
}
- else if (!$this->folder_exists($folder, true)) {
- $this->subscribe($folder);
+ }
+
+ $this->icache['special-use'] = $special;
+ unset($this->icache['special-folders']);
+
+ return array_merge($result, $special);
+ }
+
+
+ /**
+ * Set special folder associations stored in storage backend
+ */
+ public function set_special_folders($specials)
+ {
+ if (!$this->get_capability('SPECIAL-USE') || !$this->get_capability('METADATA')) {
+ return false;
+ }
+
+ if (!$this->check_connection()) {
+ return false;
+ }
+
+ $folders = $this->get_special_folders(true);
+ $old = (array) $this->icache['special-use'];
+
+ foreach ($specials as $type => $folder) {
+ if (in_array($type, rcube_storage::$folder_types)) {
+ $old_folder = $old[$type];
+ if ($old_folder !== $folder) {
+ // unset old-folder metadata
+ if ($old_folder !== null) {
+ $this->delete_metadata($old_folder, array('/private/specialuse'));
+ }
+ // set new folder metadata
+ if ($folder) {
+ $this->set_metadata($folder, array('/private/specialuse' => "\\" . ucfirst($type)));
+ }
+ }
}
}
+
+ $this->icache['special-use'] = $specials;
+ unset($this->icache['special-folders']);
+
+ return true;
}
@@ -3133,13 +3362,13 @@ class rcube_imap extends rcube_storage
*
* @return boolean TRUE or FALSE
*/
- public function folder_exists($folder, $subscription=false)
+ public function folder_exists($folder, $subscription = false)
{
if ($folder == 'INBOX') {
return true;
}
- $key = $subscription ? 'subscribed' : 'existing';
+ $key = $subscription ? 'subscribed' : 'existing';
if (is_array($this->icache[$key]) && in_array($folder, $this->icache[$key])) {
return true;
@@ -3150,10 +3379,24 @@ class rcube_imap extends rcube_storage
}
if ($subscription) {
- $a_folders = $this->conn->listSubscribed('', $folder);
+ // It's possible we already called LIST command, check LIST data
+ if (!empty($this->conn->data['LIST']) && !empty($this->conn->data['LIST'][$folder])
+ && in_array('\\Subscribed', $this->conn->data['LIST'][$folder])
+ ) {
+ $a_folders = array($folder);
+ }
+ else {
+ $a_folders = $this->conn->listSubscribed('', $folder);
+ }
}
else {
- $a_folders = $this->conn->listMailboxes('', $folder);
+ // It's possible we already called LIST command, check LIST data
+ if (!empty($this->conn->data['LIST']) && isset($this->conn->data['LIST'][$folder])) {
+ $a_folders = array($folder);
+ }
+ else {
+ $a_folders = $this->conn->listMailboxes('', $folder);
+ }
}
if (is_array($a_folders) && in_array($folder, $a_folders)) {
@@ -3364,7 +3607,7 @@ class rcube_imap extends rcube_storage
$options['name'] = $folder;
$options['attributes'] = $this->folder_attributes($folder, true);
$options['namespace'] = $this->folder_namespace($folder);
- $options['special'] = in_array($folder, $this->default_folders);
+ $options['special'] = $this->is_special_folder($folder);
// Set 'noselect' flag
if (is_array($options['attributes'])) {
@@ -3899,58 +4142,72 @@ class rcube_imap extends rcube_storage
*/
public function sort_folder_list($a_folders, $skip_default = false)
{
- $a_out = $a_defaults = $folders = array();
+ $specials = array_merge(array('INBOX'), array_values($this->get_special_folders()));
+ $folders = array();
- $delimiter = $this->get_hierarchy_delimiter();
-
- // find default folders and skip folders starting with '.'
+ // convert names to UTF-8 and skip folders starting with '.'
foreach ($a_folders as $folder) {
- if ($folder[0] == '.') {
- continue;
+ if ($folder[0] != '.') {
+ // for better performance skip encoding conversion
+ // if the string does not look like UTF7-IMAP
+ $folders[$folder] = strpos($folder, '&') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP');
}
+ }
- if (!$skip_default && ($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) {
- $a_defaults[$p] = $folder;
- }
- else {
- $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
- }
+ // sort folders
+ // asort($folders, SORT_LOCALE_STRING) is not properly sorting case sensitive names
+ uasort($folders, array($this, 'sort_folder_comparator'));
+
+ $folders = array_keys($folders);
+
+ if ($skip_default) {
+ return $folders;
}
- // sort folders and place defaults on the top
- asort($folders, SORT_LOCALE_STRING);
- ksort($a_defaults);
- $folders = array_merge($a_defaults, array_keys($folders));
+ // force the type of folder name variable (#1485527)
+ $folders = array_map('strval', $folders);
+ $specials = array_unique(array_intersect($specials, $folders));
+ $head = array();
+
+ // place default folders on top
+ foreach ($specials as $special) {
+ $prefix = $special . $this->delimiter;
- // finally we must rebuild the list to move
- // subfolders of default folders to their place...
- // ...also do this for the rest of folders because
- // asort() is not properly sorting case sensitive names
- while (list($key, $folder) = each($folders)) {
- // set the type of folder name variable (#1485527)
- $a_out[] = (string) $folder;
- unset($folders[$key]);
- $this->rsort($folder, $delimiter, $folders, $a_out);
+ foreach ($folders as $idx => $folder) {
+ if ($folder === $special) {
+ $head[] = $special;
+ unset($folders[$idx]);
+ }
+ // put subfolders of default folders on their place...
+ else if (strpos($folder, $prefix) === 0) {
+ $head[] = $folder;
+ unset($folders[$idx]);
+ }
+ }
}
- return $a_out;
+ return array_merge($head, $folders);
}
/**
- * Recursive method for sorting folders
+ * Callback for uasort() that implements correct
+ * locale-aware case-sensitive sorting
*/
- protected function rsort($folder, $delimiter, &$list, &$out)
+ protected function sort_folder_comparator($str1, $str2)
{
- while (list($key, $name) = each($list)) {
- if (strpos($name, $folder.$delimiter) === 0) {
- // set the type of folder name variable (#1485527)
- $out[] = (string) $name;
- unset($list[$key]);
- $this->rsort($name, $delimiter, $list, $out);
+ $path1 = explode($this->delimiter, $str1);
+ $path2 = explode($this->delimiter, $str2);
+
+ foreach ($path1 as $idx => $folder1) {
+ $folder2 = $path2[$idx];
+
+ if ($folder1 === $folder2) {
+ continue;
}
+
+ return strcoll($folder1, $folder2);
}
- reset($list);
}
diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php
index 0c3edeaad..e49e77803 100644
--- a/program/lib/Roundcube/rcube_imap_cache.php
+++ b/program/lib/Roundcube/rcube_imap_cache.php
@@ -171,7 +171,7 @@ class rcube_imap_cache
// Seek in internal cache
if (array_key_exists('index', $this->icache[$mailbox])) {
// The index was fetched from database already, but not validated yet
- if (!array_key_exists('object', $this->icache[$mailbox]['index'])) {
+ if (empty($this->icache[$mailbox]['index']['validated'])) {
$index = $this->icache[$mailbox]['index'];
}
// We've got a valid index
@@ -248,6 +248,7 @@ class rcube_imap_cache
}
$this->icache[$mailbox]['index'] = array(
+ 'validated' => true,
'object' => $data,
'sort_field' => $sort_field,
'modseq' => !empty($index['modseq']) ? $index['modseq'] : $mbox_data['HIGHESTMODSEQ']
@@ -890,6 +891,8 @@ class rcube_imap_cache
return false;
}
+ $index['validated'] = true;
+
// Get mailbox data (UIDVALIDITY, counters, etc.) for status check
$mbox_data = $this->imap->folder_data($mailbox);
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index f9a62f010..3b5be15db 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -73,6 +73,7 @@ class rcube_imap_generic
const COMMAND_NORESPONSE = 1;
const COMMAND_CAPABILITY = 2;
const COMMAND_LASTLINE = 4;
+ const COMMAND_ANONYMIZED = 8;
const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
@@ -88,16 +89,28 @@ class rcube_imap_generic
*
* @param string $string Command string
* @param bool $endln True if CRLF need to be added at the end of command
+ * @param bool $anonymized Don't write the given data to log but a placeholder
*
* @param int Number of bytes sent, False on error
*/
- function putLine($string, $endln=true)
+ function putLine($string, $endln=true, $anonymized=false)
{
if (!$this->fp)
return false;
if ($this->_debug) {
- $this->debug('C: '. rtrim($string));
+ // anonymize the sent command for logging
+ $cut = $endln ? 2 : 0;
+ if ($anonymized && preg_match('/^(A\d+ (?:[A-Z]+ )+)(.+)/', $string, $m)) {
+ $log = $m[1] . sprintf('****** [%d]', strlen($m[2]) - $cut);
+ }
+ else if ($anonymized) {
+ $log = sprintf('****** [%d]', strlen($string) - $cut);
+ }
+ else {
+ $log = rtrim($string);
+ }
+ $this->debug('C: ' . $log);
}
$res = fwrite($this->fp, $string . ($endln ? "\r\n" : ''));
@@ -116,10 +129,11 @@ class rcube_imap_generic
*
* @param string $string Command string
* @param bool $endln True if CRLF need to be added at the end of command
+ * @param bool $anonymized Don't write the given data to log but a placeholder
*
* @return int|bool Number of bytes sent, False on error
*/
- function putLineC($string, $endln=true)
+ function putLineC($string, $endln=true, $anonymized=false)
{
if (!$this->fp) {
return false;
@@ -138,7 +152,7 @@ class rcube_imap_generic
$parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]);
}
- $bytes = $this->putLine($parts[$i].$parts[$i+1], false);
+ $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized);
if ($bytes === false)
return false;
$res += $bytes;
@@ -153,7 +167,7 @@ class rcube_imap_generic
$i++;
}
else {
- $bytes = $this->putLine($parts[$i], false);
+ $bytes = $this->putLine($parts[$i], false, $anonymized);
if ($bytes === false)
return false;
$res += $bytes;
@@ -519,7 +533,7 @@ class rcube_imap_generic
$reply = base64_encode($user . ' ' . $hash);
// send result
- $this->putLine($reply);
+ $this->putLine($reply, true, true);
}
else {
// RFC2831: DIGEST-MD5
@@ -537,7 +551,7 @@ class rcube_imap_generic
base64_decode($challenge), $this->host, 'imap', $user));
// send result
- $this->putLine($reply);
+ $this->putLine($reply, true, true);
$line = trim($this->readReply());
if ($line[0] == '+') {
@@ -577,7 +591,7 @@ class rcube_imap_generic
// RFC 4959 (SASL-IR): save one round trip
if ($this->getCapability('SASL-IR')) {
list($result, $line) = $this->execute("AUTHENTICATE PLAIN", array($reply),
- self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY);
+ self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
}
else {
$this->putLine($this->nextTag() . " AUTHENTICATE PLAIN");
@@ -588,7 +602,7 @@ class rcube_imap_generic
}
// send result, get reply and process it
- $this->putLine($reply);
+ $this->putLine($reply, true, true);
$line = $this->readReply();
$result = $this->parseResult($line);
}
@@ -619,7 +633,7 @@ class rcube_imap_generic
function login($user, $password)
{
list($code, $response) = $this->execute('LOGIN', array(
- $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY);
+ $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
// re-set capabilities list if untagged CAPABILITY response provided
if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) {
@@ -1177,13 +1191,20 @@ class rcube_imap_generic
* Folder creation (CREATE)
*
* @param string $mailbox Mailbox name
+ * @param array $types Optional folder types (RFC 6154)
*
* @return bool True on success, False on error
*/
- function createFolder($mailbox)
+ function createFolder($mailbox, $types = null)
{
- $result = $this->execute('CREATE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
+ $args = array($this->escape($mailbox));
+
+ // RFC 6154: CREATE-SPECIAL-USE
+ if (!empty($types) && $this->getCapability('CREATE-SPECIAL-USE')) {
+ $args[] = '(USE (' . implode(' ', $types) . '))';
+ }
+
+ $result = $this->execute('CREATE', $args, self::COMMAND_NORESPONSE);
return ($result == self::ERROR_OK);
}
@@ -1279,10 +1300,12 @@ class rcube_imap_generic
* @param string $ref Reference name
* @param string $mailbox Mailbox name
* @param bool $subscribed Enables returning subscribed mailboxes only
- * @param array $status_opts List of STATUS options (RFC5819: LIST-STATUS)
- * Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN
+ * @param array $status_opts List of STATUS options
+ * (RFC5819: LIST-STATUS: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN)
+ * or RETURN options (RFC5258: LIST_EXTENDED: SUBSCRIBED, CHILDREN)
* @param array $select_opts List of selection options (RFC5258: LIST-EXTENDED)
- * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE
+ * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE,
+ * SPECIAL-USE (RFC6154)
*
* @return array List of mailboxes or hash of options if $status_ops argument
* is non-empty.
@@ -1295,6 +1318,7 @@ class rcube_imap_generic
}
$args = array();
+ $rets = array();
if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) {
$select_opts = (array) $select_opts;
@@ -1305,11 +1329,21 @@ class rcube_imap_generic
$args[] = $this->escape($ref);
$args[] = $this->escape($mailbox);
+ if (!empty($status_opts) && $this->getCapability('LIST-EXTENDED')) {
+ $rets = array_intersect($status_opts, array('SUBSCRIBED', 'CHILDREN'));
+ }
+
if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) {
- $status_opts = (array) $status_opts;
- $lstatus = true;
+ $status_opts = array_intersect($status_opts, array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'));
+
+ if (!empty($status_opts)) {
+ $lstatus = true;
+ $rets[] = 'STATUS (' . implode(' ', $status_opts) . ')';
+ }
+ }
- $args[] = 'RETURN (STATUS (' . implode(' ', $status_opts) . '))';
+ if (!empty($rets)) {
+ $args[] = 'RETURN (' . implode(' ', $rets) . ')';
}
list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args);
@@ -1555,23 +1589,23 @@ class rcube_imap_generic
*
* @param string $mailbox Mailbox name
* @param string $field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO)
- * @param string $add Searching criteria
+ * @param string $criteria Searching criteria
* @param bool $return_uid Enables UID SORT usage
* @param string $encoding Character set
*
* @return rcube_result_index Response data
*/
- function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII')
+ function sort($mailbox, $field = 'ARRIVAL', $criteria = '', $return_uid = false, $encoding = 'US-ASCII')
{
- $field = strtoupper($field);
+ $old_sel = $this->selected;
+ $supported = array('ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO');
+ $field = strtoupper($field);
+
if ($field == 'INTERNALDATE') {
$field = 'ARRIVAL';
}
- $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
- 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
-
- if (!$fields[$field]) {
+ if (!in_array($field, $supported)) {
return new rcube_result_index($mailbox);
}
@@ -1579,18 +1613,21 @@ class rcube_imap_generic
return new rcube_result_index($mailbox);
}
+ // return empty result when folder is empty and we're just after SELECT
+ if ($old_sel != $mailbox && !$this->data['EXISTS']) {
+ return new rcube_result_index($mailbox, '* SORT');
+ }
+
// RFC 5957: SORT=DISPLAY
if (($field == 'FROM' || $field == 'TO') && $this->getCapability('SORT=DISPLAY')) {
$field = 'DISPLAY' . $field;
}
- // message IDs
- if (!empty($add)) {
- $add = $this->compressMessageSet($add);
- }
+ $encoding = $encoding ? trim($encoding) : 'US-ASCII';
+ $criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL';
list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT',
- array("($field)", $encoding, !empty($add) ? $add : 'ALL'));
+ array("($field)", $encoding, $criteria));
if ($code != self::ERROR_OK) {
$response = null;
@@ -1620,7 +1657,7 @@ class rcube_imap_generic
// return empty result when folder is empty and we're just after SELECT
if ($old_sel != $mailbox && !$this->data['EXISTS']) {
- return new rcube_result_thread($mailbox);
+ return new rcube_result_thread($mailbox, '* THREAD');
}
$encoding = $encoding ? trim($encoding) : 'US-ASCII';
@@ -3106,7 +3143,8 @@ class rcube_imap_generic
if (isset($mbox) && is_array($data[$i])) {
$size_sub = count($data[$i]);
for ($x=0; $x<$size_sub; $x++) {
- $result[$mbox][$data[$i][$x]] = $data[$i][++$x];
+ if ($data[$i][$x+1] !== null)
+ $result[$mbox][$data[$i][$x]] = $data[$i][++$x];
}
unset($data[$i]);
}
@@ -3124,7 +3162,8 @@ class rcube_imap_generic
}
}
else if (isset($mbox)) {
- $result[$mbox][$data[$i]] = $data[++$i];
+ if ($data[$i+1] !== null)
+ $result[$mbox][$data[$i]] = $data[++$i];
unset($data[$i]);
unset($data[$i-1]);
}
@@ -3269,10 +3308,10 @@ class rcube_imap_generic
for ($x=0, $len=count($attribs); $x<$len;) {
$attr = $attribs[$x++];
$value = $attribs[$x++];
- if ($attr == 'value.priv') {
+ if ($attr == 'value.priv' && $value !== null) {
$result[$mbox]['/private' . $entry] = $value;
}
- else if ($attr == 'value.shared') {
+ else if ($attr == 'value.shared' && $value !== null) {
$result[$mbox]['/shared' . $entry] = $value;
}
}
@@ -3419,7 +3458,7 @@ class rcube_imap_generic
}
// Send command
- if (!$this->putLineC($query)) {
+ if (!$this->putLineC($query, true, ($options & self::COMMAND_ANONYMIZED))) {
$this->setError(self::ERROR_COMMAND, "Unable to send command: $query");
return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, '');
}
diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php
new file mode 100644
index 000000000..365d78f76
--- /dev/null
+++ b/program/lib/Roundcube/rcube_imap_search.php
@@ -0,0 +1,231 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | |
+ | Copyright (C) 2013, The Roundcube Dev Team |
+ | Copyright (C) 2014, 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: |
+ | Execute (multi-threaded) searches in multiple IMAP folders |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Class to control search jobs on multiple IMAP folders.
+ *
+ * @package Framework
+ * @subpackage Storage
+ * @author Thomas Bruederli <roundcube@gmail.com>
+ */
+class rcube_imap_search
+{
+ public $options = array();
+
+ protected $jobs = array();
+ protected $timelimit = 0;
+ protected $results;
+ protected $conn;
+
+ /**
+ * Default constructor
+ */
+ public function __construct($options, $conn)
+ {
+ $this->options = $options;
+ $this->conn = $conn;
+ }
+
+ /**
+ * Invoke search request to IMAP server
+ *
+ * @param array $folders List of IMAP folders to search in
+ * @param string $str Search criteria
+ * @param string $charset Search charset
+ * @param string $sort_field Header field to sort by
+ * @param boolean $threading True if threaded listing is active
+ */
+ public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null)
+ {
+ $start = floor(microtime(true));
+ $results = new rcube_result_multifolder($folders);
+
+ // start a search job for every folder to search in
+ foreach ($folders as $folder) {
+ // a complete result for this folder already exists
+ $result = $this->results ? $this->results->get_set($folder) : false;
+ if ($result && !$result->incomplete) {
+ $results->add($result);
+ }
+ else {
+ $search = is_array($str) && $str[$folder] ? $str[$folder] : $str;
+ $job = new rcube_imap_search_job($folder, $search, $charset, $sort_field, $threading);
+ $job->worker = $this;
+ $this->jobs[] = $job;
+ }
+ }
+
+ // execute jobs and gather results
+ foreach ($this->jobs as $job) {
+ // only run search if within the configured time limit
+ // TODO: try to estimate the required time based on folder size and previous search performance
+ if (!$this->timelimit || floor(microtime(true)) - $start < $this->timelimit) {
+ $job->run();
+ }
+
+ // add result (may have ->incomplete flag set)
+ $results->add($job->get_result());
+ }
+
+ return $results;
+ }
+
+ /**
+ * Setter for timelimt property
+ */
+ public function set_timelimit($seconds)
+ {
+ $this->timelimit = $seconds;
+ }
+
+ /**
+ * Setter for previous (potentially incomplete) search results
+ */
+ public function set_results($res)
+ {
+ $this->results = $res;
+ }
+
+ /**
+ * Get connection to the IMAP server
+ * (used for single-thread mode)
+ */
+ public function get_imap()
+ {
+ return $this->conn;
+ }
+}
+
+
+/**
+ * Stackable item to run the search on a specific IMAP folder
+ */
+class rcube_imap_search_job /* extends Stackable */
+{
+ private $folder;
+ private $search;
+ private $charset;
+ private $sort_field;
+ private $threading;
+ private $searchset;
+ private $result;
+ private $pagesize = 100;
+
+ public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
+ {
+ $this->folder = $folder;
+ $this->search = $str;
+ $this->charset = $charset;
+ $this->sort_field = $sort_field;
+ $this->threading = $threading;
+
+ $this->result = new rcube_result_index($folder);
+ $this->result->incomplete = true;
+ }
+
+ public function run()
+ {
+ $this->result = $this->search_index();
+ }
+
+ /**
+ * Copy of rcube_imap::search_index()
+ */
+ protected function search_index()
+ {
+ $criteria = $this->search;
+ $charset = $this->charset;
+ $imap = $this->worker->get_imap();
+
+ if (!$imap->connected()) {
+ trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);
+
+ if ($this->threading) {
+ return new rcube_result_thread($this->folder);
+ }
+ else {
+ return new rcube_result_index($this->folder);
+ }
+ }
+
+ if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) {
+ $criteria = 'UNDELETED '.$criteria;
+ }
+
+ // unset CHARSET if criteria string is ASCII, this way
+ // SEARCH won't be re-sent after "unsupported charset" response
+ if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
+ $charset = 'US-ASCII';
+ }
+
+ if ($this->threading) {
+ $threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset);
+
+ // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
+ // but I've seen that Courier doesn't support UTF-8)
+ if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
+ $threads = $imap->thread($this->folder, $this->threading,
+ rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
+ }
+
+ return $threads;
+ }
+
+ if ($this->sort_field) {
+ $messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset);
+
+ // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
+ // but I've seen Courier with disabled UTF-8 support)
+ if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
+ $messages = $imap->sort($this->folder, $this->sort_field,
+ rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
+ }
+ }
+
+ if (!$messages || $messages->is_error()) {
+ $messages = $imap->search($this->folder,
+ ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);
+
+ // Error, try with US-ASCII (some servers may support only US-ASCII)
+ if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
+ $messages = $imap->search($this->folder,
+ rcube_imap::convert_criteria($criteria, $charset), true);
+ }
+ }
+
+ return $messages;
+ }
+
+ public function get_search_set()
+ {
+ return array(
+ $this->search,
+ $this->result,
+ $this->charset,
+ $this->sort_field,
+ $this->threading,
+ );
+ }
+
+ public function get_result()
+ {
+ return $this->result;
+ }
+}
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index de3790e5c..5a4b9dd61 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -377,10 +377,11 @@ class rcube_ldap extends rcube_addressbook
// replace placeholders in filter settings
if (!empty($this->prop['filter']))
$this->prop['filter'] = strtr($this->prop['filter'], $replaces);
- if (!empty($this->prop['groups']['filter']))
- $this->prop['groups']['filter'] = strtr($this->prop['groups']['filter'], $replaces);
- if (!empty($this->prop['groups']['member_filter']))
- $this->prop['groups']['member_filter'] = strtr($this->prop['groups']['member_filter'], $replaces);
+
+ foreach (array('base_dn','filter','member_filter') as $k) {
+ if (!empty($this->prop['groups'][$k]))
+ $this->prop['groups'][$k] = strtr($this->prop['groups'][$k], $replaces);
+ }
if (!empty($this->prop['group_filters'])) {
foreach ($this->prop['group_filters'] as $i => $gf) {
@@ -1409,6 +1410,16 @@ class rcube_ldap extends rcube_addressbook
$fieldmap['name'] = $this->group_data['name_attr'] ? $this->group_data['name_attr'] : $this->prop['groups']['name_attr'];
}
+ // assign object type from object class mapping
+ if (!empty($this->prop['class_type_map'])) {
+ foreach (array_map('strtolower', (array)$rec['objectclass']) as $objcls) {
+ if (!empty($this->prop['class_type_map'][$objcls])) {
+ $out['_type'] = $this->prop['class_type_map'][$objcls];
+ break;
+ }
+ }
+ }
+
foreach ($fieldmap as $rf => $lf)
{
for ($i=0; $i < $rec[$lf]['count']; $i++) {
diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php
index 923a12a41..252eafabe 100644
--- a/program/lib/Roundcube/rcube_ldap_generic.php
+++ b/program/lib/Roundcube/rcube_ldap_generic.php
@@ -160,7 +160,7 @@ class rcube_ldap_generic
$this->config['hosts'] = array($this->config['hosts']);
foreach ($this->config['hosts'] as $host) {
- if ($this->connect($host)) {
+ if (!empty($host) && $this->connect($host)) {
return true;
}
}
@@ -190,6 +190,9 @@ class rcube_ldap_generic
if (isset($this->config['referrals']))
ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->config['referrals']);
+
+ if (isset($this->config['dereference']))
+ ldap_set_option($lc, LDAP_OPT_DEREF, $this->config['dereference']);
}
else {
$this->_debug("S: NOT OK");
@@ -240,7 +243,7 @@ class rcube_ldap_generic
$method = 'DIGEST-MD5';
}
- $this->_debug("C: SASL Bind [mech: $method, authc: $authc, authz: $authz, pass: $pass]");
+ $this->_debug("C: SASL Bind [mech: $method, authc: $authc, authz: $authz, pass: **** [" . strlen($pass) . "]");
if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) {
$this->_debug("S: OK");
@@ -271,7 +274,7 @@ class rcube_ldap_generic
return false;
}
- $this->_debug("C: Bind $dn [pass: $pass]");
+ $this->_debug("C: Bind $dn, pass: **** [" . strlen($pass) . "]");
if (@ldap_bind($this->conn, $dn, $pass)) {
$this->_debug("S: OK");
@@ -886,9 +889,10 @@ class rcube_ldap_generic
}
$this->vlv_config = array();
+ $config_root_dn = $this->config['config_root_dn'];
- $ldap_result = ldap_search($this->conn, $this->config['config_root_dn'], '(objectclass=vlvsearch)', array('*'), 0, 0, 0);
- $vlv_searches = new rcube_ldap_result($this->conn, $ldap_result, $this->config['config_root_dn'], '(objectclass=vlvsearch)');
+ $ldap_result = ldap_search($this->conn, $config_root_dn, '(objectclass=vlvsearch)', array('*'), 0, 0, 0);
+ $vlv_searches = new rcube_ldap_result($this->conn, $ldap_result, $config_root_dn, '(objectclass=vlvsearch)');
if ($vlv_searches->count() < 1) {
$this->_debug("D: Empty result from search for '(objectclass=vlvsearch)' on '$config_root_dn'");
diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php
index f24ec3ed8..a648ae722 100644
--- a/program/lib/Roundcube/rcube_message.php
+++ b/program/lib/Roundcube/rcube_message.php
@@ -74,6 +74,11 @@ class rcube_message
*/
function __construct($uid, $folder = null)
{
+ // decode combined UID-folder identifier
+ if (preg_match('/^\d+-.+/', $uid)) {
+ list($uid, $folder) = explode('-', $uid, 2);
+ }
+
$this->uid = $uid;
$this->app = rcube::get_instance();
$this->storage = $this->app->get_storage();
diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php
index 2c5e2b6c8..2bda930eb 100644
--- a/program/lib/Roundcube/rcube_message_header.php
+++ b/program/lib/Roundcube/rcube_message_header.php
@@ -167,6 +167,13 @@ class rcube_message_header
public $mdn_to;
/**
+ * IMAP folder this message is stored in
+ *
+ * @var string
+ */
+ public $folder;
+
+ /**
* Other message headers
*
* @var array
@@ -189,6 +196,8 @@ class rcube_message_header
'reply-to' => 'replyto',
'cc' => 'cc',
'bcc' => 'bcc',
+ 'mbox' => 'folder',
+ 'folder' => 'folder',
'content-transfer-encoding' => 'encoding',
'in-reply-to' => 'in_reply_to',
'content-type' => 'ctype',
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index a931c27c1..370d5a8d5 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -366,6 +366,9 @@ class rcube_mime
$address = 'MAILER-DAEMON';
$name = substr($val, 0, -strlen($m[1]));
}
+ else if (preg_match('/('.$email_rx.')/', $val, $m)) {
+ $name = $m[1];
+ }
else {
$name = $val;
}
@@ -378,11 +381,16 @@ class rcube_mime
}
if ($decode) {
$name = self::decode_header($name, $fallback);
+ // some clients encode addressee name with quotes around it
+ if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
+ $name = substr($name, 1, -1);
+ }
}
}
if (!$address && $name) {
$address = $name;
+ $name = '';
}
if ($address) {
@@ -472,15 +480,17 @@ class rcube_mime
/**
* Interpret a format=flowed message body according to RFC 2646
*
- * @param string $text Raw body formatted as flowed text
+ * @param string $text Raw body formatted as flowed text
+ * @param string $mark Mark each flowed line with specified character
*
* @return string Interpreted text with unwrapped lines and stuffed space removed
*/
- public static function unfold_flowed($text)
+ public static function unfold_flowed($text, $mark = null)
{
$text = preg_split('/\r?\n/', $text);
$last = -1;
$q_level = 0;
+ $marks = array();
foreach ($text as $idx => $line) {
if (preg_match('/^(>+)/', $line, $m)) {
@@ -500,6 +510,10 @@ class rcube_mime
) {
$text[$last] .= $line;
unset($text[$idx]);
+
+ if ($mark) {
+ $marks[$last] = true;
+ }
}
else {
$last = $idx;
@@ -512,7 +526,7 @@ class rcube_mime
}
else {
// remove space-stuffing
- $line = preg_replace('/^\s/', '', $line);
+ $line = preg_replace('/^ /', '', $line);
if (isset($text[$last]) && $line
&& $text[$last] != '-- '
@@ -520,6 +534,10 @@ class rcube_mime
) {
$text[$last] .= $line;
unset($text[$idx]);
+
+ if ($mark) {
+ $marks[$last] = true;
+ }
}
else {
$text[$idx] = $line;
@@ -530,6 +548,12 @@ class rcube_mime
$q_level = $q;
}
+ if (!empty($marks)) {
+ foreach (array_keys($marks) as $mk) {
+ $text[$mk] = $mark . $text[$mk];
+ }
+ }
+
return implode("\r\n", $text);
}
diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php
index 7ccf9a02e..1907645b0 100644
--- a/program/lib/Roundcube/rcube_output.php
+++ b/program/lib/Roundcube/rcube_output.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube PHP suite |
- | Copyright (C) 2005-2012 The Roundcube Dev Team |
+ | Copyright (C) 2005-2014 The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -31,6 +31,7 @@ abstract class rcube_output
protected $config;
protected $charset = RCUBE_CHARSET;
protected $env = array();
+ protected $skins = array();
/**
@@ -49,9 +50,12 @@ abstract class rcube_output
*/
public function __get($var)
{
- // allow read-only access to $env
- if ($var == 'env')
- return $this->env;
+ // allow read-only access to some members
+ switch ($var) {
+ case 'env': return $this->env;
+ case 'skins': return $this->skins;
+ case 'charset': return $this->charset;
+ }
return null;
}
diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php
index f0af95332..01c340deb 100644
--- a/program/lib/Roundcube/rcube_plugin.php
+++ b/program/lib/Roundcube/rcube_plugin.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2012, The Roundcube Dev Team |
+ | Copyright (C) 2008-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -394,7 +394,11 @@ abstract class rcube_plugin
public function local_skin_path()
{
$rcube = rcube::get_instance();
- foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
+ $skins = array_keys((array)$rcube->output->skins);
+ if (empty($skins)) {
+ $skins = array($rcube->config->get('skin'));
+ }
+ foreach ($skins as $skin) {
$skin_path = 'skins/' . $skin;
if (is_dir(realpath(slashify($this->home) . $skin_path))) {
break;
diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php
index 461c3cc07..feeeb192e 100644
--- a/program/lib/Roundcube/rcube_plugin_api.php
+++ b/program/lib/Roundcube/rcube_plugin_api.php
@@ -182,7 +182,7 @@ class rcube_plugin_api
}
// plugin already loaded
- if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) {
+ if ($this->plugins[$plugin_name]) {
return true;
}
@@ -190,7 +190,9 @@ class rcube_plugin_api
. DIRECTORY_SEPARATOR . $plugin_name . '.php';
if (file_exists($fn)) {
- include $fn;
+ if (!class_exists($plugin_name, false)) {
+ include $fn;
+ }
// instantiate class if exists
if (class_exists($plugin_name, false)) {
diff --git a/program/lib/Roundcube/rcube_result_index.php b/program/lib/Roundcube/rcube_result_index.php
index 5f592c54f..ffc1ad78a 100644
--- a/program/lib/Roundcube/rcube_result_index.php
+++ b/program/lib/Roundcube/rcube_result_index.php
@@ -26,6 +26,8 @@
*/
class rcube_result_index
{
+ public $incomplete = false;
+
protected $raw_data;
protected $mailbox;
protected $meta = array();
@@ -231,29 +233,13 @@ class rcube_result_index
/**
- * Filters data set. Removes elements listed in $ids list.
+ * Filters data set. Removes elements not listed in $ids list.
*
* @param array $ids List of IDs to remove.
*/
public function filter($ids = array())
{
$data = $this->get();
- $data = array_diff($data, $ids);
-
- $this->meta = array();
- $this->meta['count'] = count($data);
- $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
- }
-
-
- /**
- * Filters data set. Removes elements not listed in $ids list.
- *
- * @param array $ids List of IDs to keep.
- */
- public function intersect($ids = array())
- {
- $data = $this->get();
$data = array_intersect($data, $ids);
$this->meta = array();
@@ -332,6 +318,7 @@ class rcube_result_index
if (empty($this->raw_data)) {
return array();
}
+
return explode(self::SEPARATOR_ELEMENT, $this->raw_data);
}
diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php
new file mode 100644
index 000000000..786ee85f6
--- /dev/null
+++ b/program/lib/Roundcube/rcube_result_multifolder.php
@@ -0,0 +1,337 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2011, The Roundcube Dev Team |
+ | Copyright (C) 2011, 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: |
+ | SORT/SEARCH/ESEARCH response handler |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Class holding a set of rcube_result_index instances that together form a
+ * result set of a multi-folder search
+ *
+ * @package Framework
+ * @subpackage Storage
+ */
+class rcube_result_multifolder
+{
+ public $multi = true;
+ public $sets = array();
+ public $incomplete = false;
+ public $folder;
+
+ protected $meta = array();
+ protected $index = array();
+ protected $folders = array();
+ protected $order = 'ASC';
+ protected $sorting;
+
+
+ /**
+ * Object constructor.
+ */
+ public function __construct($folders = array())
+ {
+ $this->folders = $folders;
+ $this->meta = array('count' => 0);
+ }
+
+
+ /**
+ * Initializes object with SORT command response
+ *
+ * @param string $data IMAP response string
+ */
+ public function add($result)
+ {
+ $this->sets[] = $result;
+
+ if ($result->count()) {
+ $this->append_result($result);
+ }
+ else if ($result->incomplete) {
+ $this->incomplete = true;
+ }
+ }
+
+ /**
+ * Append message UIDs from the given result to our index
+ */
+ protected function append_result($result)
+ {
+ $this->meta['count'] += $result->count();
+
+ // append UIDs to global index
+ $folder = $result->get_parameters('MAILBOX');
+ $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get());
+
+ $this->index = array_merge($this->index, $index);
+ }
+
+ /**
+ * Store a global index of (sorted) message UIDs
+ */
+ public function set_message_index($headers, $sort_field, $sort_order)
+ {
+ $this->index = array();
+ foreach ($headers as $header) {
+ $this->index[] = $header->uid . '-' . $header->folder;
+ }
+
+ $this->sorting = $sort_field;
+ $this->order = $sort_order;
+ }
+
+ /**
+ * Checks the result from IMAP command
+ *
+ * @return bool True if the result is an error, False otherwise
+ */
+ public function is_error()
+ {
+ return false;
+ }
+
+
+ /**
+ * Checks if the result is empty
+ *
+ * @return bool True if the result is empty, False otherwise
+ */
+ public function is_empty()
+ {
+ return empty($this->sets) || $this->meta['count'] == 0;
+ }
+
+
+ /**
+ * Returns number of elements in the result
+ *
+ * @return int Number of elements
+ */
+ public function count()
+ {
+ return $this->meta['count'];
+ }
+
+
+ /**
+ * Returns number of elements in the result.
+ * Alias for count() for compatibility with rcube_result_thread
+ *
+ * @return int Number of elements
+ */
+ public function count_messages()
+ {
+ return $this->count();
+ }
+
+
+ /**
+ * Reverts order of elements in the result
+ */
+ public function revert()
+ {
+ $this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
+ $this->index = array();
+
+ // revert order in all sub-sets
+ foreach ($this->sets as $set) {
+ if ($this->order != $set->get_parameters('ORDER')) {
+ $set->revert();
+ }
+
+ $folder = $set->get_parameters('MAILBOX');
+ $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $set->get());
+
+ $this->index = array_merge($this->index, $index);
+ }
+ }
+
+
+ /**
+ * Check if the given message ID exists in the object
+ *
+ * @param int $msgid Message ID
+ * @param bool $get_index When enabled element's index will be returned.
+ * Elements are indexed starting with 0
+ * @return mixed False if message ID doesn't exist, True if exists or
+ * index of the element if $get_index=true
+ */
+ public function exists($msgid, $get_index = false)
+ {
+ if (!empty($this->folder)) {
+ $msgid .= '-' . $this->folder;
+ }
+
+ return array_search($msgid, $this->index);
+ }
+
+
+ /**
+ * Filters data set. Removes elements listed in $ids list.
+ *
+ * @param array $ids List of IDs to remove.
+ * @param string $folder IMAP folder
+ */
+ public function filter($ids = array(), $folder = null)
+ {
+ $this->meta['count'] = 0;
+ foreach ($this->sets as $set) {
+ if ($set->get_parameters('MAILBOX') == $folder) {
+ $set->filter($ids);
+ }
+
+ $this->meta['count'] += $set->count();
+ }
+ }
+
+ /**
+ * Slices data set.
+ *
+ * @param $offset Offset (as for PHP's array_slice())
+ * @param $length Number of elements (as for PHP's array_slice())
+ *
+ */
+ public function slice($offset, $length)
+ {
+ $data = array_slice($this->get(), $offset, $length);
+
+ $this->index = $data;
+ $this->meta['count'] = count($data);
+ }
+
+ /**
+ * Filters data set. Removes elements not listed in $ids list.
+ *
+ * @param array $ids List of IDs to keep.
+ */
+ public function intersect($ids = array())
+ {
+ // not implemented
+ }
+
+ /**
+ * Return all messages in the result.
+ *
+ * @return array List of message IDs
+ */
+ public function get()
+ {
+ return $this->index;
+ }
+
+
+ /**
+ * Return all messages in the result.
+ *
+ * @return array List of message IDs
+ */
+ public function get_compressed()
+ {
+ return '';
+ }
+
+
+ /**
+ * Return result element at specified index
+ *
+ * @param int|string $index Element's index or "FIRST" or "LAST"
+ *
+ * @return int Element value
+ */
+ public function get_element($idx)
+ {
+ switch ($idx) {
+ case 'FIRST': return $this->index[0];
+ case 'LAST': return end($this->index);
+ default: return $this->index[$idx];
+ }
+ }
+
+
+ /**
+ * Returns response parameters, e.g. ESEARCH's MIN/MAX/COUNT/ALL/MODSEQ
+ * or internal data e.g. MAILBOX, ORDER
+ *
+ * @param string $param Parameter name
+ *
+ * @return array|string Response parameters or parameter value
+ */
+ public function get_parameters($param=null)
+ {
+ $params = array(
+ 'SORT' => $this->sorting,
+ 'ORDER' => $this->order,
+ 'MAILBOX' => $this->folders,
+ );
+
+ if ($param !== null) {
+ return $params[$param];
+ }
+
+ return $params;
+ }
+
+ /**
+ * Returns the stored result object for a particular folder
+ *
+ * @param string $folder Folder name
+ * @return false|obejct rcube_result_* instance of false if none found
+ */
+ public function get_set($folder)
+ {
+ foreach ($this->sets as $set) {
+ if ($set->get_parameters('MAILBOX') == $folder) {
+ return $set;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns length of internal data representation
+ *
+ * @return int Data length
+ */
+ protected function length()
+ {
+ return $this->count();
+ }
+
+
+ /* Serialize magic methods */
+
+ public function __sleep()
+ {
+ return array('sets','folders','sorting','order');
+ }
+
+ public function __wakeup()
+ {
+ // restore index from saved result sets
+ $this->meta = array('count' => 0);
+
+ foreach ($this->sets as $result) {
+ if ($result->count()) {
+ $this->append_result($result);
+ }
+ else if ($result->incomplete) {
+ $this->incomplete = true;
+ }
+ }
+ }
+
+}
diff --git a/program/lib/Roundcube/rcube_result_set.php b/program/lib/Roundcube/rcube_result_set.php
index a4b070e28..82502ce5f 100644
--- a/program/lib/Roundcube/rcube_result_set.php
+++ b/program/lib/Roundcube/rcube_result_set.php
@@ -25,7 +25,7 @@
* @package Framework
* @subpackage Addressbook
*/
-class rcube_result_set implements Iterator
+class rcube_result_set implements Iterator, ArrayAccess
{
public $count = 0;
public $first = 0;
@@ -61,6 +61,34 @@ class rcube_result_set implements Iterator
$this->current = $i;
}
+ /*** Implement PHP ArrayAccess interface ***/
+
+ public function offsetSet($offset, $value)
+ {
+ if (is_null($offset)) {
+ $offset = count($this->records);
+ $this->records[] = $value;
+ }
+ else {
+ $this->records[$offset] = $value;
+ }
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->records[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->records[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->records[$offset];
+ }
+
/*** PHP 5 Iterator interface ***/
function rewind()
diff --git a/program/lib/Roundcube/rcube_result_thread.php b/program/lib/Roundcube/rcube_result_thread.php
index c7f21db53..168761696 100644
--- a/program/lib/Roundcube/rcube_result_thread.php
+++ b/program/lib/Roundcube/rcube_result_thread.php
@@ -26,6 +26,8 @@
*/
class rcube_result_thread
{
+ public $incomplete = false;
+
protected $raw_data;
protected $mailbox;
protected $meta = array();
@@ -453,7 +455,7 @@ class rcube_result_thread
// when sorting search result it's good to make the index smaller
if ($index->count() != $this->count_messages()) {
- $index->intersect($this->get());
+ $index->filter($this->get());
}
$result = array_fill_keys($index->get(), null);
diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php
index caca262c6..26f78433a 100644
--- a/program/lib/Roundcube/rcube_session.php
+++ b/program/lib/Roundcube/rcube_session.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | Copyright (C) 2005-2014, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
@@ -47,6 +47,13 @@ class rcube_session
private $storage;
private $memcache;
+ /**
+ * Blocks session data from being written to database.
+ * Can be used if write-race conditions are to be expected
+ * @var boolean
+ */
+ public $nowrite = false;
+
/**
* Default constructor
@@ -201,6 +208,9 @@ class rcube_session
$table = $this->db->table_name('session');
$ts = microtime(true);
+ if ($this->nowrite)
+ return true;
+
// no session row in DB (db_read() returns false)
if (!$this->key) {
$oldvars = null;
diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php
index 60b1389ea..70f15dc7b 100644
--- a/program/lib/Roundcube/rcube_smtp.php
+++ b/program/lib/Roundcube/rcube_smtp.php
@@ -29,6 +29,7 @@ class rcube_smtp
private $conn = null;
private $response;
private $error;
+ private $anonymize_log = 0;
// define headers delimiter
const SMTP_MIME_CRLF = "\r\n";
@@ -67,6 +68,7 @@ class rcube_smtp
'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
'smtp_timeout' => $rcube->config->get('smtp_timeout'),
+ 'smtp_conn_options' => $rcube->config->get('smtp_conn_options'),
'smtp_auth_callbacks' => array(),
));
@@ -106,10 +108,11 @@ class rcube_smtp
// IDNA Support
$smtp_host = rcube_utils::idn_to_ascii($smtp_host);
- $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
+ $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host, false, 0, $CONFIG['smtp_conn_options']);
if ($rcube->config->get('smtp_debug')) {
$this->conn->setDebug(true, array($this, 'debug_handler'));
+ $this->anonymize_log = 0;
}
// register authentication methods
@@ -329,6 +332,15 @@ class rcube_smtp
*/
public function debug_handler(&$smtp, $message)
{
+ // catch AUTH commands and set anonymization flag for subsequent sends
+ if (preg_match('/^Send: AUTH ([A-Z]+)/', $message, $m)) {
+ $this->anonymize_log = $m[1] == 'LOGIN' ? 2 : 1;
+ }
+ // anonymize this log entry
+ else if ($this->anonymize_log > 0 && strpos($message, 'Send:') === 0 && --$this->anonymize_log == 0) {
+ $message = sprintf('Send: ****** [%d]', strlen($message) - 8);
+ }
+
if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
$diff = $len - self::DEBUG_LINE_LENGTH;
$message = substr($message, 0, self::DEBUG_LINE_LENGTH)
diff --git a/program/lib/Roundcube/rcube_spellcheck_googie.php b/program/lib/Roundcube/rcube_spellcheck_googie.php
index 3777942a6..f9e450fdd 100644
--- a/program/lib/Roundcube/rcube_spellcheck_googie.php
+++ b/program/lib/Roundcube/rcube_spellcheck_googie.php
@@ -56,6 +56,10 @@ class rcube_spellcheck_googie extends rcube_spellcheck_engine
{
$this->content = $text;
+ if (empty($text)) {
+ return $this->matches = array();
+ }
+
// spell check uri is configured
$url = rcube::get_instance()->config->get('spellcheck_uri');
diff --git a/program/lib/Roundcube/rcube_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index 3d15eb660..43bab08c4 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -273,7 +273,7 @@ class rcube_spellchecker
public function is_exception($word)
{
// Contain only symbols (e.g. "+9,0", "2:2")
- if (!$word || preg_match('/^[0-9@#$%^&_+~*=:;?!,.-]+$/', $word))
+ if (!$word || preg_match('/^[0-9@#$%^&_+~*<>=:;?!,.-]+$/', $word))
return true;
// Contain symbols (e.g. "g@@gle"), all symbols excluding separators
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index ca65af1cb..c1293961c 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -35,9 +35,15 @@ abstract class rcube_storage
*/
public $conn;
+ /**
+ * List of supported special folder types
+ *
+ * @var array
+ */
+ public static $folder_types = array('drafts', 'sent', 'junk', 'trash');
+
protected $folder = 'INBOX';
protected $default_charset = 'ISO-8859-1';
- protected $default_folders = array('INBOX');
protected $search_set;
protected $options = array('auth_type' => 'check');
protected $page_size = 10;
@@ -146,6 +152,19 @@ abstract class rcube_storage
/**
+ * Get connection/class option
+ *
+ * @param string $name Option name
+ *
+ * @param mixed Option value
+ */
+ public function get_option($name)
+ {
+ return $this->options[$name];
+ }
+
+
+ /**
* Activate/deactivate debug mode.
*
* @param boolean $dbg True if conversation with the server should be logged
@@ -167,24 +186,6 @@ abstract class rcube_storage
/**
- * This list of folders will be listed above all other folders
- *
- * @param array $arr Indexed list of folder names
- */
- public function set_default_folders($arr)
- {
- if (is_array($arr)) {
- $this->default_folders = $arr;
-
- // add inbox if not included
- if (!in_array('INBOX', $this->default_folders)) {
- array_unshift($this->default_folders, 'INBOX');
- }
- }
- }
-
-
- /**
* Set internal folder reference.
* All operations will be perfomed on this folder.
*
@@ -613,7 +614,7 @@ abstract class rcube_storage
/**
* Parse message UIDs input
*
- * @param mixed $uids UIDs array or comma-separated list or '*' or '1:*'
+ * @param mixed $uids UIDs array or comma-separated list or '*' or '1:*'
*
* @return array Two elements array with UIDs converted to list and ALL flag
*/
@@ -633,6 +634,9 @@ abstract class rcube_storage
if (is_array($uids)) {
$uids = join(',', $uids);
}
+ else if (strpos($uids, ':')) {
+ $uids = join(',', rcube_imap_generic::uncompressMessageSet($uids));
+ }
if (preg_match('/[^0-9,]/', $uids)) {
$uids = '';
@@ -855,15 +859,59 @@ abstract class rcube_storage
*/
public function create_default_folders()
{
+ $rcube = rcube::get_instance();
+
// create default folders if they do not exist
- foreach ($this->default_folders as $folder) {
- if (!$this->folder_exists($folder)) {
- $this->create_folder($folder, true);
+ foreach (self::$folder_types as $type) {
+ if ($folder = $rcube->config->get($type . '_mbox')) {
+ if (!$this->folder_exists($folder)) {
+ $this->create_folder($folder, true, $type);
+ }
+ else if (!$this->folder_exists($folder, true)) {
+ $this->subscribe($folder);
+ }
}
- else if (!$this->folder_exists($folder, true)) {
- $this->subscribe($folder);
+ }
+ }
+
+
+ /**
+ * Check if specified folder is a special folder
+ */
+ public function is_special_folder($name)
+ {
+ return $name == 'INBOX' || in_array($name, $this->get_special_folders());
+ }
+
+
+ /**
+ * Return configured special folders
+ */
+ public function get_special_folders($forced = false)
+ {
+ // getting config might be expensive, store special folders in memory
+ if (!isset($this->icache['special-folders'])) {
+ $rcube = rcube::get_instance();
+ $this->icache['special-folders'] = array();
+
+ foreach (self::$folder_types as $type) {
+ if ($folder = $rcube->config->get($type . '_mbox')) {
+ $this->icache['special-folders'][$type] = $folder;
+ }
}
}
+
+ return $this->icache['special-folders'];
+ }
+
+
+ /**
+ * Set special folder associations stored in backend
+ */
+ public function set_special_folders($specials)
+ {
+ // should be overriden by storage class if backend supports special folders (SPECIAL-USE)
+ unset($this->icache['special-folders']);
}
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 77b91d18b..ce61e5367 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -42,7 +42,7 @@ class rcube_string_replacer
// Support unicode/punycode in top-level domain part
$utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
$url1 = '.:;,';
- $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*-';
+ $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*\x80-\xFE-';
$this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]*[$url2]+)*)/";
$this->mailto_pattern = "/("
diff --git a/program/lib/Roundcube/rcube_text2html.php b/program/lib/Roundcube/rcube_text2html.php
new file mode 100644
index 000000000..46c2b7e9a
--- /dev/null
+++ b/program/lib/Roundcube/rcube_text2html.php
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2008-2014, 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: |
+ | Converts plain text to HTML |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl> |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ * Converts plain text to HTML
+ *
+ * @package Framework
+ * @subpackage Utils
+ */
+class rcube_text2html
+{
+ /**
+ * Contains the HTML content after conversion.
+ *
+ * @var string $html
+ */
+ protected $html;
+
+ /**
+ * Contains the plain text.
+ *
+ * @var string $text
+ */
+ protected $text;
+
+ /**
+ * Configuration
+ *
+ * @var array $config
+ */
+ protected $config = array(
+ // non-breaking space
+ 'space' => "\xC2\xA0",
+ // enables format=flowed parser
+ 'flowed' => false,
+ // enables wrapping for non-flowed text
+ 'wrap' => true,
+ // line-break tag
+ 'break' => "<br>\n",
+ // prefix and suffix (wrapper element)
+ 'begin' => '<div class="pre">',
+ 'end' => '</div>',
+ // enables links replacement
+ 'links' => true,
+ );
+
+
+ /**
+ * Constructor.
+ *
+ * If the plain text source string (or file) is supplied, the class
+ * will instantiate with that source propagated, all that has
+ * to be done it to call get_html().
+ *
+ * @param string $source Plain text
+ * @param boolean $from_file Indicates $source is a file to pull content from
+ * @param array $config Class configuration
+ */
+ function __construct($source = '', $from_file = false, $config = array())
+ {
+ if (!empty($source)) {
+ $this->set_text($source, $from_file);
+ }
+
+ if (!empty($config) && is_array($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+ }
+
+ /**
+ * Loads source text into memory, either from $source string or a file.
+ *
+ * @param string $source Plain text
+ * @param boolean $from_file Indicates $source is a file to pull content from
+ */
+ function set_text($source, $from_file = false)
+ {
+ if ($from_file && file_exists($source)) {
+ $this->text = file_get_contents($source);
+ }
+ else {
+ $this->text = $source;
+ }
+
+ $this->_converted = false;
+ }
+
+ /**
+ * Returns the HTML content.
+ *
+ * @return string HTML content
+ */
+ function get_html()
+ {
+ if (!$this->_converted) {
+ $this->_convert();
+ }
+
+ return $this->html;
+ }
+
+ /**
+ * Prints the HTML.
+ */
+ function print_html()
+ {
+ print $this->get_html();
+ }
+
+ /**
+ * Workhorse function that does actual conversion (calls _converter() method).
+ */
+ protected function _convert()
+ {
+ // Convert TXT to HTML
+ $this->html = $this->_converter($this->text);
+ $this->_converted = true;
+ }
+
+ /**
+ * Workhorse function that does actual conversion.
+ *
+ * @param string Plain text
+ */
+ protected function _converter($text)
+ {
+ // make links and email-addresses clickable
+ $attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
+ $replacer = new rcmail_string_replacer($attribs);
+
+ if ($this->config['flowed']) {
+ $flowed_char = 0x01;
+ $text = rcube_mime::unfold_flowed($text, chr($flowed_char));
+ }
+
+ // search for patterns like links and e-mail addresses and replace with tokens
+ if ($this->config['links']) {
+ $text = $replacer->replace($text);
+ }
+
+ // split body into single lines
+ $text = preg_split('/\r?\n/', $text);
+ $quote_level = 0;
+ $last = null;
+
+ // wrap quoted lines with <blockquote>
+ for ($n = 0, $cnt = count($text); $n < $cnt; $n++) {
+ $flowed = false;
+ if ($this->config['flowed'] && ord($text[$n][0]) == $flowed_char) {
+ $flowed = true;
+ $text[$n] = substr($text[$n], 1);
+ }
+
+ if ($text[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $text[$n], $regs)) {
+ $q = substr_count($regs[0], '>');
+ $text[$n] = substr($text[$n], strlen($regs[0]));
+ $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
+ $_length = strlen(str_replace(' ', '', $text[$n]));
+
+ if ($q > $quote_level) {
+ if ($last !== null) {
+ $text[$last] .= (!$length ? "\n" : '')
+ . $replacer->get_replacement($replacer->add(
+ str_repeat('<blockquote>', $q - $quote_level)))
+ . $text[$n];
+
+ unset($text[$n]);
+ }
+ else {
+ $text[$n] = $replacer->get_replacement($replacer->add(
+ str_repeat('<blockquote>', $q - $quote_level))) . $text[$n];
+
+ $last = $n;
+ }
+ }
+ else if ($q < $quote_level) {
+ $text[$last] .= (!$length ? "\n" : '')
+ . $replacer->get_replacement($replacer->add(
+ str_repeat('</blockquote>', $quote_level - $q)))
+ . $text[$n];
+
+ unset($text[$n]);
+ }
+ else {
+ $last = $n;
+ }
+ }
+ else {
+ $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
+ $q = 0;
+ $_length = strlen(str_replace(' ', '', $text[$n]));
+
+ if ($quote_level > 0) {
+ $text[$last] .= (!$length ? "\n" : '')
+ . $replacer->get_replacement($replacer->add(
+ str_repeat('</blockquote>', $quote_level)))
+ . $text[$n];
+
+ unset($text[$n]);
+ }
+ else {
+ $last = $n;
+ }
+ }
+
+ $quote_level = $q;
+ $length = $_length;
+ }
+
+ if ($quote_level > 0) {
+ $text[$last] .= $replacer->get_replacement($replacer->add(
+ str_repeat('</blockquote>', $quote_level)));
+ }
+
+ $text = join("\n", $text);
+
+ // colorize signature (up to <sig_max_lines> lines)
+ $len = strlen($text);
+ $sig_sep = "--" . $this->config['space'] . "\n";
+ $sig_max_lines = rcube::get_instance()->config->get('sig_max_lines', 15);
+
+ while (($sp = strrpos($text, $sig_sep, $sp ? -$len+$sp-1 : 0)) !== false) {
+ if ($sp == 0 || $text[$sp-1] == "\n") {
+ // do not touch blocks with more that X lines
+ if (substr_count($text, "\n", $sp) < $sig_max_lines) {
+ $text = substr($text, 0, max(0, $sp))
+ .'<span class="sig">'.substr($text, $sp).'</span>';
+ }
+
+ break;
+ }
+ }
+
+ // insert url/mailto links and citation tags
+ $text = $replacer->resolve($text);
+
+ // replace line breaks
+ $text = str_replace("\n", $this->config['break'], $text);
+
+ return $this->config['begin'] . $text . $this->config['end'];
+ }
+
+ /**
+ * Converts spaces in line of text
+ */
+ protected function _convert_line($text, $is_flowed)
+ {
+ static $table;
+
+ if (empty($table)) {
+ $table = get_html_translation_table(HTML_SPECIALCHARS);
+ unset($table['?']);
+ }
+
+ // skip signature separator
+ if ($text == '-- ') {
+ return '--' . $this->config['space'];
+ }
+
+ // replace HTML special characters
+ $text = strtr($text, $table);
+
+ $nbsp = $this->config['space'];
+
+ // replace some whitespace characters
+ $text = str_replace(array("\r", "\t"), array('', ' '), $text);
+
+ // replace spaces with non-breaking spaces
+ if ($is_flowed) {
+ $pos = 0;
+ $diff = 0;
+ $len = strlen($nbsp);
+ $copy = $text;
+
+ while (($pos = strpos($text, ' ', $pos)) !== false) {
+ if ($pos == 0 || $text[$pos-1] == ' ') {
+ $copy = substr_replace($copy, $nbsp, $pos + $diff, 1);
+ $diff += $len - 1;
+ }
+ $pos++;
+ }
+
+ $text = $copy;
+ }
+ else {
+ // make the whole line non-breakable
+ $text = str_replace(array(' ', '-', '/'), array($nbsp, '-&#8288;', '/&#8288;'), $text);
+ }
+
+ return $text;
+ }
+}
diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php
index e232736c9..739b6f2a0 100644
--- a/program/lib/Roundcube/rcube_user.php
+++ b/program/lib/Roundcube/rcube_user.php
@@ -267,7 +267,10 @@ class rcube_user
"SELECT * FROM ".$this->db->table_name('identities').
" WHERE del <> 1 AND user_id = ?".
($sql_add ? " ".$sql_add : "").
- " ORDER BY ".$this->db->quote_identifier('standard')." DESC, name ASC, identity_id ASC",
+ " ORDER BY ". $this->db->quote_identifier('standard') . " DESC, "
+ . $this->db->quote_identifier('name') . " ASC, "
+ . $this->db->quote_identifier('email') . " ASC, "
+ . $this->db->quote_identifier('identity_id') . " ASC",
$this->ID);
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index c48cd80e8..00999ba50 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -593,18 +593,18 @@ class rcube_utils
*/
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') {
+ if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])
+ && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'
+ && in_array($_SERVER['REMOTE_ADDR'], rcube::get_instance()->config->get('proxy_whitelist', array()))) {
return true;
}
if ($port && $_SERVER['SERVER_PORT'] == $port) {
return true;
}
- if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https')) {
+ if ($use_https && rcube::get_instance()->config->get('use_https')) {
return true;
}
@@ -683,13 +683,22 @@ class rcube_utils
*/
public static function remote_addr()
{
- if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
- $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2);
- return $hosts[0];
- }
+ // Check if any of the headers are set first to improve performance
+ if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) || !empty($_SERVER['HTTP_X_REAL_IP'])) {
+ $proxy_whitelist = rcube::get_instance()->config->get('proxy_whitelist', array());
+ if (in_array($_SERVER['REMOTE_ADDR'], $proxy_whitelist)) {
+ if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ foreach(array_reverse(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])) as $forwarded_ip) {
+ if (!in_array($forwarded_ip, $proxy_whitelist)) {
+ return $forwarded_ip;
+ }
+ }
+ }
- if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
- return $_SERVER['HTTP_X_REAL_IP'];
+ if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
+ return $_SERVER['HTTP_X_REAL_IP'];
+ }
+ }
}
if (!empty($_SERVER['REMOTE_ADDR'])) {
@@ -919,7 +928,7 @@ class rcube_utils
/**
* Normalize the given string for fulltext search.
- * Currently only optimized for Latin-1 characters; to be extended
+ * Currently only optimized for ISO-8859-1 and ISO-8859-2 characters; to be extended
*
* @param string Input string (UTF-8)
* @param boolean True to return list of words as array
@@ -940,15 +949,32 @@ class rcube_utils
// split by words
$arr = self::tokenize_string($str);
+ // detect character set
+ if (utf8_encode(utf8_decode($str)) == $str) {
+ // ISO-8859-1 (or ASCII)
+ preg_match_all('/./u', 'äâàåáãæçéêëèïîìíñöôòøõóüûùúýÿ', $keys);
+ preg_match_all('/./', 'aaaaaaaceeeeiiiinoooooouuuuyy', $values);
+
+ $mapping = array_combine($keys[0], $values[0]);
+ $mapping = array_merge($mapping, array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u'));
+ }
+ else if (rcube_charset::convert(rcube_charset::convert($str, 'UTF-8', 'ISO-8859-2'), 'ISO-8859-2', 'UTF-8') == $str) {
+ // ISO-8859-2
+ preg_match_all('/./u', 'ąáâäćçčéęëěíîłľĺńňóôöŕřśšşťţůúűüźžżý', $keys);
+ preg_match_all('/./', 'aaaaccceeeeiilllnnooorrsssttuuuuzzzy', $values);
+
+ $mapping = array_combine($keys[0], $values[0]);
+ $mapping = array_merge($mapping, array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u'));
+ }
+
foreach ($arr as $i => $part) {
- if (utf8_encode(utf8_decode($part)) == $part) { // is latin-1 ?
- $arr[$i] = utf8_encode(strtr(strtolower(strtr(utf8_decode($part),
- 'ÇçäâàåéêëèïîìÅÉöôòüûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ',
- 'ccaaaaeeeeiiiaeooouuuyooaiounnaaaaaeeeiiioooouuuyy')),
- array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u')));
+ $part = mb_strtolower($part);
+
+ if (!empty($mapping)) {
+ $part = strtr($part, $mapping);
}
- else
- $arr[$i] = mb_strtolower($part);
+
+ $arr[$i] = $part;
}
return $as_array ? $arr : join(" ", $arr);
@@ -1030,7 +1056,6 @@ class rcube_utils
}
}
-
/**
* Find out if the string content means true or false
*
@@ -1045,4 +1070,16 @@ class rcube_utils
return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
}
+ /**
+ * OS-dependent absolute path detection
+ */
+ public static function is_absolute_path($path)
+ {
+ if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
+ return (bool) preg_match('!^[a-z]:[\\\\/]!i', $path);
+ }
+ else {
+ return $path[0] == DIRECTORY_SEPARATOR;
+ }
+ }
}
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index a54ee7e11..fb8fdd525 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -149,6 +149,11 @@ class rcube_vcard
$this->email[0] = $this->email[$pref_index];
$this->email[$pref_index] = $tmp;
}
+
+ // fix broken vcards from Outlook that only supply ORG but not the required N or FN properties
+ if (!strlen(trim($this->displayname . $this->surname . $this->firstname)) && strlen($this->organization)) {
+ $this->displayname = $this->organization;
+ }
}
/**
diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
index 5a5b3dc55..984294376 100644
--- a/program/lib/Roundcube/rcube_washtml.php
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -171,7 +171,7 @@ class rcube_washtml
*/
private function wash_style($style)
{
- $s = '';
+ $result = array();
foreach (explode(';', $style) as $declaration) {
if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
@@ -179,54 +179,48 @@ class rcube_washtml
$str = $match[2];
$value = '';
- while (sizeof($str) > 0 &&
- preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/
- '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'.
- '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'.
- '|#[0-9a-f]{3,6}'.
- '|[a-z0-9", -]+'.
- ')\s*/i', $str, $match)
- ) {
- if ($match[2]) {
- if (($src = $this->config['cid_map'][$match[2]])
- || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])
- ) {
- $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
- }
- else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) {
- if ($this->config['allow_remote']) {
- $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')';
+ foreach ($this->explode_style($str) as $val) {
+ if (preg_match('/^url\(/i', $val)) {
+ if (preg_match('/^url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
+ $url = $match[1];
+ if (($src = $this->config['cid_map'][$url])
+ || ($src = $this->config['cid_map'][$this->config['base_url'].$url])
+ ) {
+ $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
}
- else {
- $this->extlinks = true;
+ else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $url, $m)) {
+ if ($this->config['allow_remote']) {
+ $value .= ' url('.htmlspecialchars($m[0], ENT_QUOTES).')';
+ }
+ else {
+ $this->extlinks = true;
+ }
+ }
+ else if (preg_match('/^data:.+/i', $url)) { // RFC2397
+ $value .= ' url('.htmlspecialchars($url, ENT_QUOTES).')';
}
- }
- else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397
- $value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')';
}
}
- else {
+ else if (!preg_match('/^(behavior|expression)/i', $val)) {
// whitelist ?
- $value .= ' ' . $match[0];
+ $value .= ' ' . $val;
// #1488535: Fix size units, so width:800 would be changed to width:800px
- if (preg_match('/(left|right|top|bottom|width|height)/i', $cssid)
- && preg_match('/^[0-9]+$/', $match[0])
+ if (preg_match('/^(left|right|top|bottom|width|height)/i', $cssid)
+ && preg_match('/^[0-9]+$/', $val)
) {
$value .= 'px';
}
}
-
- $str = substr($str, strlen($match[0]));
}
if (isset($value[0])) {
- $s .= ($s?' ':'') . $cssid . ':' . $value . ';';
+ $result[] = $cssid . ':' . $value;
}
}
}
- return $s;
+ return implode('; ', $result);
}
/**
@@ -283,10 +277,12 @@ class rcube_washtml
/**
* The main loop that recurse on a node tree.
- * It output only allowed tags with allowed attributes
- * and allowed inline styles
+ * It output only allowed tags with allowed attributes and allowed inline styles
+ *
+ * @param DOMNode $node HTML element
+ * @param int $level Recurrence level (safe initial value found empirically)
*/
- private function dumpHtml($node, $level = 0)
+ private function dumpHtml($node, $level = 20)
{
if (!$node->hasChildNodes()) {
return '';
@@ -460,7 +456,7 @@ class rcube_washtml
// Remove invalid HTML comments (#1487759)
// Don't remove valid conditional comments
// Don't remove MSOutlook (<!-->) conditional comments (#1489004)
- $html = preg_replace('/<!--[^->\[\n]+>/', '', $html);
+ $html = preg_replace('/<!--[^-<>\[\n]+>/', '', $html);
// fix broken nested lists
self::fix_broken_lists($html);
@@ -576,4 +572,49 @@ class rcube_washtml
}
}
}
+
+ /**
+ * Explode css style value
+ */
+ protected function explode_style($style)
+ {
+ $style = trim($style);
+
+ // first remove comments
+ $pos = 0;
+ while (($pos = strpos($style, '/*', $pos)) !== false) {
+ $end = strpos($style, '*/', $pos+2);
+
+ if ($end === false) {
+ $style = substr($style, 0, $pos);
+ }
+ else {
+ $style = substr_replace($style, '', $pos, $end - $pos + 2);
+ }
+ }
+
+ $strlen = strlen($style);
+ $result = array();
+
+ // explode value
+ for ($p=$i=0; $i < $strlen; $i++) {
+ if (($style[$i] == "\"" || $style[$i] == "'") && $style[$i-1] != "\\") {
+ if ($q == $style[$i]) {
+ $q = false;
+ }
+ else if (!$q) {
+ $q = $style[$i];
+ }
+ }
+
+ if (!$q && $style[$i] == ' ' && !preg_match('/[,\(]/', $style[$i-1])) {
+ $result[] = substr($style, $p, $i - $p);
+ $p = $i + 1;
+ }
+ }
+
+ $result[] = (string) substr($style, $p);
+
+ return $result;
+ }
}