diff --git a/CHANGELOG b/CHANGELOG index 80543659b..48ff235c4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- SMTP GSSAPI support via krb_authentication plugin (#6417) - Removed referer_check option (#6440) - Update to TinyMCE 4.8.2 - Plugin API: Added 'raise_error' hook (#6199) @@ -8,9 +9,11 @@ CHANGELOG Roundcube Webmail - Password: Added 'modoboa' driver (#6361) - Password: Fix bug where password_dovecotpw_with_method setting could be ignored (#6436) - Password: Fix bug where new users could skip forced password change (#6434) +- Elastic: Support new-line char as a separator for pasted recipients (#6460) - Elastic: Improved UX of search dialogs (#6416) - Elastic: Fix unwanted thread expanding when selecting a collapsed thread in non-mobile mode (#6445) - Log errors caused by low pcre.backtrack_limit when sending a mail message (#6433) +- Fix bug where autocomplete list could be displayed out of screen (#6469) - Fix style/navigation on error page depending on authentication state (#6362) - Fix so invalid smtp_helo_host is never used, fallback to localhost (#6408) - Fix custom logo size in Elastic (#6424) @@ -19,6 +22,9 @@ CHANGELOG Roundcube Webmail - Managesieve: Fix bug where show_real_foldernames setting wasn't respected (#6422) - New_user_identity: Fix %fu/%u vars substitution in user specific LDAP params (#6419) - Fix support for "allow-from " in "x_frame_options" config option (#6449) +- Fix bug where valid content between HTML comments could have been skipped in some cases (#6464) +- Fix multiple VCard field search (#6466) +- Fix session issue on long running requests (#6470) RELEASE 1.4-beta ---------------- diff --git a/INSTALL b/INSTALL index 9c62bbb14..d8ab5a127 100644 --- a/INSTALL +++ b/INSTALL @@ -19,7 +19,7 @@ REQUIREMENTS - GD, Imagick (optional thumbnails generation, QR-code) * PEAR and PEAR packages distributed with Roundcube or external: - Mail_Mime 1.10.0 or newer - - Net_SMTP 1.7.1 or newer + - Net_SMTP 1.8.1 or newer - Net_Socket 1.0.12 or newer - Net_IDNA2 0.1.1 or newer - Auth_SASL 1.0.6 or newer diff --git a/composer.json-dist b/composer.json-dist index 97d0f4abd..0ca230fb5 100644 --- a/composer.json-dist +++ b/composer.json-dist @@ -11,11 +11,10 @@ "require": { "php": ">=5.4.0", "pear/pear-core-minimal": "~1.10.1", - "pear/net_socket": "~1.2.1", "pear/auth_sasl": "~1.1.0", "pear/net_idna2": "~0.2.0", "pear/mail_mime": "~1.10.0", - "pear/net_smtp": "~1.8.0", + "pear/net_smtp": "~1.8.1", "pear/crypt_gpg": "~1.6.3", "pear/net_sieve": "~1.4.3", "roundcube/plugin-installer": "~0.1.6", diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 312a7ec35..653dfcfaa 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -569,7 +569,7 @@ $config['max_group_members'] = 0; $config['product_name'] = 'Roundcube Webmail'; // Add this user-agent to message headers when sending -$config['useragent'] = 'Roundcube Webmail/'.RCMAIL_VERSION; +$config['useragent'] = 'Roundcube Webmail/'.RCUBE_VERSION; // try to load host-specific configuration // see https://github.com/roundcube/roundcubemail/wiki/Configuration:-Multi-Domain-Setup diff --git a/plugins/krb_authentication/composer.json b/plugins/krb_authentication/composer.json index ee835556b..10af7eb35 100644 --- a/plugins/krb_authentication/composer.json +++ b/plugins/krb_authentication/composer.json @@ -3,7 +3,7 @@ "type": "roundcube-plugin", "description": "Kerberos Authentication", "license": "GPLv3+", - "version": "1.1", + "version": "1.2", "authors": [ { "name": "Jeroen van Meeuwen", diff --git a/plugins/krb_authentication/config.inc.php.dist b/plugins/krb_authentication/config.inc.php.dist index 63db16943..975cacb85 100644 --- a/plugins/krb_authentication/config.inc.php.dist +++ b/plugins/krb_authentication/config.inc.php.dist @@ -9,5 +9,12 @@ // Unlike $config['default_host'] this must be a string! $config['krb_authentication_host'] = ''; -// GSS API security context -$config['krb_authentication_context'] = 'imap/kolab.example.org@EXAMPLE.ORG'; +// GSSAPI security context. +// Single value or an array with per-protocol values. Example: +// +// $config['krb_authentication_context'] = array( +// 'imap' => 'imap/host.fqdn@REALM.NAME', +// 'smtp' => 'smtp/host.fqdn@REALM.NAME', +// 'sieve' => 'sieve/host.fqdn@REALM.NAME', +// ); +$config['krb_authentication_context'] = 'principal@REALM.NAME'; diff --git a/plugins/krb_authentication/krb_authentication.php b/plugins/krb_authentication/krb_authentication.php index 66a3581ad..57b79e526 100644 --- a/plugins/krb_authentication/krb_authentication.php +++ b/plugins/krb_authentication/krb_authentication.php @@ -25,6 +25,7 @@ class krb_authentication extends rcube_plugin $this->add_hook('login_after', array($this, 'login')); $this->add_hook('storage_connect', array($this, 'storage_connect')); $this->add_hook('managesieve_connect', array($this, 'managesieve_connect')); + $this->add_hook('smtp_connect', array($this, 'smtp_connect')); } /** @@ -74,19 +75,41 @@ class krb_authentication extends rcube_plugin return $args; } + /** + * login_after hook handler + */ + function login($args) + { + // Redirect to the previous QUERY_STRING + if ($this->redirect_query) { + header('Location: ./?' . $this->redirect_query); + exit; + } + + return $args; + } + /** * Storage_connect hook handler */ function storage_connect($args) { if (!empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) { - // Load plugin's config file - $this->load_config(); + $args['gssapi_context'] = $this->gssapi_context('imap'); + $args['gssapi_cn'] = $_SERVER['KRB5CCNAME']; + $args['auth_type'] = 'GSSAPI'; + } - $rcmail = rcmail::get_instance(); - $context = $rcmail->config->get('krb_authentication_context'); + return $args; + } - $args['gssapi_context'] = $context ?: 'imap/kolab.example.org@EXAMPLE.ORG'; + /** + * managesieve_connect hook handler + */ + function managesieve_connect($args) + { + if ((!isset($args['auth_type']) || $args['auth_type'] == 'GSSAPI') && !empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) { + $args['gssapi_context'] = $this->gssapi_context('sieve'); $args['gssapi_cn'] = $_SERVER['KRB5CCNAME']; $args['auth_type'] = 'GSSAPI'; } @@ -95,36 +118,38 @@ class krb_authentication extends rcube_plugin } /** - * login_after hook handler + * smtp_connect hook handler */ - function login($args) + function smtp_connect($args) { - // Redirect to the previous QUERY_STRING - if ($this->redirect_query) { - header('Location: ./?' . $this->redirect_query); - exit; + if ((!isset($args['smtp_auth_type']) || $args['smtp_auth_type'] == 'GSSAPI') && !empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) { + $args['gssapi_context'] = $this->gssapi_context('smtp'); + $args['gssapi_cn'] = $_SERVER['KRB5CCNAME']; + $args['smtp_auth_type'] = 'GSSAPI'; } return $args; } /** - * managesieve_connect hook handler + * Returns configured GSSAPI context string */ - function managesieve_connect($args) + private function gssapi_context($protocol) { - if ((!isset($args['auth_type']) || $args['auth_type'] == 'GSSAPI') && !empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) { - // Load plugin's config file - $this->load_config(); + // Load plugin's config file + $this->load_config(); - $rcmail = rcmail::get_instance(); - $context = $rcmail->config->get('krb_authentication_context'); + $rcmail = rcmail::get_instance(); + $context = $rcmail->config->get('krb_authentication_context'); - $args['gssapi_context'] = $context ?: 'imap/kolab.example.org@EXAMPLE.ORG'; - $args['gssapi_cn'] = $_SERVER['KRB5CCNAME']; - $args['auth_type'] = 'GSSAPI'; + if (is_array($context)) { + $context = $context[$protocol]; } - return $args; + if (empty($context)) { + rcube::raise_error("Empty GSSAPI context ($protocol).", true); + } + + return $context; } } diff --git a/program/js/app.js b/program/js/app.js index 2f0c3625f..fc22e0b7c 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -5941,9 +5941,20 @@ function rcube_webmail() // reset content ul.innerHTML = ''; this.env.contacts = []; + // move the results pane right under the input box - var pos = $(this.ksearch_input).offset(); - this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'}); + var pos = $(this.ksearch_input).offset(), + w = $(window).width(), + left = w - pos.left > 200 ? pos.left : w - 200, + width = Math.min(400, w - left); + + this.ksearch_pane.css({ + left: left + 'px', + top: (pos.top + this.ksearch_input.offsetHeight + 1) + 'px', + maxWidth: width + 'px', + minWidth: '200px', + display: 'none' + }); } // add each result line to list diff --git a/program/lib/Roundcube/README.md b/program/lib/Roundcube/README.md index b5f5a51e9..1e028a3be 100644 --- a/program/lib/Roundcube/README.md +++ b/program/lib/Roundcube/README.md @@ -32,7 +32,7 @@ include_path directory of your webserver. Some classes of the framework require one or multiple of the following [PEAR][pear] libraries: - Mail_Mime 1.8.1 or newer -- Net_SMTP 1.7.1 or newer +- Net_SMTP 1.8.1 or newer - Net_Socket 1.0.12 or newer - Net_IDNA2 0.1.1 or newer - Auth_SASL 1.0.6 or newer diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php index f72e42766..55e5f6fb3 100644 --- a/program/lib/Roundcube/rcube_contacts.php +++ b/program/lib/Roundcube/rcube_contacts.php @@ -380,7 +380,7 @@ class rcube_contacts extends rcube_addressbook foreach ((array)$row[$col] as $value) { if ($this->compare_search_value($colname, $value, $search, $mode)) { $found[$colname] = true; - break 2; + break; } } } diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index 33bcaddc3..32faed526 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -306,7 +306,7 @@ abstract class rcube_session $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)) { + else if ($key == $this->key && (!$this->vars || microtime(true) - $this->start < 0.5)) { $cache = $this->vars; } else { // else read data again diff --git a/program/lib/Roundcube/rcube_smtp.php b/program/lib/Roundcube/rcube_smtp.php index 6be2510ea..cfa326b6f 100644 --- a/program/lib/Roundcube/rcube_smtp.php +++ b/program/lib/Roundcube/rcube_smtp.php @@ -108,7 +108,8 @@ class rcube_smtp // IDNA Support $smtp_host = rcube_utils::idn_to_ascii($smtp_host); - $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host, false, 0, $CONFIG['smtp_conn_options']); + $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host, false, 0, $CONFIG['smtp_conn_options'], + $CONFIG['gssapi_context'], $CONFIG['gssapi_cn']); if ($rcube->config->get('smtp_debug')) { $this->conn->setDebug(true, array($this, 'debug_handler')); @@ -154,7 +155,7 @@ class rcube_smtp } // attempt to authenticate to the SMTP server - if ($smtp_user && $smtp_pass) { + if (($smtp_user && $smtp_pass) || ($smtp_auth_type == 'GSSAPI')) { // IDNA Support if (strpos($smtp_user, '@')) { $smtp_user = rcube_utils::idn_to_ascii($smtp_user); diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 8837a917f..497a1c3e4 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -643,6 +643,9 @@ class rcube_washtml $html = str_replace($badwordchars, $fixedwordchars, $html); + // FIXME: HTML comments handling could be better. The code below can break comments (#6464), + // we should probably do not modify content inside comments at all. + // fix (unknown/malformed) HTML tags before "wash" $html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)([^>]*)/', array($this, 'html_tag_callback'), $html); @@ -665,9 +668,15 @@ class rcube_washtml */ public static function html_tag_callback($matches) { + // It might be an ending of a comment, ignore (#6464) + if (substr($matches[3], -2) == '--') { + $matches[0] = ''; + return implode('', $matches); + } + $tagname = $matches[2]; $tagname = preg_replace(array( - '/:.*$/', // Microsoft's Smart Tags + '/:.*$/', // Microsoft's Smart Tags '/[^a-z0-9_\[\]\!?-]/i', // forbidden characters ), '', $tagname); diff --git a/skins/elastic/styles/widgets/dialogs.less b/skins/elastic/styles/widgets/dialogs.less index b2eb81a33..57751655b 100644 --- a/skins/elastic/styles/widgets/dialogs.less +++ b/skins/elastic/styles/widgets/dialogs.less @@ -59,12 +59,20 @@ #rcmKSearchpane { width: auto; - max-width: none; - overflow: hidden; li { padding-right: .5rem; } + + html.layout-small &, + html.layout-phone & { + bottom: auto; + border: 1px solid @color-input-border; + } + + html.layout-phone & { + max-width: 100% !important; + } } html.layout-small, @@ -106,11 +114,6 @@ html.layout-phone { width: 100%; } - #rcmKSearchpane { - bottom: auto; - border: 1px solid @color-input-border; - } - .popover-header { display: block; border-radius: 0; diff --git a/skins/elastic/ui.js b/skins/elastic/ui.js index 9c620ca0a..96a1b38b4 100644 --- a/skins/elastic/ui.js +++ b/skins/elastic/ui.js @@ -2892,7 +2892,7 @@ function rcube_elastic_ui() */ function recipient_input(obj) { - var list, input, ac_props, + var list, input, ac_props, update_lock, input_len_update = function() { input.css('width', input.val().length * 10 + 15); }, @@ -2932,29 +2932,41 @@ function rcube_elastic_ui() else recipient.insertBefore(input.parent()); }, - update_func = function() { - var text = input.val().replace(/[,;\s]+$/, ''), - result = recipient_input_parser(text); + update_func = function(text) { + var result; + + if (update_lock) { + return; + } + + update_lock = true; + + text = (text || input.val()).replace(/[,;\s]+$/, ''); + result = recipient_input_parser(text); $.each(result.recipients, function() { insert_recipient(this.name, this.email); }); - input.val(result.text); - apply_func(); - input_len_update(); + // setTimeout() here is needed for proper input reset on paste event + // This is also the reason why we need parse_lock + setTimeout(function() { + input.val(result.text); + apply_func(); + input_len_update(); + update_lock = false; + }, 1); - if (result.recipients.length) { - return true; - } + return result.recipients.length > 0; }, parse_func = function(e) { - // Note it can be also executed when autocomplete inserts a recipient - update_func(); - if (e.type == 'blur') { list.removeClass('focus'); } + + // On paste the text is not yet in the input we have to use clipboard. + // Also because on paste new-line characters are replaced by spaces (#6460) + update_func(e.type == 'paste' ? (e.originalEvent.clipboardData || window.clipboardData).getData('text') : null); }, keydown_func = function(e) { // On Backspace remove the last recipient @@ -2963,8 +2975,8 @@ function rcube_elastic_ui() apply_func(); return false; } - // Here we add a recipient box when the separator character (,;) was pressed - else if (e.key == ',' || e.key == ';') { + // Here we add a recipient box when the separator (,;) or Enter was pressed + else if (e.key == ',' || e.key == ';' || e.key == 'Enter') { if (update_func()) { return false; } @@ -2973,7 +2985,7 @@ function rcube_elastic_ui() input_len_update(); }; - // Create the input elemennt and "editable" area + // Create the input element and "editable" area input = $('').attr({type: 'text', tabindex: $(obj).attr('tabindex')}) .on('paste change blur', parse_func) .on('keydown', keydown_func) @@ -3021,6 +3033,9 @@ function rcube_elastic_ui() */ function recipient_input_parser(text) { + // support new-line as a separator, for paste action (#6460) + text = $.trim(text.replace(/[,;\s]*[\r\n]+/g, ',')); + var recipients = [], address_rx_part = '(\\S+|("[^"]+"))@\\S+', recipient_rx1 = new RegExp('(<' + address_rx_part + '>)'), diff --git a/skins/larry/styles.css b/skins/larry/styles.css index d073302ec..20ac1b33d 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -2426,6 +2426,8 @@ ul.toolbarmenu li span.copy { border: 0; cursor: default; position: relative; + overflow: hidden; + text-overflow: ellipsis; } #rcmKSearchpane ul li i.icon { diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php index 9879575a8..eebd80de5 100644 --- a/tests/Framework/Washtml.php +++ b/tests/Framework/Washtml.php @@ -98,6 +98,11 @@ class Framework_Washtml extends PHPUnit_Framework_TestCase $washed = $this->cleanupResult($washer->wash($html)); $this->assertEquals('

para1

para2

', $washed, "HTML comments - bracket inside"); + + $html = "

\n2\n4

"; + $washed = $this->cleanupResult($washer->wash($html)); + + $this->assertEquals("

\n2\n4

", $washed, "HTML comments (#6464)"); } /**