summaryrefslogtreecommitdiff
path: root/program/lib/Roundcube
diff options
context:
space:
mode:
Diffstat (limited to 'program/lib/Roundcube')
-rw-r--r--program/lib/Roundcube/html.php2
-rw-r--r--program/lib/Roundcube/rcube.php12
-rw-r--r--program/lib/Roundcube/rcube_cache.php35
-rw-r--r--program/lib/Roundcube/rcube_db.php90
-rw-r--r--program/lib/Roundcube/rcube_db_mssql.php20
-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.php2
-rw-r--r--program/lib/Roundcube/rcube_image.php120
-rw-r--r--program/lib/Roundcube/rcube_imap.php149
-rw-r--r--program/lib/Roundcube/rcube_imap_generic.php50
-rw-r--r--program/lib/Roundcube/rcube_imap_search.php25
-rw-r--r--program/lib/Roundcube/rcube_ldap_generic.php8
-rw-r--r--program/lib/Roundcube/rcube_mime.php22
-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_result_multifolder.php27
-rw-r--r--program/lib/Roundcube/rcube_session.php12
-rw-r--r--program/lib/Roundcube/rcube_spellchecker.php15
-rw-r--r--program/lib/Roundcube/rcube_storage.php13
-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_utils.php34
-rw-r--r--program/lib/Roundcube/rcube_washtml.php10
24 files changed, 821 insertions, 300 deletions
diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php
index de03265c1..0209d1bf2 100644
--- a/program/lib/Roundcube/html.php
+++ b/program/lib/Roundcube/html.php
@@ -283,7 +283,7 @@ class html
continue;
}
- // ignore not allowed attributes
+ // ignore not allowed attributes, except data-*
if (!empty($allowed)) {
$is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0;
$is_aria_attr = @substr_compare($key, 'aria-', 0, 5) === 0;
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index 707929951..d618fb64d 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -1132,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);
@@ -1479,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_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_db.php b/program/lib/Roundcube/rcube_db.php
index a2271fd6d..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;
}
}
@@ -609,8 +611,7 @@ class rcube_db
{
// get tables if not cached
if ($this->tables === null) {
- $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME',
- array($this->db_dsnw_array['database']));
+ $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME');
if ($q) {
$this->tables = $q->fetchAll(PDO::FETCH_COLUMN, 0);
@@ -632,8 +633,8 @@ class rcube_db
*/
public function list_cols($table)
{
- $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?',
- array($table, $this->db_dsnw_array['database']));
+ $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
+ array($table));
if ($q) {
return $q->fetchAll(PDO::FETCH_COLUMN, 0);
@@ -921,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;
}
@@ -954,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;
}
/**
@@ -1130,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_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 8628371d7..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);
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 4204354b3..e29bfc46b 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -1489,23 +1489,39 @@ 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';
}
- // multi-folder search
- if (is_array($folder) && count($folder) > 1 && $str != 'ALL') {
- new rcube_result_index; // trigger autoloader and make these classes available for threaded context
- new rcube_result_thread;
+ if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) {
+ $folder = $this->folder;
+ }
+ $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();
@@ -1518,29 +1534,28 @@ class rcube_imap extends rcube_storage
$searcher->set_timelimit(60);
// continue existing incomplete search
- if (!empty($this->search_set) && $this->search_set->incomplete && $str == $this->search_string) {
+ 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,
- $str,
+ $search,
$charset ? $charset : $this->default_charset,
$sort_field && $this->get_capability('SORT') ? $sort_field : null,
$this->threading
);
}
- else {
- $folder = is_array($folder) ? $folder[0] : $folder;
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
- $results = $this->search_index($folder, $str, $charset, $sort_field);
+ 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);
}
- $this->set_search_set(array($str, $results, $charset, $sort_field,
- $this->threading || $this->search_sorted ? true : false));
+ $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false;
+
+ $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted));
return $results;
}
@@ -1676,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);
}
@@ -1830,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
@@ -2972,7 +2990,7 @@ class rcube_imap extends rcube_storage
* @param array $result Reference to folders list
* @param string $type Listing type (ext-subscribed, subscribed or all)
*/
- private function list_folders_update(&$result, $type = null)
+ protected function list_folders_update(&$result, $type = null)
{
$namespace = $this->get_namespace();
$search = array();
@@ -4124,61 +4142,82 @@ class rcube_imap extends rcube_storage
*/
public function sort_folder_list($a_folders, $skip_default = false)
{
- $a_out = $a_defaults = $folders = array();
-
- $delimiter = $this->get_hierarchy_delimiter();
$specials = array_merge(array('INBOX'), array_values($this->get_special_folders()));
+ $folders = array();
- // 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 (!$skip_default && ($p = array_search($folder, $specials)) !== false && !$a_defaults[$p]) {
- $a_defaults[$p] = $folder;
- }
- else {
- $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
+ 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');
}
}
- // sort folders and place defaults on the top
- asort($folders, SORT_LOCALE_STRING);
- ksort($a_defaults);
- $folders = array_merge($a_defaults, array_keys($folders));
+ // 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);
- // 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);
+ if ($skip_default) {
+ return $folders;
}
- return $a_out;
- }
+ // force the type of folder name variable (#1485527)
+ $folders = array_map('strval', $folders);
+ $out = array();
+
+ // finally we must put special folders on top and rebuild the list
+ // to move their subfolders where they belong...
+ $specials = array_unique(array_intersect($specials, $folders));
+ $folders = array_merge($specials, array_diff($folders, $specials));
+ $this->sort_folder_specials(null, $folders, $specials, $out);
+
+ return $out;
+ }
/**
- * Recursive method for sorting folders
+ * Recursive function to put subfolders of special folders in place
*/
- protected function rsort($folder, $delimiter, &$list, &$out)
+ protected function sort_folder_specials($folder, &$list, &$specials, &$out)
{
while (list($key, $name) = each($list)) {
- if (strpos($name, $folder.$delimiter) === 0) {
- // set the type of folder name variable (#1485527)
- $out[] = (string) $name;
+ if ($folder === null || strpos($name, $folder.$this->delimiter) === 0) {
+ $out[] = $name;
unset($list[$key]);
- $this->rsort($name, $delimiter, $list, $out);
+
+ if (!empty($specials) && ($found = array_search($name, $specials)) !== false) {
+ unset($specials[$found]);
+ $this->sort_folder_specials($name, $list, $specials, $out);
+ }
}
}
+
reset($list);
}
+ /**
+ * Callback for uasort() that implements correct
+ * locale-aware case-sensitive sorting
+ */
+ protected function sort_folder_comparator($str1, $str2)
+ {
+ $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);
+ }
+ }
+
/**
* Find UID of the specified message sequence ID
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index f45694dd0..e4c9b7eb8 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -50,17 +50,17 @@ class rcube_imap_generic
public static $mupdate;
- private $fp;
- private $host;
- private $logged = false;
- private $capability = array();
- private $capability_readed = false;
- private $prefs;
- private $cmd_tag;
- private $cmd_num = 0;
- private $resourceid;
- private $_debug = false;
- private $_debug_handler = false;
+ protected $fp;
+ protected $host;
+ protected $logged = false;
+ protected $capability = array();
+ protected $capability_readed = false;
+ protected $prefs;
+ protected $cmd_tag;
+ protected $cmd_num = 0;
+ protected $resourceid;
+ protected $_debug = false;
+ protected $_debug_handler = false;
const ERROR_OK = 0;
const ERROR_NO = -1;
@@ -352,7 +352,7 @@ class rcube_imap_generic
*
* @return bool True if connection is closed
*/
- private function eof()
+ protected function eof()
{
if (!is_resource($this->fp)) {
return true;
@@ -375,7 +375,7 @@ class rcube_imap_generic
/**
* Closes connection stream.
*/
- private function closeSocket()
+ protected function closeSocket()
{
@fclose($this->fp);
$this->fp = null;
@@ -421,7 +421,7 @@ class rcube_imap_generic
return false;
}
- private function hasCapability($name)
+ protected function hasCapability($name)
{
if (empty($this->capability) || $name == '') {
return false;
@@ -1310,7 +1310,7 @@ class rcube_imap_generic
* @return array List of mailboxes or hash of options if $status_ops argument
* is non-empty.
*/
- private function _listMailboxes($ref, $mailbox, $subscribed=false,
+ protected function _listMailboxes($ref, $mailbox, $subscribed=false,
$status_opts=array(), $select_opts=array())
{
if (!strlen($mailbox)) {
@@ -1985,7 +1985,7 @@ class rcube_imap_generic
*
* @return bool True on success, False on failure
*/
- private function modFlag($mailbox, $messages, $flag, $mod = '+')
+ protected function modFlag($mailbox, $messages, $flag, $mod = '+')
{
if ($mod != '+' && $mod != '-') {
$mod = '+';
@@ -3143,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]);
}
@@ -3161,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]);
}
@@ -3306,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;
}
}
@@ -3679,7 +3681,7 @@ class rcube_imap_generic
return $result;
}
- private function _xor($string, $string2)
+ protected function _xor($string, $string2)
{
$result = '';
$size = strlen($string);
@@ -3698,7 +3700,7 @@ class rcube_imap_generic
*
* @return string Space-separated list of flags
*/
- private function flagsToStr($flags)
+ protected function flagsToStr($flags)
{
foreach ((array)$flags as $idx => $flag) {
if ($flag = $this->flags[strtoupper($flag)]) {
@@ -3750,7 +3752,7 @@ class rcube_imap_generic
/**
* CAPABILITY response parser
*/
- private function parseCapability($str, $trusted=false)
+ protected function parseCapability($str, $trusted=false)
{
$str = preg_replace('/^\* CAPABILITY /i', '', $str);
@@ -3827,7 +3829,7 @@ class rcube_imap_generic
*
* @since 0.5-stable
*/
- private function debug($message)
+ protected function debug($message)
{
if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
$diff = $len - self::DEBUG_LINE_LENGTH;
diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php
index 0c44daf1b..365d78f76 100644
--- a/program/lib/Roundcube/rcube_imap_search.php
+++ b/program/lib/Roundcube/rcube_imap_search.php
@@ -29,7 +29,7 @@ class rcube_imap_search
{
public $options = array();
- protected $jobs = array();
+ protected $jobs = array();
protected $timelimit = 0;
protected $results;
protected $conn;
@@ -40,7 +40,7 @@ class rcube_imap_search
public function __construct($options, $conn)
{
$this->options = $options;
- $this->conn = $conn;
+ $this->conn = $conn;
}
/**
@@ -54,7 +54,7 @@ class rcube_imap_search
*/
public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null)
{
- $start = floor(microtime(true));
+ $start = floor(microtime(true));
$results = new rcube_result_multifolder($folders);
// start a search job for every folder to search in
@@ -65,7 +65,8 @@ class rcube_imap_search
$results->add($result);
}
else {
- $job = new rcube_imap_search_job($folder, $str, $charset, $sort_field, $threading);
+ $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;
}
@@ -129,11 +130,11 @@ class rcube_imap_search_job /* extends Stackable */
public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
{
- $this->folder = $folder;
- $this->search = $str;
- $this->charset = $charset;
+ $this->folder = $folder;
+ $this->search = $str;
+ $this->charset = $charset;
$this->sort_field = $sort_field;
- $this->threading = $threading;
+ $this->threading = $threading;
$this->result = new rcube_result_index($folder);
$this->result->incomplete = true;
@@ -150,9 +151,8 @@ class rcube_imap_search_job /* extends Stackable */
protected function search_index()
{
$criteria = $this->search;
- $charset = $this->charset;
-
- $imap = $this->worker->get_imap();
+ $charset = $this->charset;
+ $imap = $this->worker->get_imap();
if (!$imap->connected()) {
trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);
@@ -228,7 +228,4 @@ class rcube_imap_search_job /* extends Stackable */
{
return $this->result;
}
-
}
-
-
diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php
index f1048ef39..252eafabe 100644
--- a/program/lib/Roundcube/rcube_ldap_generic.php
+++ b/program/lib/Roundcube/rcube_ldap_generic.php
@@ -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");
@@ -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_mime.php b/program/lib/Roundcube/rcube_mime.php
index 091b2fae8..370d5a8d5 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -480,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)) {
@@ -508,6 +510,10 @@ class rcube_mime
) {
$text[$last] .= $line;
unset($text[$idx]);
+
+ if ($mark) {
+ $marks[$last] = true;
+ }
}
else {
$last = $idx;
@@ -520,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] != '-- '
@@ -528,6 +534,10 @@ class rcube_mime
) {
$text[$last] .= $line;
unset($text[$idx]);
+
+ if ($mark) {
+ $marks[$last] = true;
+ }
}
else {
$text[$idx] = $line;
@@ -538,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_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php
index 4bbd2188d..786ee85f6 100644
--- a/program/lib/Roundcube/rcube_result_multifolder.php
+++ b/program/lib/Roundcube/rcube_result_multifolder.php
@@ -26,16 +26,16 @@
*/
class rcube_result_multifolder
{
- public $multi = true;
- public $sets = array();
+ public $multi = true;
+ public $sets = array();
public $incomplete = false;
public $folder;
- protected $meta = array();
- protected $index = array();
+ protected $meta = array();
+ protected $index = array();
protected $folders = array();
+ protected $order = 'ASC';
protected $sorting;
- protected $order = 'ASC';
/**
@@ -44,7 +44,7 @@ class rcube_result_multifolder
public function __construct($folders = array())
{
$this->folders = $folders;
- $this->meta = array('count' => 0);
+ $this->meta = array('count' => 0);
}
@@ -74,7 +74,8 @@ class rcube_result_multifolder
// append UIDs to global index
$folder = $result->get_parameters('MAILBOX');
- $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get());
+ $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get());
+
$this->index = array_merge($this->index, $index);
}
@@ -89,7 +90,7 @@ class rcube_result_multifolder
}
$this->sorting = $sort_field;
- $this->order = $sort_order;
+ $this->order = $sort_order;
}
/**
@@ -150,8 +151,10 @@ class rcube_result_multifolder
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());
+ $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $set->get());
+
$this->index = array_merge($this->index, $index);
}
}
@@ -171,6 +174,7 @@ class rcube_result_multifolder
if (!empty($this->folder)) {
$msgid .= '-' . $this->folder;
}
+
return array_search($msgid, $this->index);
}
@@ -188,6 +192,7 @@ class rcube_result_multifolder
if ($set->get_parameters('MAILBOX') == $folder) {
$set->filter($ids);
}
+
$this->meta['count'] += $set->count();
}
}
@@ -267,8 +272,8 @@ class rcube_result_multifolder
public function get_parameters($param=null)
{
$params = array(
- 'SORT' => $this->sorting,
- 'ORDER' => $this->order,
+ 'SORT' => $this->sorting,
+ 'ORDER' => $this->order,
'MAILBOX' => $this->folders,
);
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_spellchecker.php b/program/lib/Roundcube/rcube_spellchecker.php
index 5b77bda02..43bab08c4 100644
--- a/program/lib/Roundcube/rcube_spellchecker.php
+++ b/program/lib/Roundcube/rcube_spellchecker.php
@@ -226,7 +226,18 @@ class rcube_spellchecker
else {
$word = mb_substr($this->content, $item[1], $item[2], RCUBE_CHARSET);
}
- $result[$word] = is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
+
+ if (is_array($item[4])) {
+ $suggestions = $item[4];
+ }
+ else if (empty($item[4])) {
+ $suggestions = array();
+ }
+ else {
+ $suggestions = explode("\t", $item[4]);
+ }
+
+ $result[$word] = $suggestions;
}
return $result;
@@ -262,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 69d6d2fae..c1293961c 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -152,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
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_utils.php b/program/lib/Roundcube/rcube_utils.php
index c2009cee0..00999ba50 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -928,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
@@ -949,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);
@@ -1039,7 +1056,6 @@ class rcube_utils
}
}
-
/**
* Find out if the string content means true or false
*
diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
index e23e5b21d..b93d3b117 100644
--- a/program/lib/Roundcube/rcube_washtml.php
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -95,6 +95,7 @@ class rcube_washtml
'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q',
's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img',
+ 'video', 'source',
// form elements
'button', 'input', 'textarea', 'select', 'option', 'optgroup'
);
@@ -206,7 +207,7 @@ class rcube_washtml
$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)
+ if (preg_match('/^(left|right|top|bottom|width|height)/i', $cssid)
&& preg_match('/^[0-9]+$/', $val)
) {
$value .= 'px';
@@ -246,7 +247,10 @@ class rcube_washtml
$quot = strpos($style, '"') !== false ? "'" : '"';
$t .= ' style=' . $quot . $style . $quot;
}
- else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway
+ else if ($key == 'background'
+ || ($key == 'src' && preg_match('/^(img|source)$/i', $node->tagName))
+ || ($key == 'poster' && strtolower($node->tagName) == 'video')
+ ) {
if (($src = $this->config['cid_map'][$value])
|| ($src = $this->config['cid_map'][$this->config['base_url'].$value])
) {
@@ -456,7 +460,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);