diff options
author | Hugues Hiegel <root@paranoid> | 2015-04-21 12:49:44 +0200 |
---|---|---|
committer | Hugues Hiegel <root@paranoid> | 2015-04-21 12:49:44 +0200 |
commit | 733f8e8d0ce6217d906d06dc4fb08e36d48ed794 (patch) | |
tree | cff28366ff63ea6596f8026e1698090bd0b9405c /program/lib/Roundcube | |
parent | ef2e7b3f9d264ec146d4dae257b1e295ab3b462a (diff) | |
parent | a4ba3df54834ee90fb2c9930669f1229dc80261a (diff) |
Conflicts:
composer.json-dist
config/defaults.inc.php
plugins
plugins/acl/acl.js
plugins/acl/acl.php
plugins/acl/skins/classic/templates/table.html
plugins/acl/skins/larry/templates/table.html
plugins/enigma/README
plugins/enigma/config.inc.php.dist
plugins/enigma/enigma.js
plugins/enigma/enigma.php
plugins/enigma/lib/enigma_driver.php
plugins/enigma/lib/enigma_driver_gnupg.php
plugins/enigma/lib/enigma_driver_phpssl.php
plugins/enigma/lib/enigma_engine.php
plugins/enigma/lib/enigma_error.php
plugins/enigma/lib/enigma_key.php
plugins/enigma/lib/enigma_signature.php
plugins/enigma/lib/enigma_subkey.php
plugins/enigma/lib/enigma_ui.php
plugins/enigma/lib/enigma_userid.php
plugins/enigma/localization/en_US.inc
plugins/enigma/localization/ja_JP.inc
plugins/enigma/localization/ru_RU.inc
plugins/enigma/skins/classic/enigma.css
plugins/enigma/skins/classic/templates/keys.html
plugins/help/config.inc.php.dist
plugins/help/help.php
plugins/help/localization/en_US.inc
plugins/jqueryui/jqueryui.php
plugins/managesieve/Changelog
plugins/managesieve/composer.json
plugins/managesieve/config.inc.php.dist
plugins/managesieve/lib/Roundcube/rcube_sieve.php
plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php
plugins/managesieve/localization/en_US.inc
plugins/managesieve/managesieve.js
plugins/managesieve/skins/classic/managesieve.css
plugins/managesieve/skins/larry/managesieve.css
plugins/password/README
plugins/password/config.inc.php.dist
plugins/password/drivers/ldap.php
plugins/password/drivers/poppassd.php
plugins/password/drivers/vpopmaild.php
plugins/vcard_attachments/vcardattach.js
plugins/zipdownload/zipdownload.php
Diffstat (limited to 'program/lib/Roundcube')
31 files changed, 1233 insertions, 517 deletions
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index 24c1f86d4..ba7954e84 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -78,9 +78,11 @@ if (!defined('RCUBE_LOCALIZATION_DIR')) { } // set internal encoding for mbstring extension -if (extension_loaded('mbstring')) { +if (function_exists('mb_internal_encoding')) { mb_internal_encoding(RCUBE_CHARSET); - @mb_regex_encoding(RCUBE_CHARSET); +} +if (function_exists('mb_regex_encoding')) { + mb_regex_encoding(RCUBE_CHARSET); } // make sure the Roundcube lib directory is in the include_path @@ -97,7 +99,7 @@ if (!preg_match($regexp, $path)) { spl_autoload_register('rcube_autoload'); // set PEAR error handling (will also load the PEAR main class) -PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error'); +@PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error'); diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 3aca88843..cf6ebf993 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -521,16 +521,9 @@ class rcube ini_set('session.use_only_cookies', 1); ini_set('session.cookie_httponly', 1); - // use database for storing session data - $this->session = new rcube_session($this->get_dbh(), $this->config); - + // get session driver instance + $this->session = rcube_session::factory($this->config); $this->session->register_gc_handler(array($this, 'gc')); - $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); - $this->session->set_ip_check($this->config->get('ip_check')); - - if ($this->config->get('session_auth_name')) { - $this->session->set_cookiename($this->config->get('session_auth_name')); - } // start PHP session (if not in CLI mode) if ($_SERVER['REMOTE_ADDR']) { @@ -538,7 +531,6 @@ class rcube } } - /** * Garbage collector - cache/temp cleaner */ @@ -1681,15 +1673,18 @@ class rcube if ($message->getParam('delay_file_io')) { // use common temp dir - $temp_dir = $this->config->get('temp_dir'); - $body_file = tempnam($temp_dir, 'rcmMsg'); - if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) { + $temp_dir = $this->config->get('temp_dir'); + $body_file = tempnam($temp_dir, 'rcmMsg'); + $mime_result = $message->saveMessageBody($body_file); + + if (is_a($mime_result, 'PEAR_Error')) { self::raise_error(array('code' => 650, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not create message: ".$mime_result->getMessage()), - TRUE, FALSE); + true, false); return false; } + $msg_body = fopen($body_file, 'r'); } else { @@ -1732,11 +1727,11 @@ class rcube $msg_body = $message->get(); - if (PEAR::isError($msg_body)) { + if (is_a($msg_body, 'PEAR_Error')) { self::raise_error(array('code' => 650, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not create message: ".$msg_body->getMessage()), - TRUE, FALSE); + true, false); } else { $delim = $this->config->header_delimiter(); diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php index 69027b0e8..31189e0fc 100644 --- a/program/lib/Roundcube/rcube_addressbook.php +++ b/program/lib/Roundcube/rcube_addressbook.php @@ -544,13 +544,20 @@ abstract class rcube_addressbook $fn = trim($fn, ', '); - // fallback to display name - if (empty($fn) && $contact['name']) - $fn = $contact['name']; - - // fallback to email address - if (empty($fn) && ($email = self::get_col_values('email', $contact, true)) && !empty($email)) { - return $email[0]; + // fallbacks... + if ($fn === '') { + // ... display name + if (!empty($contact['name'])) { + $fn = $contact['name']; + } + // ... organization + else if (!empty($contact['organization'])) { + $fn = $contact['organization']; + } + // ... email address + else if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) { + $fn = $email[0]; + } } return $fn; @@ -587,6 +594,13 @@ abstract class rcube_addressbook switch ($key) { case 'name': $value = $name ?: self::compose_list_name($contact); + + // If name(s) are undefined compose_list_name() may return an email address + // here we prevent from returning the same name and email + if ($name === $email && strpos($result, '{email}') !== false) { + $value = ''; + } + break; case 'email': diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php index 52a2db997..303abdac4 100644 --- a/program/lib/Roundcube/rcube_cache.php +++ b/program/lib/Roundcube/rcube_cache.php @@ -605,8 +605,10 @@ class rcube_cache $this->max_packet = 2097152; // default/max is 2 MB if ($this->type == 'db') { - $value = $this->db->get_variable('max_allowed_packet', $this->max_packet); - $this->max_packet = max($value, $this->max_packet) - 2000; + if ($value = $this->db->get_variable('max_allowed_packet', $this->max_packet)) { + $this->max_packet = $value; + } + $this->max_packet -= 2000; } else if ($this->type == 'memcache') { $stats = $this->db->getStats(); diff --git a/program/lib/Roundcube/rcube_cache_shared.php b/program/lib/Roundcube/rcube_cache_shared.php index 339a9aa20..3f0f20e41 100644 --- a/program/lib/Roundcube/rcube_cache_shared.php +++ b/program/lib/Roundcube/rcube_cache_shared.php @@ -595,8 +595,10 @@ class rcube_cache_shared $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; + if ($value = $this->db->get_variable('max_allowed_packet', $this->max_packet)) { + $this->max_packet = $value; + } + $this->max_packet -= 2000; } else if ($this->type == 'memcache') { $stats = $this->db->getStats(); diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php index 89e45449d..e80474af2 100644 --- a/program/lib/Roundcube/rcube_config.php +++ b/program/lib/Roundcube/rcube_config.php @@ -683,7 +683,6 @@ class rcube_config '180' => "Europe/Moscow", '210' => "Asia/Tehran", '240' => "Asia/Dubai", - '300' => "Asia/Karachi", '270' => "Asia/Kabul", '300' => "Asia/Karachi", '330' => "Asia/Kolkata", diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php index 6ac9fd5de..23e0b710c 100644 --- a/program/lib/Roundcube/rcube_contacts.php +++ b/program/lib/Roundcube/rcube_contacts.php @@ -302,46 +302,27 @@ class rcube_contacts extends rcube_addressbook */ function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array()) { - if (!is_array($fields)) - $fields = array($fields); if (!is_array($required) && !empty($required)) $required = array($required); - $where = $and_where = array(); + $where = $and_where = $post_search = array(); $mode = intval($mode); $WS = ' '; $AS = self::SEPARATOR; - foreach ($fields as $idx => $col) { - // direct ID search - if ($col == 'ID' || $col == $this->primary_key) { - $ids = !is_array($value) ? explode(self::SEPARATOR, $value) : $value; - $ids = $this->db->array2list($ids, 'integer'); - $where[] = 'c.' . $this->primary_key.' IN ('.$ids.')'; - continue; - } - // fulltext search in all fields - else if ($col == '*') { - $words = array(); - foreach (explode($WS, rcube_utils::normalize_string($value)) as $word) { - switch ($mode) { - case 1: // strict - $words[] = '(' . $this->db->ilike('words', $word . '%') - . ' OR ' . $this->db->ilike('words', '%' . $WS . $word . $WS . '%') - . ' OR ' . $this->db->ilike('words', '%' . $WS . $word) . ')'; - break; - case 2: // prefix - $words[] = '(' . $this->db->ilike('words', $word . '%') - . ' OR ' . $this->db->ilike('words', '%' . $WS . $word . '%') . ')'; - break; - default: // partial - $words[] = $this->db->ilike('words', '%' . $word . '%'); - } - } - $where[] = '(' . join(' AND ', $words) . ')'; - } - else { - $val = is_array($value) ? $value[$idx] : $value; + // direct ID search + if ($fields == 'ID' || $fields == $this->primary_key) { + $ids = !is_array($value) ? explode(self::SEPARATOR, $value) : $value; + $ids = $this->db->array2list($ids, 'integer'); + $where[] = 'c.' . $this->primary_key.' IN ('.$ids.')'; + } + else if (is_array($value)) { + foreach ((array)$fields as $idx => $col) { + $val = $value[$idx]; + + if (!strlen($val)) + continue; + // table column if (in_array($col, $this->table_cols)) { switch ($mode) { @@ -362,36 +343,35 @@ class rcube_contacts extends rcube_addressbook // vCard field else { if (in_array($col, $this->fulltext_cols)) { - foreach (rcube_utils::normalize_string($val, true) as $word) { - switch ($mode) { - case 1: // strict - $words[] = '(' . $this->db->ilike('words', $word . $WS . '%') - . ' OR ' . $this->db->ilike('words', '%' . $AS . $word . $WS .'%') - . ' OR ' . $this->db->ilike('words', '%' . $AS . $word) . ')'; - break; - case 2: // prefix - $words[] = '(' . $this->db->ilike('words', $word . '%') - . ' OR ' . $this->db->ilike('words', $AS . $word . '%') . ')'; - break; - default: // partial - $words[] = $this->db->ilike('words', '%' . $word . '%'); - } - } - $where[] = '(' . join(' AND ', $words) . ')'; + $where[] = $this->fulltext_sql_where($val, $mode, 'words'); } - if (is_array($value)) - $post_search[$col] = mb_strtolower($val); + $post_search[$col] = mb_strtolower($val); } } } + // fulltext search in all fields + else if ($fields == '*') { + $where[] = $this->fulltext_sql_where($value, $mode, 'words'); + } + else { + // require each word in to be present in one of the fields + foreach (rcube_utils::tokenize_string($value, 1) as $word) { + $groups = array(); + foreach ((array)$fields as $idx => $col) { + $groups[] = $this->fulltext_sql_where($word, $mode, $col); + } + $where[] = '(' . join(' OR ', $groups) . ')'; + } + } foreach (array_intersect($required, $this->table_cols) as $col) { $and_where[] = $this->db->quote_identifier($col).' <> '.$this->db->quote(''); } + $required = array_diff($required, $this->table_cols); if (!empty($where)) { // use AND operator for advanced searches - $where = join(is_array($value) ? ' AND ' : ' OR ', $where); + $where = join(" AND ", $where); } if (!empty($and_where)) @@ -399,7 +379,7 @@ class rcube_contacts extends rcube_addressbook // Post-searching in vCard data fields // we will search in all records and then build a where clause for their IDs - if (!empty($post_search)) { + if (!empty($post_search) || !empty($required)) { $ids = array(0); // build key name regexp $regexp = '/^(' . implode(array_keys($post_search), '|') . ')(?:.*)$/'; @@ -408,7 +388,7 @@ class rcube_contacts extends rcube_addressbook $this->set_search_set($where); // count result pages - $cnt = $this->count(); + $cnt = $this->count()->count; $pages = ceil($cnt / $this->page_size); $scnt = count($post_search); @@ -418,14 +398,33 @@ class rcube_contacts extends rcube_addressbook while ($row = $this->result->next()) { $id = $row[$this->primary_key]; $found = array(); - foreach (preg_grep($regexp, array_keys($row)) as $col) { - $pos = strpos($col, ':'); - $colname = $pos ? substr($col, 0, $pos) : $col; - $search = $post_search[$colname]; - foreach ((array)$row[$col] as $value) { - if ($this->compare_search_value($colname, $value, $search, $mode)) { - $found[$colname] = true; - break 2; + if (!empty($post_search)) { + foreach (preg_grep($regexp, array_keys($row)) as $col) { + $pos = strpos($col, ':'); + $colname = $pos ? substr($col, 0, $pos) : $col; + $search = $post_search[$colname]; + foreach ((array)$row[$col] as $value) { + if ($this->compare_search_value($colname, $value, $search, $mode)) { + $found[$colname] = true; + break 2; + } + } + } + } + // check if required fields are present + if (!empty($required)) { + foreach ($required as $req) { + $hit = false; + foreach ($row as $c => $values) { + if ($c === $req || strpos($c, $req.':') === 0) { + if ((is_string($row[$c]) && strlen($row[$c])) || !empty($row[$c])) { + $hit = true; + break; + } + } + } + if (!$hit) { + continue 2; } } } @@ -460,6 +459,34 @@ class rcube_contacts extends rcube_addressbook return $this->result; } + /** + * Helper method to compose SQL where statements for fulltext searching + */ + private function fulltext_sql_where($value, $mode, $col = 'words', $bool = 'AND') + { + $WS = ' '; + $AS = $col == 'words' ? $WS : self::SEPARATOR; + $words = $col == 'words' ? rcube_utils::normalize_string($value, true) : array($value); + + $where = array(); + foreach ($words as $word) { + switch ($mode) { + case 1: // strict + $where[] = '(' . $this->db->ilike($col, $word . '%') + . ' OR ' . $this->db->ilike($col, '%' . $WS . $word . $WS . '%') + . ' OR ' . $this->db->ilike($col, '%' . $WS . $word) . ')'; + break; + case 2: // prefix + $where[] = '(' . $this->db->ilike($col, $word . '%') + . ' OR ' . $this->db->ilike($col, '%' . $AS . $word . '%') . ')'; + break; + default: // partial + $where[] = $this->db->ilike($col, '%' . $word . '%'); + } + } + + return count($where) ? '(' . join(" $bool ", $where) . ')' : ''; + } /** * Count number of available contacts in database @@ -714,6 +741,11 @@ class rcube_contacts extends rcube_addressbook // copy values into vcard object $vcard = new rcube_vcard($record['vcard'] ? $record['vcard'] : $save_data['vcard'], RCUBE_CHARSET, false, $this->vcard_fieldmap); $vcard->reset(); + + // don't store groups in vCard (#1490277) + $vcard->set('groups', null); + unset($save_data['groups']); + foreach ($save_data as $key => $values) { list($field, $section) = explode(':', $key); $fulltext = in_array($field, $this->fulltext_cols); diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index ab7058f2f..4ccc59ba3 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -357,7 +357,7 @@ class rcube_db public function get_variable($varname, $default = null) { // to be implemented by driver class - return $default; + return rcube::get_instance()->config->get('db_' . $varname, $default); } /** @@ -448,10 +448,15 @@ class rcube_db } } - // replace escaped '?' back to normal, see self::quote() - $query = str_replace('??', '?', $query); $query = rtrim($query, " \t\n\r\0\x0B;"); + // replace escaped '?' and quotes back to normal, see self::quote() + $query = str_replace( + array('??', self::DEFAULT_QUOTE.self::DEFAULT_QUOTE), + array('?', self::DEFAULT_QUOTE), + $query + ); + // log query $this->debug($query); @@ -516,9 +521,6 @@ class rcube_db } } - // replace escaped quote back to normal, see self::quote() - $query = str_replace($quote.$quote, $quote, $query); - return $query; } @@ -689,14 +691,11 @@ class rcube_db { // get tables if not cached if ($this->tables === null) { - $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME'); + $q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES" + . " WHERE TABLE_TYPE = 'BASE TABLE'" + . " ORDER BY TABLE_NAME"); - if ($q) { - $this->tables = $q->fetchAll(PDO::FETCH_COLUMN, 0); - } - else { - $this->tables = array(); - } + $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array(); } return $this->tables; diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php index 067e94be6..616d1752b 100644 --- a/program/lib/Roundcube/rcube_db_mysql.php +++ b/program/lib/Roundcube/rcube_db_mysql.php @@ -150,6 +150,30 @@ class rcube_db_mysql extends rcube_db } /** + * Returns list of tables in a database + * + * @return array List of all tables of the current database + */ + public function list_tables() + { + // get tables if not cached + if ($this->tables === null) { + // first fetch current database name + $d = $this->query("SELECT database()"); + $d = $this->fetch_array($d); + + // get list of tables in current database + $q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES" + . " WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'" + . " ORDER BY TABLE_NAME", $d ? $d[0] : ''); + + $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array(); + } + + return $this->tables; + } + + /** * Get database runtime variables * * @param string $varname Variable name @@ -167,6 +191,12 @@ class rcube_db_mysql extends rcube_db return $this->variables[$varname]; } + // configured value has higher prio + $conf_value = rcube::get_instance()->config->get('db_' . $varname); + if ($conf_value !== null) { + return $this->variables[$varname] = $conf_value; + } + $result = $this->query('SHOW VARIABLES LIKE ?', $varname); while ($row = $this->fetch_array($result)) { diff --git a/program/lib/Roundcube/rcube_db_oracle.php b/program/lib/Roundcube/rcube_db_oracle.php index 34e4e69f8..bb033884c 100644 --- a/program/lib/Roundcube/rcube_db_oracle.php +++ b/program/lib/Roundcube/rcube_db_oracle.php @@ -155,10 +155,15 @@ class rcube_db_oracle extends rcube_db } } - // replace escaped '?' back to normal, see self::quote() - $query = str_replace('??', '?', $query); $query = rtrim($query, " \t\n\r\0\x0B;"); + // replace escaped '?' and quotes back to normal, see self::quote() + $query = str_replace( + array('??', self::DEFAULT_QUOTE.self::DEFAULT_QUOTE), + array('?', self::DEFAULT_QUOTE), + $query + ); + // log query $this->debug($query); diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php index d33cdd859..b4255513b 100644 --- a/program/lib/Roundcube/rcube_db_pgsql.php +++ b/program/lib/Roundcube/rcube_db_pgsql.php @@ -139,9 +139,11 @@ class rcube_db_pgsql extends rcube_db // There's a known case when max_allowed_packet is queried // PostgreSQL doesn't have such limit, return immediately if ($varname == 'max_allowed_packet') { - return $default; + return rcube::get_instance()->config->get('db_' . $varname, $default); } + $this->variables[$varname] = rcube::get_instance()->config->get('db_' . $varname); + if (!isset($this->variables)) { $this->variables = array(); @@ -156,6 +158,25 @@ class rcube_db_pgsql extends rcube_db } /** + * Returns list of tables in a database + * + * @return array List of all tables of the current database + */ + public function list_tables() + { + // get tables if not cached + if ($this->tables === null) { + $q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES" + . " WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('pg_catalog', 'information_schema')" + . " ORDER BY TABLE_NAME"); + + $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array(); + } + + return $this->tables; + } + + /** * Returns PDO DSN string from DSN array * * @param array $dsn DSN parameters diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php index 284e50dca..00a59a7f3 100644 --- a/program/lib/Roundcube/rcube_html2text.php +++ b/program/lib/Roundcube/rcube_html2text.php @@ -142,7 +142,7 @@ class rcube_html2text '/<script[^>]*>.*?<\/script>/i', // <script>s -- which strip_tags supposedly has problems with '/<style[^>]*>.*?<\/style>/i', // <style>s -- which strip_tags supposedly has problems with '/<p[^>]*>/i', // <P> - '/<br[^>]*>/i', // <br> + '/<br[^>]*>\s*/i', // <br> '/<i[^>]*>(.*?)<\/i>/i', // <i> '/<em[^>]*>(.*?)<\/em>/i', // <em> '/(<ul[^>]*>|<\/ul>)/i', // <ul> and </ul> diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index d17b33f6e..65e095099 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1957,6 +1957,16 @@ class rcube_imap extends rcube_storage for ($i=1; $i<count($part); $i++) { if (!is_array($part[$i])) { $struct->ctype_secondary = strtolower($part[$i]); + + // read content type parameters + if (is_array($part[$i+1])) { + $struct->ctype_parameters = array(); + for ($j=0; $j<count($part[$i+1]); $j+=2) { + $param = strtolower($part[$i+1][$j]); + $struct->ctype_parameters[$param] = $part[$i+1][$j+1]; + } + } + break; } } @@ -2364,36 +2374,38 @@ class rcube_imap extends rcube_storage /** * Returns the whole message source as string (or saves to a file) * - * @param int $uid Message UID - * @param resource $fp File pointer to save the message + * @param int $uid Message UID + * @param resource $fp File pointer to save the message + * @param string $part Optional message part ID * * @return string Message source string */ - public function get_raw_body($uid, $fp=null) + public function get_raw_body($uid, $fp=null, $part = null) { if (!$this->check_connection()) { return null; } return $this->conn->handlePartBody($this->folder, $uid, - true, null, null, false, $fp); + true, $part, null, false, $fp); } /** * Returns the message headers as string * - * @param int $uid Message UID + * @param int $uid Message UID + * @param string $part Optional message part ID * * @return string Message headers string */ - public function get_raw_headers($uid) + public function get_raw_headers($uid, $part = null) { if (!$this->check_connection()) { return null; } - return $this->conn->fetchPartHeader($this->folder, $uid, true); + return $this->conn->fetchPartHeader($this->folder, $uid, true, $part); } diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 0058bf487..ad7f2db27 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -48,8 +48,6 @@ class rcube_imap_generic '*' => '\\*', ); - public static $mupdate; - protected $fp; protected $host; protected $logged = false; @@ -3271,11 +3269,6 @@ class rcube_imap_generic } foreach ($data as $entry) { - // Workaround cyrus-murder bug, the entry[2] string needs to be escaped - if (self::$mupdate) { - $entry[2] = addcslashes($entry[2], '\\"'); - } - // ANNOTATEMORE drafts before version 08 require quoted parameters $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true), $this->escape($entry[1], true), $this->escape($entry[2], true)); @@ -3840,10 +3833,6 @@ class rcube_imap_generic $this->prefs['literal+'] = true; } - if (preg_match('/(\[| )MUPDATE=.*/', $str)) { - self::$mupdate = true; - } - if ($trusted) { $this->capability_readed = true; } diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 13d55bde6..f492111cc 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -698,8 +698,9 @@ class rcube_ldap extends rcube_addressbook for ($i=0; $i < $entry['memberurl']['count']; $i++) { // extract components from url - if (!preg_match('!ldap:///([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m)) + if (!preg_match('!ldap://[^/]*/([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m)) { continue; + } // add search filter if any $filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3]; @@ -791,33 +792,24 @@ class rcube_ldap extends rcube_addressbook return $this->result; } - // use AND operator for advanced searches - $filter = is_array($value) ? '(&' : '(|'; - // set wildcards - $wp = $ws = ''; - if (!empty($this->prop['fuzzy_search']) && $mode != 1) { - $ws = '*'; - if (!$mode) { - $wp = '*'; - } - } + // advanced per-attribute search + if (is_array($value)) { + // use AND operator for advanced searches + $filter = '(&'; - if ($fields == '*') { - // search_fields are required for fulltext search - if (empty($this->prop['search_fields'])) { - $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch'); - $this->result = new rcube_result_set(); - return $this->result; - } - if (is_array($this->prop['search_fields'])) { - foreach ($this->prop['search_fields'] as $field) { - $filter .= "($field=$wp" . rcube_ldap_generic::quote_string($value) . "$ws)"; + // set wildcards + $wp = $ws = ''; + if (!empty($this->prop['fuzzy_search']) && $mode != 1) { + $ws = '*'; + if (!$mode) { + $wp = '*'; } } - } - else { + foreach ((array)$fields as $idx => $field) { - $val = is_array($value) ? $value[$idx] : $value; + $val = $value[$idx]; + if (!strlen($val)) + continue; if ($attrs = $this->_map_field($field)) { if (count($attrs) > 1) $filter .= '(|'; @@ -827,8 +819,33 @@ class rcube_ldap extends rcube_addressbook $filter .= ')'; } } + + $filter .= ')'; + } + else { + if ($fields == '*') { + // search_fields are required for fulltext search + if (empty($this->prop['search_fields'])) { + $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch'); + $this->result = new rcube_result_set(); + return $this->result; + } + $attributes = (array)$this->prop['search_fields']; + } + else { + // map address book fields into ldap attributes + $me = $this; + $attributes = array(); + array_walk($fields, function($field) use ($me, &$attributes) { + if ($me->coltypes[$field] && ($attrs = (array)$me->coltypes[$field]['attributes'])) { + $attributes = array_merge($attributes, $attrs); + } + }); + } + + // compose a full-text-like search filter + $filter = rcube_ldap_generic::fulltext_search_filter($value, $attributes, $mode); } - $filter .= ')'; // add required (non empty) fields filter $req_filter = ''; diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php index a76ad6d06..abe16760d 100644 --- a/program/lib/Roundcube/rcube_ldap_generic.php +++ b/program/lib/Roundcube/rcube_ldap_generic.php @@ -6,7 +6,7 @@ | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2006-2014, The Roundcube Dev Team | - | Copyright (C) 2012-2014, Kolab Systems AG | + | Copyright (C) 2012-2015, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -316,6 +316,47 @@ class rcube_ldap_generic extends Net_LDAP3 return $rec; } + + /** + * Compose an LDAP filter string matching all words from the search string + * in the given list of attributes. + * + * @param string $value Search value + * @param mixed $attrs List of LDAP attributes to search + * @param int $mode Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * @return string LDAP filter + */ + public static function fulltext_search_filter($value, $attributes, $mode = 1) + { + if (empty($attributes)) { + $attributes = array('cn'); + } + + $groups = array(); + $value = str_replace('*', '', $value); + $words = $mode == 0 ? rcube_utils::tokenize_string($value, 1) : array($value); + + // set wildcards + $wp = $ws = ''; + if ($mode != 1) { + $ws = '*'; + $wp = !$mode ? '*' : ''; + } + + // search each word in all listed attributes + foreach ($words as $word) { + $parts = array(); + foreach ($attributes as $attr) { + $parts[] = "($attr=$wp" . self::quote_string($word) . "$ws)"; + } + $groups[] = '(|' . join('', $parts) . ')'; + } + + return count($groups) > 1 ? '(&' . join('', $groups) . ')' : join('', $groups); + } } // for backward compat. diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index b076f95bf..978e6be82 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -53,13 +53,13 @@ class rcube_message public $uid; public $folder; public $headers; - public $parts = array(); - public $mime_parts = array(); + public $sender; + public $parts = array(); + public $mime_parts = array(); public $inline_parts = array(); - public $attachments = array(); - public $subject = ''; - public $sender = null; - public $is_safe = false; + public $attachments = array(); + public $subject = ''; + public $is_safe = false; const BODY_MAX_SIZE = 1048576; // 1MB @@ -218,6 +218,10 @@ class rcube_message return; } + // allow plugins to modify part body + $plugin = $this->app->plugins->exec_hook('message_part_body', + array('object' => $this, 'part' => $part)); + // only text parts can be formatted $formatted = $formatted && $part->ctype_primary == 'text'; @@ -500,8 +504,9 @@ class rcube_message $structure->headers = rcube_mime::parse_headers($headers); } } - else + else { $mimetype = $structure->mimetype; + } // show message headers if ($recursive && is_array($structure->headers) && @@ -517,11 +522,15 @@ class rcube_message array('object' => $this, 'structure' => $structure, 'mimetype' => $mimetype, 'recursive' => $recursive)); - if ($plugin['abort']) + if ($plugin['abort']) { return; + } $structure = $plugin['structure']; - list($message_ctype_primary, $message_ctype_secondary) = explode('/', $plugin['mimetype']); + $mimetype = $plugin['mimetype']; + $recursive = $plugin['recursive']; + + list($message_ctype_primary, $message_ctype_secondary) = explode('/', $mimetype); // print body if message doesn't have multiple parts if ($message_ctype_primary == 'text' && !$recursive) { @@ -571,8 +580,10 @@ class rcube_message $related_part = $p; else if ($sub_mimetype == 'text/plain' && !$plain_part) $plain_part = $p; - else if ($sub_mimetype == 'text/html' && !$html_part) + else if ($sub_mimetype == 'text/html' && !$html_part) { $html_part = $p; + $this->got_html_part = true; + } else if ($sub_mimetype == 'text/enriched' && !$enriched_part) $enriched_part = $p; else { @@ -674,7 +685,7 @@ class rcube_message } else { $part_mimetype = $part_orig_mimetype = $mail_part->mimetype; - } + } // multipart/alternative if ($primary_type == 'multipart') { @@ -697,7 +708,7 @@ class rcube_message continue; if ($part_mimetype == 'text/html' && $mail_part->size) { - $got_html_part = true; + $this->got_html_part = true; } $mail_part = $plugin['structure']; @@ -731,10 +742,17 @@ class rcube_message } // part is Microsoft Outlook TNEF (winmail.dat) else if ($part_mimetype == 'application/ms-tnef') { - foreach ((array)$this->tnef_decode($mail_part) as $tpart) { + $tnef_parts = (array) $this->tnef_decode($mail_part); + foreach ($tnef_parts as $tpart) { $this->mime_parts[$tpart->mime_id] = $tpart; $this->attachments[] = $tpart; } + + // add winmail.dat to the list if it's content is unknown + if (empty($tnef_parts) && !empty($mail_part->filename)) { + $this->mime_parts[$mail_part->mime_id] = $mail_part; + $this->attachments[] = $mail_part; + } } // part is a file/attachment else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || @@ -783,6 +801,14 @@ class rcube_message else if ($mail_part->mimetype == 'message/rfc822') { $this->parse_structure($mail_part); } + // calendar part not marked as attachment (#1490325) + else if ($part_mimetype == 'text/calendar') { + if (!$mail_part->filename) { + $mail_part->filename = 'calendar.ics'; + } + + $this->attachments[] = $mail_part; + } } // if this was a related part try to resolve references @@ -802,7 +828,7 @@ class rcube_message // MS Outlook sends sometimes non-related attachments as related // In this case multipart/related message has only one text part // We'll add all such attachments to the attachments list - if (!isset($got_html_part) && empty($inline_object->content_id)) { + if (!isset($this->got_html_part)) { $this->attachments[] = $inline_object; } // MS Outlook sometimes also adds non-image attachments as related diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 24535dd84..de764f4b5 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -227,7 +227,7 @@ class rcube_message_header if ($decode) { if (is_array($value)) { foreach ($value as $key => $val) { - $value[$key] = rcube_mime::decode_header($val, $this->charset); + $val = rcube_mime::decode_header($val, $this->charset); $value[$key] = rcube_charset::clean($val); } } diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index 8fd3253e0..ce727e2e7 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -43,6 +43,7 @@ class rcube_plugin_api public $active_plugins = array(); protected $plugins = array(); + protected $plugins_initialized = array(); protected $tasks = array(); protected $actions = array(); protected $actionmap = array(); @@ -94,8 +95,9 @@ class rcube_plugin_api foreach ($this->plugins as $plugin) { // ... task, request type and framed mode - if (!$this->filter($plugin)) { + if (!$this->plugins_initialized[$plugin_name] && !$this->filter($plugin)) { $plugin->init(); + $this->plugins_initialized[$plugin->ID] = $plugin; } } @@ -146,7 +148,7 @@ class rcube_plugin_api /** * Load the specified plugin * - * @param string Plugin name + * @param string Plugin name * @param boolean Force loading of the plugin even if it doesn't match the filter * @param boolean Require loading of the plugin, error if it doesn't exist * @@ -161,63 +163,62 @@ class rcube_plugin_api $plugins_dir = unslashify($dir->path); } - // plugin already loaded - if ($this->plugins[$plugin_name]) { - return true; - } + // plugin already loaded? + if (!$this->plugins[$plugin_name]) { + $fn = "$plugins_dir/$plugin_name/$plugin_name.php"; + + if (!is_readable($fn)) { + if ($require) { + rcube::raise_error(array('code' => 520, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Failed to load plugin file $fn"), true, false); + } - $fn = "$plugins_dir/$plugin_name/$plugin_name.php"; + return false; + } - if (is_readable($fn)) { if (!class_exists($plugin_name, false)) { include $fn; } // instantiate class if exists - if (class_exists($plugin_name, false)) { - $plugin = new $plugin_name($this); - $this->active_plugins[] = $plugin_name; - - // check inheritance... - if (is_subclass_of($plugin, 'rcube_plugin')) { - // ... task, request type and framed mode - - // call onload method on plugin if it exists. - // this is useful if you want to be called early in the boot process - if (method_exists($plugin, 'onload')) { - $plugin->onload(); - } + if (!class_exists($plugin_name, false)) { + rcube::raise_error(array('code' => 520, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "No plugin class $plugin_name found in $fn"), + true, false); - // init a plugin only if $force is set or if we're called after initialization - if (($force || $this->initialized) - && !$this->filter($plugin)) - { - $plugin->init(); - } + return false; + } - $this->plugins[$plugin_name] = $plugin; + $plugin = new $plugin_name($this); + $this->active_plugins[] = $plugin_name; - if (!empty($plugin->allowed_prefs)) { - $this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs); - } + // check inheritance... + if (is_subclass_of($plugin, 'rcube_plugin')) { + // call onload method on plugin if it exists. + // this is useful if you want to be called early in the boot process + if (method_exists($plugin, 'onload')) { + $plugin->onload(); + } - return true; + if (!empty($plugin->allowed_prefs)) { + $this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs); } - } - else { - rcube::raise_error(array('code' => 520, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "No plugin class $plugin_name found in $fn"), - true, false); + + $this->plugins[$plugin_name] = $plugin; } } - else if ($require) { - rcube::raise_error(array('code' => 520, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Failed to load plugin file $fn"), true, false); + + if ($plugin = $this->plugins[$plugin_name]) { + // init a plugin only if $force is set or if we're called after initialization + if (($force || $this->initialized) && !$this->plugins_initialized[$plugin_name] && ($force || !$this->filter($plugin))) { + $plugin->init(); + $this->plugins_initialized[$plugin_name] = $plugin; + } } - return false; + return true; } /** @@ -228,9 +229,9 @@ class rcube_plugin_api */ private function filter($plugin) { - return (($plugin->noajax && !(is_object($this->output) && $this->output->type == 'html') ) + return ($plugin->noajax && !(is_object($this->output) && $this->output->type == 'html')) || ($plugin->task && !preg_match('/^('.$plugin->task.')$/i', $this->task)) - || ($plugin->noframe && !empty($_REQUEST['_framed']))) ? true : false; + || ($plugin->noframe && !empty($_REQUEST['_framed'])); } /** diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index 8306a0687..ab5c24c1e 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -15,37 +15,36 @@ +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | | Author: Aleksander Machniak <alec@alec.pl> | + | Author: Cor Bosman <cor@roundcu.be> | +-----------------------------------------------------------------------+ */ /** - * Class to provide database supported session storage + * Abstract class to provide database supported session storage * * @package Framework * @subpackage Core * @author Thomas Bruederli <roundcube@gmail.com> * @author Aleksander Machniak <alec@alec.pl> */ -class rcube_session +abstract class rcube_session { - private $db; - private $ip; - private $start; - private $changed; - private $time_diff = 0; - private $reloaded = false; - private $appends = array(); - private $unsets = array(); - private $gc_handlers = array(); - private $cookiename = 'roundcube_sessauth'; - private $vars; - private $key; - private $now; - private $secret = ''; - private $ip_check = false; - private $logging = false; - private $storage; - private $memcache; + protected $key; + protected $ip; + protected $changed; + protected $start; + protected $time_diff = 0; + protected $reloaded = false; + protected $appends = array(); + protected $unsets = array(); + protected $gc_handlers = array(); + protected $cookiename = 'roundcube_sessauth'; + protected $vars; + protected $now; + protected $secret = ''; + protected $ip_check = false; + protected $logging = false; + protected $config; /** * Blocks session data from being written to database. @@ -54,203 +53,140 @@ class rcube_session */ public $nowrite = false; - /** - * Default constructor + * Factory, returns driver-specific instance of the class + * + * @param object $config + * @return Object rcube_session */ - public function __construct($db, $config) + public static function factory($config) { - $this->db = $db; - $this->start = microtime(true); - $this->ip = rcube_utils::remote_addr(); - $this->logging = $config->get('log_session', false); + // get session storage driver + $storage = $config->get('session_storage', 'db'); - $lifetime = $config->get('session_lifetime', 1) * 60; - $this->set_lifetime($lifetime); + // class name for this storage + $class = "rcube_session_" . $storage; - // use memcache backend - $this->storage = $config->get('session_storage', 'db'); - if ($this->storage == 'memcache') { - $this->memcache = rcube::get_instance()->get_memcache(); - - // set custom functions for PHP session management if memcache is available - if ($this->memcache) { - ini_set('session.serialize_handler', 'php'); - - session_set_save_handler( - array($this, 'open'), - array($this, 'close'), - array($this, 'mc_read'), - array($this, 'mc_write'), - array($this, 'mc_destroy'), - array($this, 'gc')); - } - else { - rcube::raise_error(array('code' => 604, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => "Failed to connect to memcached. Please check configuration"), - true, true); - } - } - else if ($this->storage != 'php') { - ini_set('session.serialize_handler', 'php'); - - // set custom functions for PHP session management - session_set_save_handler( - array($this, 'open'), - array($this, 'close'), - array($this, 'db_read'), - array($this, 'db_write'), - array($this, 'db_destroy'), - array($this, 'gc')); - - $this->table_name = $this->db->table_name('session', true); + // try to instantiate class + if (class_exists($class)) { + return new $class($config); } - } + // no storage found, raise error + rcube::raise_error(array('code' => 604, 'type' => 'session', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Failed to find session driver. Check session_storage config option"), + true, true); + } /** - * Wrapper for session_start() + * @param Object $config */ - public function start() + public function __construct($config) { - session_start(); - - // copy some session properties to object vars - if ($this->storage == 'php') { - $this->key = session_id(); - $this->ip = $_SESSION['__IP']; - $this->changed = $_SESSION['__MTIME']; - } - } - + $this->config = $config; - public function open($save_path, $session_name) - { - return true; - } + // set secret + $this->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); + // set ip check + $this->set_ip_check($this->config->get('ip_check')); - public function close() - { - return true; + // set cookie name + if ($this->config->get('session_auth_name')) { + $this->set_cookiename($this->config->get('session_auth_name')); + } } - /** - * Delete session data for the given key - * - * @param string Session ID + * register session handler */ - public function destroy($key) + public function register_session_handler() { - return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key); + ini_set('session.serialize_handler', 'php'); + + // set custom functions for PHP session management + session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'read'), + array($this, 'sess_write'), + array($this, 'destroy'), + array($this, 'gc') + ); } /** - * Wrapper for session_write_close() + * Wrapper for session_start() */ - public function write_close() + public function start() { - if ($this->storage == 'php') { - $_SESSION['__IP'] = $this->ip; - $_SESSION['__MTIME'] = time(); - } + $this->start = microtime(true); + $this->ip = rcube_utils::remote_addr(); + $this->logging = $this->config->get('log_session', false); - session_write_close(); + $lifetime = $this->config->get('session_lifetime', 1) * 60; + $this->set_lifetime($lifetime); - // write_close() is called on script shutdown, see rcube::shutdown() - // execute cleanup functionality if enabled by session gc handler - // we do this after closing the session for better performance - $this->gc_shutdown(); + session_start(); } - /** - * Read session data from database - * - * @param string Session ID - * - * @return string Session vars + * Abstract methods should be implemented by driver classes */ - public function db_read($key) - { - $sql_result = $this->db->query( - "SELECT `vars`, `ip`, `changed`, " . $this->db->now() . " AS ts" - . " FROM {$this->table_name} WHERE `sess_id` = ?", $key); - - if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { - $this->time_diff = time() - strtotime($sql_arr['ts']); - $this->changed = strtotime($sql_arr['changed']); - $this->ip = $sql_arr['ip']; - $this->vars = base64_decode($sql_arr['vars']); - $this->key = $key; - - return !empty($this->vars) ? (string) $this->vars : ''; - } - - return null; - } + abstract function open($save_path, $session_name); + abstract function close(); + abstract function destroy($key); + abstract function read($key); + abstract function write($key, $vars); + abstract function update($key, $newvars, $oldvars); /** - * Save session data. - * handler for session_read() + * session write handler. This calls the implementation methods for write/update after some initial checks. * - * @param string Session ID - * @param string Serialized session vars - * - * @return boolean True on success + * @param $key + * @param $vars + * @return bool */ - public function db_write($key, $vars) + public function sess_write($key, $vars) { - $now = $this->db->now(); - $ts = microtime(true); - - if ($this->nowrite) + if ($this->nowrite) { return true; - - // no session row in DB (db_read() returns false) - if (!$this->key) { - $oldvars = null; - } - // use internal data from read() for fast requests (up to 0.5 sec.) - else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) { - $oldvars = $this->vars; - } - else { // else read data again from DB - $oldvars = $this->db_read($key); } + // check cache + $oldvars = $this->get_cache($key); + + // if there are cached vars, update store, else insert new data if ($oldvars !== null) { $newvars = $this->_fixvars($vars, $oldvars); - - if ($newvars !== $oldvars) { - $this->db->query("UPDATE {$this->table_name} " - . "SET `changed` = $now, `vars` = ? WHERE `sess_id` = ?", - base64_encode($newvars), $key); - } - else if ($ts - $this->changed + $this->time_diff > $this->lifetime / 2) { - $this->db->query("UPDATE {$this->table_name} SET `changed` = $now" - . " WHERE `sess_id` = ?", $key); - } + return $this->update($key, $newvars, $oldvars); } else { - $this->db->query("INSERT INTO {$this->table_name}" - . " (`sess_id`, `vars`, `ip`, `created`, `changed`)" - . " VALUES (?, ?, ?, $now, $now)", - $key, base64_encode($vars), (string)$this->ip); + return $this->write($key, $vars); } - - return true; } /** + * Wrapper for session_write_close() + */ + public function write_close() + { + session_write_close(); + + // write_close() is called on script shutdown, see rcube::shutdown() + // execute cleanup functionality if enabled by session gc handler + // we do this after closing the session for better performance + $this->gc_shutdown(); + } + + /** * Merge vars with old vars and apply unsets */ - private function _fixvars($vars, $oldvars) + protected function _fixvars($vars, $oldvars) { if ($oldvars !== null) { $a_oldvars = $this->unserialize($oldvars); @@ -280,97 +216,6 @@ class rcube_session return $newvars; } - - /** - * Handler for session_destroy() - * - * @param string Session ID - * - * @return boolean True on success - */ - public function db_destroy($key) - { - if ($key) { - $this->db->query("DELETE FROM {$this->table_name} WHERE `sess_id` = ?", $key); - } - - return true; - } - - - /** - * Read session data from memcache - * - * @param string Session ID - * @return string Session vars - */ - public function mc_read($key) - { - if ($value = $this->memcache->get($key)) { - $arr = unserialize($value); - $this->changed = $arr['changed']; - $this->ip = $arr['ip']; - $this->vars = $arr['vars']; - $this->key = $key; - - return !empty($this->vars) ? (string) $this->vars : ''; - } - - return null; - } - - - /** - * Save session data. - * handler for session_read() - * - * @param string Session ID - * @param string Serialized session vars - * - * @return boolean True on success - */ - public function mc_write($key, $vars) - { - $ts = microtime(true); - - // no session data in cache (mc_read() returns false) - if (!$this->key) - $oldvars = null; - // use internal data for fast requests (up to 0.5 sec.) - else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) - $oldvars = $this->vars; - else // else read data again - $oldvars = $this->mc_read($key); - - $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars; - - if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) { - return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), - MEMCACHE_COMPRESSED, $this->lifetime + 60); - } - - return true; - } - - - /** - * Handler for session_destroy() with memcache backend - * - * @param string Session ID - * - * @return boolean True on success - */ - public function mc_destroy($key) - { - if ($key) { - // #1488592: use 2nd argument - $this->memcache->delete($key, 0); - } - - return true; - } - - /** * Execute registered garbage collector routines */ @@ -381,7 +226,6 @@ class rcube_session return $this->gc_enabled = $maxlifetime; } - /** * Register additional garbage collector functions * @@ -405,12 +249,6 @@ class rcube_session protected function gc_shutdown() { if ($this->gc_enabled) { - // just delete all expired sessions - if ($this->storage == 'db') { - $this->db->query("DELETE FROM {$this->table_name}" - . " WHERE `changed` < " . $this->db->now(-$this->gc_enabled)); - } - foreach ($this->gc_handlers as $fct) { call_user_func($fct); } @@ -422,6 +260,7 @@ class rcube_session * Generate and set new session id * * @param boolean $destroy If enabled the current session will be destroyed + * @return bool */ public function regenerate_id($destroy=true) { @@ -433,6 +272,28 @@ class rcube_session return true; } + /** + * see if we have vars of this key already cached, and if so, return them. + * + * @param $key + * @return null|array + */ + protected function get_cache($key) + { + // no session data in cache (read() returns false) + if (!$this->key) { + $cache = null; + } + // use internal data for fast requests (up to 0.5 sec.) + else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) { + $cache = $this->vars; + } + else { // else read data again + $cache = $this->read($key); + } + return $cache; + } + /** * Append the given value to the certain node in the session data array @@ -523,10 +384,9 @@ class rcube_session $node[$k] = $value; } - if ($this->key && $this->memcache) - $data = $this->mc_read($this->key); - else if ($this->key) - $data = $this->db_read($this->key); + if($this->key) { + $data = $this->read($this->key); + } if ($data) { session_decode($data); @@ -553,7 +413,7 @@ class rcube_session * Returns a reference to the node in data array referenced by the given path. * e.g. ['compose','attachments'] will return $_SESSION['compose']['attachments'] */ - private function &get_node($path, &$data_arr) + protected function &get_node($path, &$data_arr) { $node = &$data_arr; if (!empty($path)) { @@ -570,7 +430,7 @@ class rcube_session /** * Serialize session data */ - private function serialize($vars) + protected function serialize($vars) { $data = ''; if (is_array($vars)) { @@ -589,7 +449,7 @@ class rcube_session * Unserialize session data * http://www.php.net/manual/en/function.session-decode.php#56106 */ - private function unserialize($str) + protected function unserialize($str) { $str = (string)$str; $endptr = strlen($str); @@ -788,6 +648,7 @@ class rcube_session * Create session cookie from session data * * @param int Time slot to use + * @return string */ function _mkcookie($timeslot) { diff --git a/program/lib/Roundcube/rcube_session_db.php b/program/lib/Roundcube/rcube_session_db.php new file mode 100644 index 000000000..a814c2b9b --- /dev/null +++ b/program/lib/Roundcube/rcube_session_db.php @@ -0,0 +1,175 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2014, 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: | + | Provide database supported session management | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + | Author: Cor Bosman <cor@roundcu.be> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Class to provide database session storage + * + * @package Framework + * @subpackage Core + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + * @author Cor Bosman <cor@roundcu.be> + */ +class rcube_session_db extends rcube_session +{ + private $db; + private $table_name; + + /** + * @param Object $config + */ + public function __construct($config) + { + parent::__construct($config); + + // get db instance + $this->db = rcube::get_instance()->get_dbh(); + + // session table name + $this->table_name = $this->db->table_name('session', true); + + // register sessions handler + $this->register_session_handler(); + + // register db gc handler + $this->register_gc_handler(array($this, 'gc_db')); + } + + /** + * @param $save_path + * @param $session_name + * @return bool + */ + public function open($save_path, $session_name) + { + return true; + } + + /** + * @return bool + */ + public function close() + { + return true; + } + + + /** + * Handler for session_destroy() + * + * @param $key + * @return bool + */ + public function destroy($key) + { + if ($key) { + $this->db->query("DELETE FROM {$this->table_name} WHERE `sess_id` = ?", $key); + } + + return true; + } + + /** + * Read session data from database + * + * @param string Session ID + * + * @return string Session vars + */ + public function read($key) + { + $sql_result = $this->db->query( + "SELECT `vars`, `ip`, `changed`, " . $this->db->now() . " AS ts" + . " FROM {$this->table_name} WHERE `sess_id` = ?", $key); + + if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { + $this->time_diff = time() - strtotime($sql_arr['ts']); + $this->changed = strtotime($sql_arr['changed']); + $this->ip = $sql_arr['ip']; + $this->vars = base64_decode($sql_arr['vars']); + $this->key = $key; + + return !empty($this->vars) ? (string) $this->vars : ''; + } + return null; + } + + /** + * insert new data into db session store + * + * @param $key + * @param $vars + * @return bool + */ + public function write($key, $vars) + { + $now = $this->db->now(); + + $this->db->query("INSERT INTO {$this->table_name}" + . " (`sess_id`, `vars`, `ip`, `created`, `changed`)" + . " VALUES (?, ?, ?, $now, $now)", + $key, base64_encode($vars), (string)$this->ip); + + return true; + } + + + /** + * update session data + * + * @param $key + * @param $newvars + * @param $oldvars + * + * @return bool + */ + public function update($key, $newvars, $oldvars) + { + $now = $this->db->now(); + $ts = microtime(true); + + // if new and old data are not the same, update data + // else update expire timestamp only when certain conditions are met + if ($newvars !== $oldvars) { + $this->db->query("UPDATE {$this->table_name} " + . "SET `changed` = $now, `vars` = ? WHERE `sess_id` = ?", + base64_encode($newvars), $key); + } + else if ($ts - $this->changed + $this->time_diff > $this->lifetime / 2) { + $this->db->query("UPDATE {$this->table_name} SET `changed` = $now" + . " WHERE `sess_id` = ?", $key); + } + + return true; + } + + /** + * Clean up db sessions. + */ + public function gc_db() + { + // just clean all old sessions when this GC is called + $this->db->query("DELETE FROM " . $this->db->table_name('session') + . " WHERE changed < " . $this->db->now(-$this->gc_enabled)); + $this->log("Session GC (DB): remove records < " . date('Y-m-d H:i:s', time() - $this->gc_enabled) . '; rows = ' . intval($this->db->affected_rows())); + } + +}
\ No newline at end of file diff --git a/program/lib/Roundcube/rcube_session_memcache.php b/program/lib/Roundcube/rcube_session_memcache.php new file mode 100644 index 000000000..732d5fb7a --- /dev/null +++ b/program/lib/Roundcube/rcube_session_memcache.php @@ -0,0 +1,144 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2014, 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: | + | Provide database supported session management | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + | Author: Cor Bosman <cor@roundcu.bet> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Class to provide memcache session storage + * + * @package Framework + * @subpackage Core + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + * @author Cor Bosman <cor@roundcu.be> + */ +class rcube_session_memcache extends rcube_session +{ + private $memcache; + + /** + * @param Object $config + */ + public function __construct($config) + { + parent::__construct($config); + + $this->memcache = rcube::get_instance()->get_memcache(); + + if (!$this->memcache) { + rcube::raise_error(array('code' => 604, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Failed to connect to memcached. Please check configuration"), + true, true); + } + + // register sessions handler + $this->register_session_handler(); + } + + /** + * @param $save_path + * @param $session_name + * @return bool + */ + public function open($save_path, $session_name) + { + return true; + } + + /** + * @return bool + */ + public function close() + { + return true; + } + + /** + * Handler for session_destroy() with memcache backend + * + * @param $key + * @return bool + */ + public function destroy($key) + { + if ($key) { + // #1488592: use 2nd argument + $this->memcache->delete($key, 0); + } + + return true; + } + + + /** + * Read session data from memcache + * + * @param $key + * @return null|string + */ + public function read($key) + { + if ($value = $this->memcache->get($key)) { + $arr = unserialize($value); + $this->changed = $arr['changed']; + $this->ip = $arr['ip']; + $this->vars = $arr['vars']; + $this->key = $key; + + return !empty($this->vars) ? (string) $this->vars : ''; + } + + return null; + } + + /** + * write data to memcache storage + * + * @param $key + * @param $vars + * @return bool + */ + public function write($key, $vars) + { + return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $vars)), + MEMCACHE_COMPRESSED, $this->lifetime + 60); + } + + /** + * update memcache session data + * + * @param $key + * @param $newvars + * @param $oldvars + * @return bool + */ + public function update($key, $newvars, $oldvars) + { + $ts = microtime(true); + + if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) { + return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), + MEMCACHE_COMPRESSED, $this->lifetime + 60); + } + + return true; + } + +}
\ No newline at end of file diff --git a/program/lib/Roundcube/rcube_session_php.php b/program/lib/Roundcube/rcube_session_php.php new file mode 100644 index 000000000..2f7085fc7 --- /dev/null +++ b/program/lib/Roundcube/rcube_session_php.php @@ -0,0 +1,77 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2014, 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: | + | Provide database supported session management | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + | Author: Cor Bosman <cor@roundcu.be> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Class to provide native php session storage + * + * @package Framework + * @subpackage Core + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + * @author Cor Bosman <cor@roundcu.be> + */ +class rcube_session_php extends rcube_session { + + /** + * native php sessions don't need a save handler + * we do need to define abstract function implementations but they are not used. + */ + + public function open($save_path, $session_name) {} + public function close() {} + public function destroy($key) {} + public function read($key) {} + public function write($key, $vars) {} + public function update($key, $newvars, $oldvars) {} + + /** + * @param Object $config + */ + public function __construct($config) + { + parent::__construct($config); + } + + /** + * Wrapper for session_write_close() + */ + public function write_close() + { + $_SESSION['__IP'] = $this->ip; + $_SESSION['__MTIME'] = time(); + + parent::write_close(); + } + + /** + * Wrapper for session_start() + */ + public function start() + { + parent::start(); + + $this->key = session_id(); + $this->ip = $_SESSION['__IP']; + $this->changed = $_SESSION['__MTIME']; + + } + +}
\ No newline at end of file diff --git a/program/lib/Roundcube/rcube_session_redis.php b/program/lib/Roundcube/rcube_session_redis.php new file mode 100644 index 000000000..4822db7f9 --- /dev/null +++ b/program/lib/Roundcube/rcube_session_redis.php @@ -0,0 +1,211 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | 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. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Provide redis supported session management | + +-----------------------------------------------------------------------+ + | Author: Cor Bosman <cor@roundcu.be> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Class to provide redis session storage + * + * @package Framework + * @subpackage Core + * @author Cor Bosman <cor@roundcu.be> + */ +class rcube_session_redis extends rcube_session { + + private $redis; + + /** + * @param Object $config + */ + public function __construct($config) + { + parent::__construct($config); + + // instantiate Redis object + $this->redis = new Redis(); + + if (!$this->redis) { + rcube::raise_error(array('code' => 604, 'type' => 'session', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Failed to find Redis. Make sure php-redis is included"), + true, true); + } + + // get config instance + $hosts = $this->config->get('redis_hosts', array('localhost')); + + // host config is wrong + if (!is_array($hosts) || empty($hosts)) { + rcube::raise_error(array('code' => 604, 'type' => 'session', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Redis host not configured"), + true, true); + } + + // only allow 1 host for now until we support clustering + if (count($hosts) > 1) { + rcube::raise_error(array('code' => 604, 'type' => 'session', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Redis cluster not yet supported"), + true, true); + } + + foreach ($hosts as $host) { + // explode individual fields + list($host, $port, $database, $password) = array_pad(explode(':', $host, 4), 4, null); + + // set default values if not set + $host = ($host !== null) ? $host : '127.0.0.1'; + $port = ($port !== null) ? $port : 6379; + $database = ($database !== null) ? $database : 0; + + if ($this->redis->connect($host, $port) === false) { + rcube::raise_error( + array( + 'code' => 604, + 'type' => 'session', + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => "Could not connect to Redis server. Please check host and port" + ), + true, + true + ); + } + + if ($password != null && $this->redis->auth($password) === false) { + rcube::raise_error( + array( + 'code' => 604, + 'type' => 'session', + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => "Could not authenticate with Redis server. Please check password." + ), + true, + true + ); + } + + if ($database != 0 && $this->redis->select($database) === false) { + rcube::raise_error( + array( + 'code' => 604, + 'type' => 'session', + 'line' => __LINE__, + 'file' => __FILE__, + 'message' => "Could not select Redis database. Please check database setting." + ), + true, + true + ); + } + } + + // register sessions handler + $this->register_session_handler(); + } + + /** + * @param $save_path + * @param $session_name + * @return bool + */ + public function open($save_path, $session_name) + { + return true; + } + + /** + * @return bool + */ + public function close() + { + return true; + } + + /** + * remove data from store + * + * @param $key + * @return bool + */ + public function destroy($key) + { + if ($key) { + $this->redis->del($key); + } + + return true; + } + + + /** + * read data from redis store + * + * @param $key + * @return null + */ + public function read($key) + { + if ($value = $this->redis->get($key)) { + $arr = unserialize($value); + $this->changed = $arr['changed']; + $this->ip = $arr['ip']; + $this->vars = $arr['vars']; + $this->key = $key; + + return !empty($this->vars) ? (string) $this->vars : ''; + } + + return null; + } + + + /** + * write data to redis store + * + * @param $key + * @param $newvars + * @param $oldvars + * @return bool + */ + public function update($key, $newvars, $oldvars) + { + $ts = microtime(true); + + if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) { + $this->redis->setex($key, $this->lifetime + 60, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars))); + } + + return true; + } + + + /** + * write data to redis store + * + * @param $key + * @param $vars + * @return bool + */ + public function write($key, $vars) + { + return $this->redis->setex($key, $this->lifetime + 60, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $vars))); + } + + +}
\ No newline at end of file diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php index c3a51467b..0322a0d46 100644 --- a/program/lib/Roundcube/rcube_smtp.php +++ b/program/lib/Roundcube/rcube_smtp.php @@ -126,7 +126,7 @@ class rcube_smtp // try to connect to server and exit on failure $result = $this->conn->connect($CONFIG['smtp_timeout']); - if (PEAR::isError($result)) { + if (is_a($result, 'PEAR_Error')) { $this->response[] = "Connection failed: ".$result->getMessage(); $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code)); $this->conn = null; @@ -159,7 +159,7 @@ class rcube_smtp $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz); - if (PEAR::isError($result)) { + if (is_a($result, 'PEAR_Error')) { $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code)); $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')'; $this->reset(); @@ -240,7 +240,8 @@ class rcube_smtp } // set From: address - if (PEAR::isError($this->conn->mailFrom($from, $from_params))) { + $result = $this->conn->mailFrom($from, $from_params); + if (is_a($result, 'PEAR_Error')) { $err = $this->conn->getResponse(); $this->error = array('label' => 'smtpfromerror', 'vars' => array( 'from' => $from, 'code' => $err[0], 'msg' => $err[1])); @@ -252,7 +253,7 @@ class rcube_smtp // prepare list of recipients $recipients = $this->_parse_rfc822($recipients); - if (PEAR::isError($recipients)) { + if (is_a($recipients, 'PEAR_Error')) { $this->error = array('label' => 'smtprecipientserror'); $this->reset(); return false; @@ -260,7 +261,8 @@ class rcube_smtp // set mail recipients foreach ($recipients as $recipient) { - if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) { + $result = $this->conn->rcptTo($recipient, $recipient_params); + if (is_a($result, 'PEAR_Error')) { $err = $this->conn->getResponse(); $this->error = array('label' => 'smtptoerror', 'vars' => array( 'to' => $recipient, 'code' => $err[0], 'msg' => $err[1])); @@ -288,7 +290,8 @@ class rcube_smtp } // Send the message's headers and the body as SMTP data. - if (PEAR::isError($this->conn->data($data, $text_headers))) { + $result = $this->conn->data($data, $text_headers); + if (is_a($result, 'PEAR_Error')) { $err = $this->conn->getResponse(); if (!in_array($err[0], array(354, 250, 221))) { $msg = sprintf('[%d] %s', $err[0], $err[1]); diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index ccb28c680..23e65770d 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -492,22 +492,24 @@ abstract class rcube_storage /** * Returns the whole message source as string (or saves to a file) * - * @param int $uid Message UID - * @param resource $fp File pointer to save the message + * @param int $uid Message UID + * @param resource $fp File pointer to save the message + * @param string $part Optional message part ID * * @return string Message source string */ - abstract function get_raw_body($uid, $fp = null); + abstract function get_raw_body($uid, $fp = null, $part = null); /** * Returns the message headers as string * - * @param int $uid Message UID + * @param int $uid Message UID + * @param string $part Optional message part ID * * @return string Message headers string */ - abstract function get_raw_headers($uid); + abstract function get_raw_headers($uid, $part = null); /** diff --git a/program/lib/Roundcube/rcube_text2html.php b/program/lib/Roundcube/rcube_text2html.php index 1981b2f98..2ffe5301d 100644 --- a/program/lib/Roundcube/rcube_text2html.php +++ b/program/lib/Roundcube/rcube_text2html.php @@ -45,18 +45,22 @@ class rcube_text2html */ protected $config = array( // non-breaking space - 'space' => "\xC2\xA0", + 'space' => "\xC2\xA0", + // word-joiner (zero-width no-break space) + // 'wordjoiner' => "\xEF\xBB\xBF", // U+2060 + // use deprecated U+FEFF character because of webkit issue with displaying U+2060 (#1490353) + 'wordjoiner' => "\xEF\xBB\xBF", // U+FEFF // enables format=flowed parser 'flowed' => false, // enables wrapping for non-flowed text - 'wrap' => true, + 'wrap' => true, // line-break tag - 'break' => "<br>\n", + 'break' => "<br>\n", // prefix and suffix (wrapper element) - 'begin' => '<div class="pre">', - 'end' => '</div>', + 'begin' => '<div class="pre">', + 'end' => '</div>', // enables links replacement - 'links' => true, + 'links' => true, // string replacer class 'replacer' => 'rcube_string_replacer', ); @@ -278,6 +282,7 @@ class rcube_text2html $text = strtr($text, $table); $nbsp = $this->config['space']; + $nobr = $this->config['wordjoiner']; // replace some whitespace characters $text = str_replace(array("\r", "\t"), array('', ' '), $text); @@ -299,9 +304,15 @@ class rcube_text2html $text = $copy; } + // make the whole line non-breakable else { - // make the whole line non-breakable - $text = str_replace(array(' ', '-', '/'), array($nbsp, '-⁠', '/⁠'), $text); + $repl = array( + ' ' => $nbsp, + '-' => $nobr . '-' . $nobr, + '/' => $nobr . '/', + ); + + $text = str_replace(array_keys($repl), array_values($repl), $text); } return $text; diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php index 77c58dd14..8ed34fc28 100644 --- a/program/lib/Roundcube/rcube_user.php +++ b/program/lib/Roundcube/rcube_user.php @@ -29,6 +29,7 @@ class rcube_user public $ID; public $data; public $language; + public $prefs; /** * Holds database connection. @@ -132,10 +133,14 @@ class rcube_user */ function get_prefs() { - $prefs = array(); + if (isset($this->prefs)) { + return $this->prefs; + } + + $this->prefs = array(); if (!empty($this->language)) - $prefs['language'] = $this->language; + $this->prefs['language'] = $this->language; if ($this->ID) { // Preferences from session (write-master is unavailable) @@ -153,11 +158,11 @@ class rcube_user } if ($this->data['preferences']) { - $prefs += (array)unserialize($this->data['preferences']); + $this->prefs += (array)unserialize($this->data['preferences']); } } - return $prefs; + return $this->prefs; } /** @@ -183,7 +188,7 @@ class rcube_user $config = $this->rc->config; // merge (partial) prefs array with existing settings - $save_prefs = $a_user_prefs + $old_prefs; + $this->prefs = $save_prefs = $a_user_prefs + $old_prefs; unset($save_prefs['language']); // don't save prefs with default values if they haven't been changed yet @@ -229,12 +234,19 @@ class rcube_user } /** - * Generate a unique hash to identify this user which + * Generate a unique hash to identify this user whith */ function get_hash() { - $key = substr($this->rc->config->get('des_key'), 1, 4); - return md5($this->data['user_id'] . $key . $this->data['username'] . '@' . $this->data['mail_host']); + $prefs = $this->get_prefs(); + + // generate a random hash and store it in user prefs + if (empty($prefs['client_hash'])) { + $prefs['client_hash'] = md5($this->data['username'] . mt_rand() . $this->data['mail_host']); + $this->save_prefs(array('client_hash' => $prefs['client_hash'])); + } + + return $prefs['client_hash']; } /** diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index f4c0e90ca..0ca2a9e31 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -917,7 +917,7 @@ class rcube_utils */ public static function tokenize_string($str, $minlen = 2) { - $expr = array('/[\s;\/+-]+/ui', '/(\d)[-.\s]+(\d)/u'); + $expr = array('/[\s;,"\'\/+-]+/ui', '/(\d)[-.\s]+(\d)/u'); $repl = array(' ', '\\1\\2'); if ($minlen > 1) { @@ -985,6 +985,28 @@ class rcube_utils } /** + * Compare two strings for matching words (order not relevant) + * + * @param string Haystack + * @param string Needle + * @return boolen True if match, False otherwise + */ + public static function words_match($haystack, $needle) + { + $a_needle = self::tokenize_string($needle, 1); + $haystack = join(" ", self::tokenize_string($haystack, 1)); + + $hits = 0; + foreach ($a_needle as $w) { + if (stripos($haystack, $w) !== false) { + $hits++; + } + } + + return $hits >= count($a_needle); + } + + /** * Parse commandline arguments into a hash array * * @param array $aliases Argument alias names diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index 7f6b11851..c0e261df4 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -393,6 +393,10 @@ class rcube_vcard $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type)); } } + else { + unset($this->raw[$tag]); + } + break; } } diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index e0cce685b..b042f5f80 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -403,16 +403,23 @@ class rcube_washtml { // special replacements (not properly handled by washtml class) $html_search = array( - '/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR> - '/<title[^>]*>[^<]*<\/title>/i', // PHP bug #32547 workaround: remove title tag - '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?) - '/<html\s[^>]+>/i', // washtml/DOMDocument cannot handle xml namespaces + // space(s) between <NOBR> + '/(<\/nobr>)(\s+)(<nobr>)/i', + // PHP bug #32547 workaround: remove title tag + '/<title[^>]*>[^<]*<\/title>/i', + // remove <!doctype> before BOM (#1490291) + '/<\!doctype[^>]+>[^<]*/im', + // byte-order mark (only outlook?) + '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', + // washtml/DOMDocument cannot handle xml namespaces + '/<html\s[^>]+>/i', ); $html_replace = array( '\\1'.' '.'\\3', '', '', + '', '<html>', ); $html = preg_replace($html_search, $html_replace, trim($html)); |