Thomas Bruederli
2012-09-09 99d9f50a0000447d0a752e6c43716237dc0da176
Merge branch 'master' of github.com:roundcube/roundcubemail
2 files renamed
17 files deleted
42 files added
74 files modified
5346 ■■■■■ changed files
CHANGELOG 45 ●●●● patch | view | raw | blame | history
config/main.inc.php.dist 27 ●●●●● patch | view | raw | blame | history
installer/rcube_install.php 11 ●●●● patch | view | raw | blame | history
plugins/acl/acl.php 3 ●●●● patch | view | raw | blame | history
plugins/acl/skins/larry/acl.css 4 ●●●● patch | view | raw | blame | history
plugins/acl/skins/larry/templates/table.html 8 ●●●● patch | view | raw | blame | history
plugins/hide_blockquote/hide_blockquote.php 6 ●●●● patch | view | raw | blame | history
plugins/hide_blockquote/skins/larry/style.css patch | view | raw | blame | history
plugins/managesieve/tests/Makefile 7 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/Parser.php 54 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/Tokenizer.php 33 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser.phpt 120 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_body.phpt 49 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_imapflags.phpt 28 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_include.phpt 30 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_kep14.phpt 19 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_prefix.phpt 25 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_relational.phpt 25 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_vacation.phpt 39 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_variables.phpt 39 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parset_subaddress.phpt 38 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser 52 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser.out 52 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_body 17 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_imapflags 7 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_include 7 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_kep14 2 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_kep14.out 3 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_prefix 5 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_relational 6 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_subaddress 11 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_vacation 12 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/src/parser_variables 12 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/tokenize.phpt 66 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/virtualmin.php 4 ●●●● patch | view | raw | blame | history
plugins/virtuser_query/virtuser_query.php 5 ●●●● patch | view | raw | blame | history
program/include/clisetup.php 49 ●●●● patch | view | raw | blame | history
program/include/html.php 30 ●●●●● patch | view | raw | blame | history
program/include/rcmail.php 35 ●●●●● patch | view | raw | blame | history
program/include/rcube.php 1485 ●●●● patch | view | raw | blame | history
program/include/rcube_bc.inc 5 ●●●●● patch | view | raw | blame | history
program/include/rcube_browser.php 2 ●●●●● patch | view | raw | blame | history
program/include/rcube_cache.php 4 ●●●● patch | view | raw | blame | history
program/include/rcube_charset.php 6 ●●●●● patch | view | raw | blame | history
program/include/rcube_config.php 5 ●●●●● patch | view | raw | blame | history
program/include/rcube_db.php 4 ●●●● patch | view | raw | blame | history
program/include/rcube_imap.php 33 ●●●●● patch | view | raw | blame | history
program/include/rcube_imap_cache.php 52 ●●●●● patch | view | raw | blame | history
program/include/rcube_imap_generic.php 11 ●●●●● patch | view | raw | blame | history
program/include/rcube_ldap.php 17 ●●●●● patch | view | raw | blame | history
program/include/rcube_message.php 28 ●●●● patch | view | raw | blame | history
program/include/rcube_mime.php 4 ●●●● patch | view | raw | blame | history
program/include/rcube_output_html.php 39 ●●●●● patch | view | raw | blame | history
program/include/rcube_plugin.php 2 ●●● patch | view | raw | blame | history
program/include/rcube_plugin_api.php 4 ●●●● patch | view | raw | blame | history
program/include/rcube_session.php 11 ●●●● patch | view | raw | blame | history
program/include/rcube_shared.inc 33 ●●●● patch | view | raw | blame | history
program/include/rcube_smtp.php 2 ●●● patch | view | raw | blame | history
program/include/rcube_storage.php 7 ●●●● patch | view | raw | blame | history
program/include/rcube_user.php 2 ●●● patch | view | raw | blame | history
program/include/rcube_utils.php 67 ●●●● patch | view | raw | blame | history
program/include/rcube_vcard.php 3 ●●●● patch | view | raw | blame | history
program/js/app.js 186 ●●●● patch | view | raw | blame | history
program/js/common.js 7 ●●●● patch | view | raw | blame | history
program/js/googiespell.js 4 ●●●● patch | view | raw | blame | history
program/js/list.js 4 ●●●● patch | view | raw | blame | history
program/lib/html2text.php 34 ●●●● patch | view | raw | blame | history
program/lib/washtml.php 8 ●●●● patch | view | raw | blame | history
program/localization/en_US/labels.inc 13 ●●●● patch | view | raw | blame | history
program/localization/pl_PL/labels.inc 1 ●●●● patch | view | raw | blame | history
program/steps/addressbook/export.inc 2 ●●● patch | view | raw | blame | history
program/steps/addressbook/search.inc 7 ●●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 83 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 17 ●●●● patch | view | raw | blame | history
program/steps/mail/headers.inc 3 ●●●● patch | view | raw | blame | history
program/steps/mail/sendmail.inc 15 ●●●● patch | view | raw | blame | history
program/steps/mail/viewsource.inc 2 ●●● patch | view | raw | blame | history
program/steps/settings/folders.inc 12 ●●●●● patch | view | raw | blame | history
program/steps/settings/func.inc 16 ●●●●● patch | view | raw | blame | history
program/steps/settings/save_folder.inc 5 ●●●● patch | view | raw | blame | history
program/steps/settings/save_prefs.inc 4 ●●●● patch | view | raw | blame | history
skins/classic/functions.js 15 ●●●●● patch | view | raw | blame | history
skins/classic/includes/links.html 1 ●●●● patch | view | raw | blame | history
skins/classic/splitter.js 4 ●●●● patch | view | raw | blame | history
skins/classic/templates/compose.html 2 ●●● patch | view | raw | blame | history
skins/classic/templates/message.html 15 ●●●● patch | view | raw | blame | history
skins/classic/templates/messageerror.html 15 ●●●● patch | view | raw | blame | history
skins/larry/addressbook.css 1 ●●●● patch | view | raw | blame | history
skins/larry/ie7hacks.css 16 ●●●●● patch | view | raw | blame | history
skins/larry/iehacks.css 2 ●●● patch | view | raw | blame | history
skins/larry/images/contactpic_32px.png patch | view | raw | blame | history
skins/larry/images/contactpic_48px.png patch | view | raw | blame | history
skins/larry/includes/links.html 1 ●●●● patch | view | raw | blame | history
skins/larry/mail.css 90 ●●●●● patch | view | raw | blame | history
skins/larry/styles.css 6 ●●●●● patch | view | raw | blame | history
skins/larry/svggradient.php 2 ●●●●● patch | view | raw | blame | history
skins/larry/svggradients.css 2 ●●● patch | view | raw | blame | history
skins/larry/templates/compose.html 2 ●●● patch | view | raw | blame | history
skins/larry/templates/message.html 63 ●●●●● patch | view | raw | blame | history
skins/larry/templates/messageerror.html 10 ●●●●● patch | view | raw | blame | history
skins/larry/templates/messagepreview.html 5 ●●●●● patch | view | raw | blame | history
skins/larry/ui.js 62 ●●●●● patch | view | raw | blame | history
tests/Framework/BaseReplacer.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Browser.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Cache.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Charset.php 28 ●●●●● patch | view | raw | blame | history
tests/Framework/ContentFilter.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Html.php 46 ●●●●● patch | view | raw | blame | history
tests/Framework/Image.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Imap.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/ImapGeneric.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/MessageHeader.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/MessagePart.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Mime.php 123 ●●●●● patch | view | raw | blame | history
tests/Framework/Rcube.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/ResultIndex.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/ResultSet.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/ResultThread.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Shared.php 204 ●●●●● patch | view | raw | blame | history
tests/Framework/Smtp.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Spellchecker.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/StringReplacer.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/User.php 20 ●●●●● patch | view | raw | blame | history
tests/Framework/Utils.php 196 ●●●●● patch | view | raw | blame | history
tests/Framework/VCard.php 73 ●●●●● patch | view | raw | blame | history
tests/HtmlToText.php 32 ●●●● patch | view | raw | blame | history
tests/MailFunc.php 172 ●●●●● patch | view | raw | blame | history
tests/bootstrap.php 35 ●●●●● patch | view | raw | blame | history
tests/maildecode.php 130 ●●●●● patch | view | raw | blame | history
tests/mailfunc.php 173 ●●●●● patch | view | raw | blame | history
tests/modcss.php 45 ●●●●● patch | view | raw | blame | history
tests/phpunit.xml 37 ●●●●● patch | view | raw | blame | history
tests/runtests.sh 54 ●●●●● patch | view | raw | blame | history
tests/src/photo.vcf 45 ●●●●● patch | view | raw | blame | history
tests/vcards.php 65 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,11 +1,28 @@
CHANGELOG Roundcube Webmail
===========================
- Fix focus on the list when list row is clicked (#1488600)
- Added separate From and To columns apart from smart From/To column (#1486891)
- Fix fallback to Larry skin when configured skin isn't available (#1488591)
- Fix (workaround) delete operations with some versions of memcache (#1488592)
- Fix (disable) request validation for spell and spell_html actions
- Fix PLAIN authentication for some IMAP servers (#1488674)
- Fix encoding vCard file when contains PHOTO;ENCODING=b (#1488683)
- Fix focus issue in IE when selecting message row (#1488620)
- Remove (too big) min-width on mail screen
- Add full headers view in message preview window (#1488538)
- Fix message display page issues - unified with message preview (#1488590, #1488642)
- Fix displaying all headers when they contain malformed characters (#1488666)
- Fix decoding of HTML messages with UTF-16 charset specified (#1488654)
- Fix quota capability detection so it can be overwritten by a plugin (#1488655)
- Added template object 'frame'
- Fix identity selection on reply (#1488101)
- Add option to enable HTML editor on forwarding (#1488517)
- Add option to not include original message on reply, rename option top_posting to reply_mode (#1485149)
- Fix Larry's messages list filter in IE (#1488632)
- Fix more IE issues by disabling Compat. mode with X-UA-Compatible meta tag (#1488626)
- Fix setting locales under Solaris - use additional .UTF-8 suffix (#1488628)
- Fix email address validation for addresses with IP address in domain part
- Fix Larry skin issues in IE7 compat. mode (#1488618)
- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
- Added session_path config option and unified cookies settings in javascript
- Added "Undeleted" option to messages list filter
- Rewritten test scripts for PHPUnit
- Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332)
- Removed PEAR::MDB2 package
- Removed users.alias column, added option ('user_aliases')
@@ -31,6 +48,24 @@
    Move global functions from main.inc and rcube_shared.inc into classes
    Better classes separation
RELEASE 0.8.1
-------------
- Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593)
- Fix lower-casing email address on replies (#1488598)
- Fix line separator in exported messages (#1488603)
- Fix XSS issue where plain signatures wasn't secured in HTML mode (#1488613)
- Fix XSS issue where href="javascript:" wasn't secured (#1488613)
- Fix impossible to create message with empty plain text part (#1488610)
- Fix stripped apostrophes when replying in plain text to HTML message (#1488606)
- Fix inactive Save search option after advanced search (#1488607)
- Fix Remove from group option is active for contact search result (#1488608)
- Disable autocapitalization in login form on iPad/iPhone (#1488609)
- Fix focus on the list when list row is clicked (#1488600)
- Added separate From and To columns apart from smart From/To column (#1486891)
- Fix fallback to Larry skin when configured skin isn't available (#1488591)
- Fix (workaround) delete operations with some versions of memcache (#1488592)
- Fix (disable) request validation for spell and spell_html actions
RELEASE 0.8.0
-------------
- Don't show product version on login screen (can be enabled by config)
config/main.inc.php.dist
@@ -21,7 +21,7 @@
// LOGGING/DEBUGGING
// ----------------------------------
// system error reporting: 1 = log; 2 = report (not implemented yet), 4 = show, 8 = trace
// system error reporting, sum of: 1 = log; 4 = show, 8 = trace
$rcmail_config['debug_level'] = 1;
// log driver:  'syslog' or 'file'.
@@ -78,7 +78,7 @@
// TCP port used for IMAP connections
$rcmail_config['default_port'] = 143;
// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use
// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or null to use
// best server supported one)
$rcmail_config['imap_auth_type'] = null;
@@ -224,11 +224,12 @@
// 0 - disabled, 1 - username and host only, 2 - username, host, password
$rcmail_config['login_autocomplete'] = 0;
// If users authentication is not case sensitive this must be enabled.
// You can also use it to force conversion of logins to lower case.
// Forces conversion of logins to lower case.
// 0 - disabled, 1 - only domain part, 2 - domain and local part.
// If users authentication is not case-sensitive this must be enabled.
// After enabling it all user records need to be updated, e.g. with query:
// UPDATE users SET username = LOWER(username);
$rcmail_config['login_lc'] = false;
$rcmail_config['login_lc'] = 0;
// Includes should be interpreted as PHP files
$rcmail_config['skin_include_php'] = false;
@@ -240,11 +241,14 @@
// must be greater than 'keep_alive'/60
$rcmail_config['session_lifetime'] = 10;
// session domain: .example.org
// Session domain: .example.org
$rcmail_config['session_domain'] = '';
// session name. Default: 'roundcube_sessid'
// Session name. Default: 'roundcube_sessid'
$rcmail_config['session_name'] = null;
// Session path. Defaults to PHP session.cookie_path setting.
$rcmail_config['session_path'] = null;
// Backend to use for session storage. Can either be 'db' (default) or 'memcache'
// If set to memcache, a list of servers need to be specified in 'memcache_hosts'
@@ -721,7 +725,7 @@
$rcmail_config['show_images'] = 0;
// compose html formatted messages by default
// 0 - never, 1 - always, 2 - on reply to HTML message only
// 0 - never, 1 - always, 2 - on reply to HTML message, 3 - on forward or reply to HTML message
$rcmail_config['htmleditor'] = 0;
// show pretty dates as standard
@@ -779,8 +783,11 @@
// 2 - Expand only threads with unread messages 
$rcmail_config['autoexpand_threads'] = 0;
// When replying place cursor above original message (top posting)
$rcmail_config['top_posting'] = false;
// When replying:
// -1 - don't cite the original message
// 0  - place cursor below the original message
// 1  - place cursor above original message (top posting)
$rcmail_config['reply_mode'] = 0;
// When replying strip original signature from message
$rcmail_config['strip_existing_sig'] = true;
installer/rcube_install.php
@@ -35,13 +35,14 @@
  var $obsolete_config = array('db_backend', 'double_auth');
  var $replaced_config = array(
    'skin_path' => 'skin',
    'locale_string' => 'language',
    'multiple_identities' => 'identities_level',
    'skin_path'            => 'skin',
    'locale_string'        => 'language',
    'multiple_identities'  => 'identities_level',
    'addrbook_show_images' => 'show_images',
    'imap_root' => 'imap_ns_personal',
    'pagesize' => 'mail_pagesize',
    'imap_root'            => 'imap_ns_personal',
    'pagesize'             => 'mail_pagesize',
    'default_imap_folders' => 'default_folders',
    'top_posting'          => 'reply_mode',
  );
  // these config options are required for a working system
plugins/acl/acl.php
@@ -233,8 +233,7 @@
        // Advanced rights
        $attrib['id'] = 'advancedrights';
        foreach ($supported as $val) {
            $id = "acl$val";
        foreach ($supported as $idx => $val) {
            $ul .= html::tag('li', null,
                $input->show('', array(
                    'name' => "acl[$val]", 'value' => $val, 'id' => $id))
plugins/acl/skins/larry/acl.css
@@ -123,3 +123,7 @@
{
  margin-left: 0.5em;
}
ul.toolbarmenu li span.delete {
  background-position: 0 -1509px;
}
plugins/acl/skins/larry/templates/table.html
@@ -3,14 +3,14 @@
    <roundcube:object name="acltable" id="acltable" class="records-table" />
</div>
<div id="acllist-footer" class="boxfooter">
    <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="listbutton groupactions"onclick="UI.show_popup('aclmenu');return false" innerClass="inner" content="&#9881;" />
    <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="listbutton groupactions"onclick="UI.show_popup('aclmenu', undefined, {above:1});return false" innerClass="inner" content="&#9881;" />
</div>
</div>
<div id="aclmenu" class="popupmenu">
    <ul class="toolbarmenu selectable">
        <li><roundcube:button command="acl-edit" label="edit" classAct="active" /></li>
        <li><roundcube:button command="acl-delete" label="delete" classAct="active" /></li>
    <ul class="toolbarmenu selectable iconized">
        <li><roundcube:button command="acl-edit" label="edit" class="icon" classAct="icon active" innerclass="icon edit" /></li>
        <li><roundcube:button command="acl-delete" label="delete" class="icon" classAct="icon active" innerclass="icon delete" /></li>
        <roundcube:if condition="!in_array('acl_advanced_mode', (array)config:dont_override)" />
            <li><roundcube:button name="acl-switch" id="acl-switch" label="acl.advanced" onclick="rcmail.command('acl-mode-switch')" class="active" /></li>
        <roundcube:endif />
plugins/hide_blockquote/hide_blockquote.php
@@ -27,11 +27,7 @@
            && ($limit = $rcmail->config->get('hide_blockquote_limit'))
        ) {
            // include styles
            $skin = $rcmail->config->get('skin');
            if (!file_exists($this->home."/skins/$skin/style.css")) {
                $skin = 'default';
            }
            $this->include_stylesheet("skins/$skin/style.css");
            $this->include_stylesheet($this->local_skin_path() . "/style.css");
            // Script and localization
            $this->include_script('hide_blockquote.js');
plugins/hide_blockquote/skins/larry/style.css
plugins/managesieve/tests/Makefile
File was deleted
plugins/managesieve/tests/Parser.php
New file
@@ -0,0 +1,54 @@
<?php
class Parser extends PHPUnit_Framework_TestCase
{
    function setUp()
    {
        include_once dirname(__FILE__) . '/../lib/rcube_sieve_script.php';
    }
    /**
     * Sieve script parsing
     *
     * @dataProvider data_parser
     */
    function test_parser($input, $output, $message)
    {
        $script = new rcube_sieve_script($input);
        $result = $script->as_text();
        $this->assertEquals(trim($result), trim($output), $message);
    }
    /**
     * Data provider for test_parser()
     */
    function data_parser()
    {
        $dir_path = realpath(dirname(__FILE__) . '/src');
        $dir      = opendir($dir_path);
        $result   = array();
        while ($file = readdir($dir)) {
            if (preg_match('/^[a-z0-9_]+$/', $file)) {
                $input = file_get_contents($dir_path . '/' . $file);
                if (file_exists($dir_path . '/' . $file . '.out')) {
                    $output = file_get_contents($dir_path . '/' . $file . '.out');
                }
                else {
                    $output = $input;
                }
                $result[] = array(
                    'input'   => $input,
                    'output'  => $output,
                    'message' => "Error in parsing '$file' file",
                );
            }
        }
        return $result;
    }
}
plugins/managesieve/tests/Tokenizer.php
New file
@@ -0,0 +1,33 @@
<?php
class Tokenizer extends PHPUnit_Framework_TestCase
{
    function setUp()
    {
        include_once dirname(__FILE__) . '/../lib/rcube_sieve_script.php';
    }
    function data_tokenizer()
    {
        return array(
            array(1, "text: #test\nThis is test ; message;\nMulti line\n.\n;\n", '"This is test ; message;\nMulti line"'),
            array(0, '["test1","test2"]', '[["test1","test2"]]'),
            array(1, '["test"]', '["test"]'),
            array(1, '"te\\"st"', '"te\\"st"'),
            array(0, 'test #comment', '["test"]'),
            array(0, "text:\ntest\n.\ntext:\ntest\n.\n", '["test","test"]'),
            array(1, '"\\a\\\\\\"a"', '"a\\\\\\"a"'),
        );
    }
    /**
     * @dataProvider data_tokenizer
     */
    function test_tokenizer($num, $input, $output)
    {
        $res = json_encode(rcube_sieve_script::tokenize($input, $num));
        $this->assertEquals(trim($res), trim($output));
    }
}
plugins/managesieve/tests/parser.phpt
File was deleted
plugins/managesieve/tests/parser_body.phpt
File was deleted
plugins/managesieve/tests/parser_imapflags.phpt
File was deleted
plugins/managesieve/tests/parser_include.phpt
File was deleted
plugins/managesieve/tests/parser_kep14.phpt
File was deleted
plugins/managesieve/tests/parser_prefix.phpt
File was deleted
plugins/managesieve/tests/parser_relational.phpt
File was deleted
plugins/managesieve/tests/parser_vacation.phpt
File was deleted
plugins/managesieve/tests/parser_variables.phpt
File was deleted
plugins/managesieve/tests/parset_subaddress.phpt
File was deleted
plugins/managesieve/tests/src/parser
New file
@@ -0,0 +1,52 @@
require ["fileinto","reject","envelope"];
# rule:[spam]
if anyof (header :contains "X-DSPAM-Result" "Spam")
{
    fileinto "Spam";
    stop;
}
# rule:[test1]
if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld")
{
    discard;
    stop;
}
# rule:[test2]
if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
{
    fileinto "test";
    stop;
}
# rule:[comments]
if anyof (true) /* comment
 * "comment" #comment */ {
    /* comment */ stop;
# comment
}
# rule:[reject]
if size :over 5000K {
    reject "Message over 5MB size limit. Please contact me before sending this.";
}
# rule:[false]
if false # size :over 5000K
{
    stop; /* rule disabled */
}
# rule:[true]
if true
{
    stop;
}
fileinto "Test";
# rule:[address test]
if address :all :is "From" "nagios@domain.tld"
{
    fileinto "domain.tld";
    stop;
}
# rule:[envelope test]
if envelope :domain :is "From" "domain.tld"
{
    fileinto "domain.tld";
    stop;
}
plugins/managesieve/tests/src/parser.out
New file
@@ -0,0 +1,52 @@
require ["fileinto","reject","envelope"];
# rule:[spam]
if header :contains "X-DSPAM-Result" "Spam"
{
    fileinto "Spam";
    stop;
}
# rule:[test1]
if header :contains ["From","To"] "test@domain.tld"
{
    discard;
    stop;
}
# rule:[test2]
if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]")
{
    fileinto "test";
    stop;
}
# rule:[comments]
if true
{
    stop;
}
# rule:[reject]
if size :over 5000K
{
    reject "Message over 5MB size limit. Please contact me before sending this.";
}
# rule:[false]
if false # size :over 5000K
{
    stop;
}
# rule:[true]
if true
{
    stop;
}
fileinto "Test";
# rule:[address test]
if address :all :is "From" "nagios@domain.tld"
{
    fileinto "domain.tld";
    stop;
}
# rule:[envelope test]
if envelope :domain :is "From" "domain.tld"
{
    fileinto "domain.tld";
    stop;
}
plugins/managesieve/tests/src/parser_body
New file
@@ -0,0 +1,17 @@
require ["body","fileinto"];
if body :raw :contains "MAKE MONEY FAST"
{
    stop;
}
if body :content "text" :contains ["missile","coordinates"]
{
    fileinto "secrets";
}
if body :content "audio/mp3" :contains ""
{
    fileinto "jukebox";
}
if body :text :contains "project schedule"
{
    fileinto "project/schedule";
}
plugins/managesieve/tests/src/parser_imapflags
New file
@@ -0,0 +1,7 @@
require ["imap4flags"];
# rule:[imapflags]
if header :matches "Subject" "^Test$"
{
    setflag "\\Seen";
    addflag ["\\Answered","\\Deleted"];
}
plugins/managesieve/tests/src/parser_include
New file
@@ -0,0 +1,7 @@
require ["include"];
include "script.sieve";
# rule:[two]
if true
{
    include :optional "second.sieve";
}
plugins/managesieve/tests/src/parser_kep14
New file
@@ -0,0 +1,2 @@
# EDITOR Roundcube
# EDITOR_VERSION 123
plugins/managesieve/tests/src/parser_kep14.out
New file
@@ -0,0 +1,3 @@
require ["variables"];
set "EDITOR" "Roundcube";
set "EDITOR_VERSION" "123";
plugins/managesieve/tests/src/parser_prefix
New file
@@ -0,0 +1,5 @@
# this is a comment
# and the second line
require ["variables"];
set "b" "c";
plugins/managesieve/tests/src/parser_relational
New file
@@ -0,0 +1,6 @@
require ["relational","comparator-i;ascii-numeric"];
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
{
    redirect "test@test.tld";
}
plugins/managesieve/tests/src/parser_subaddress
New file
@@ -0,0 +1,11 @@
require ["envelope","subaddress","fileinto"];
if envelope :user "To" "postmaster"
{
    fileinto "postmaster";
    stop;
}
if envelope :detail :is "To" "mta-filters"
{
    fileinto "mta-filters";
    stop;
}
plugins/managesieve/tests/src/parser_vacation
New file
@@ -0,0 +1,12 @@
require ["vacation"];
# rule:[test-vacation]
if header :contains "Subject" "vacation"
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
plugins/managesieve/tests/src/parser_variables
New file
@@ -0,0 +1,12 @@
require ["variables"];
set "honorific" "Mr";
set "vacation" text:
Dear ${HONORIFIC} ${last_name},
I am out, please leave a message after the meep.
.
;
set :length "b" "${a}";
set :lower "b" "${a}";
set :upperfirst "b" "${a}";
set :upperfirst :lower "b" "${a}";
set :quotewildcard "b" "Rock*";
plugins/managesieve/tests/tokenize.phpt
File was deleted
plugins/password/drivers/virtualmin.php
@@ -48,6 +48,10 @@
            $pieces = explode("_", $username);
            $domain = $pieces[0];
            break;
        case 8: // domain taken from alias, username left as it was
            $email = $rcmail->user->data['alias'];
            $domain = substr(strrchr($email, "@"), 1);
            break;
        default: // username@domain
            $domain = substr(strrchr($username, "@"), 1);
        }
plugins/virtuser_query/virtuser_query.php
@@ -14,8 +14,11 @@
 *
 * $rcmail_config['virtuser_query'] = array('email' => '', 'user' => '', 'host' => '');
 *
 * The email query can return more than one record to create more identities.
 * This requires identities_level option to be set to value less than 2.
 *
 * @version @package_version@
 * @author Aleksander Machniak
 * @author Aleksander Machniak <alec@alec.pl>
 * @author Steffen Vogel
 */
class virtuser_query extends rcube_plugin
program/include/clisetup.php
@@ -33,33 +33,36 @@
 */
function get_opt($aliases = array())
{
    $args = array();
    for ($i=1; $i < count($_SERVER['argv']); $i++) {
        $arg = $_SERVER['argv'][$i];
        $value = true;
        $key = null;
    $args = array();
        if ($arg[0] == '-') {
            $key = preg_replace('/^-+/', '', $arg);
            $sp = strpos($arg, '=');
            if ($sp > 0) {
                $key = substr($key, 0, $sp - 2);
                $value = substr($arg, $sp+1);
            }
            else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
                $value = $_SERVER['argv'][++$i];
            }
    for ($i=1; $i < count($_SERVER['argv']); $i++) {
        $arg   = $_SERVER['argv'][$i];
        $value = true;
        $key   = null;
            $args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
        }
        else
            $args[] = $arg;
        if ($arg[0] == '-') {
            $key = preg_replace('/^-+/', '', $arg);
            $sp  = strpos($arg, '=');
            if ($sp > 0) {
                $key   = substr($key, 0, $sp - 2);
                $value = substr($arg, $sp+1);
            }
            else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
                $value = $_SERVER['argv'][++$i];
            }
        if ($alias = $aliases[$key])
            $args[$alias] = $args[$key];
    }
            $args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
        }
        else {
            $args[] = $arg;
        }
    return $args;
        if ($alias = $aliases[$key]) {
            $args[$alias] = $args[$key];
        }
    }
    return $args;
}
program/include/html.php
@@ -154,7 +154,7 @@
            $attr = array('src' => $attr);
        }
        return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
            array('src','alt','width','height','border','usemap','onclick')));
            array('src','alt','width','height','border','usemap','onclick')));
    }
    /**
@@ -171,7 +171,7 @@
            $attr = array('href' => $attr);
        }
        return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
        array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
        array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
    }
    /**
@@ -358,7 +358,7 @@
    protected $tagname = 'input';
    protected $type = 'text';
    protected $allowed = array(
        'type','name','value','size','tabindex',
        'type','name','value','size','tabindex','autocapitalize',
        'autocomplete','checked','onchange','onclick','disabled','readonly',
        'spellcheck','results','maxlength','src','multiple','placeholder',
    );
@@ -532,7 +532,7 @@
{
    protected $tagname = 'textarea';
    protected $allowed = array('name','rows','cols','wrap','tabindex',
    'onchange','disabled','readonly','spellcheck');
        'onchange','disabled','readonly','spellcheck');
    /**
     * Get HTML code for this object
@@ -563,7 +563,7 @@
        }
        return self::tag($this->tagname, $this->attrib, $value,
            array_merge(self::$common_attrib, $this->allowed));
            array_merge(self::$common_attrib, $this->allowed));
    }
}
@@ -591,7 +591,7 @@
    protected $tagname = 'select';
    protected $options = array();
    protected $allowed = array('name','size','tabindex','autocomplete',
    'multiple','onchange','disabled','rel');
        'multiple','onchange','disabled','rel');
    /**
     * Add a new option to this drop-down
@@ -655,7 +655,7 @@
{
    protected $tagname = 'table';
    protected $allowed = array('id','class','style','width','summary',
        'cellpadding','cellspacing','border');
        'cellpadding','cellspacing','border');
    private $header = array();
    private $rows = array();
@@ -705,8 +705,9 @@
     */
    public function add_header($attr, $cont)
    {
        if (is_string($attr))
            $attr = array('class' => $attr);
        if (is_string($attr)) {
            $attr = array('class' => $attr);
        }
        $cell = new stdClass;
        $cell->attrib = $attr;
@@ -763,11 +764,13 @@
     */
    public function set_row_attribs($attr = array(), $index = null)
    {
        if (is_string($attr))
            $attr = array('class' => $attr);
        if (is_string($attr)) {
            $attr = array('class' => $attr);
        }
        if ($index === null)
        if ($index === null) {
            $index = $this->rowindex;
        }
        $this->rows[$index]->attrib = $attr;
    }
@@ -781,8 +784,9 @@
     */
    public function get_row_attribs($index = null)
    {
        if ($index === null)
        if ($index === null) {
            $index = $this->rowindex;
        }
        return $this->rows[$index] ? $this->rows[$index]->attrib : null;
    }
program/include/rcmail.php
@@ -157,14 +157,16 @@
      $this->config->set_user_prefs((array)$this->user->get_prefs());
    }
    $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
    $lang = $this->language_prop($this->config->get('language', $_SESSION['language']));
    $_SESSION['language'] = $this->user->language = $lang;
    // set localization
    setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
    setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8');
    // workaround for http://bugs.php.net/bug.php?id=18556
    if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
      setlocale(LC_CTYPE, 'en_US' . '.utf8');
    if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
      setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
    }
  }
@@ -306,7 +308,7 @@
  /**
   * Init output object for GUI and add common scripts.
   * This will instantiate a rcmail_template object and set
   * This will instantiate a rcube_output_html object and set
   * environment vars according to the current session and configuration
   *
   * @param boolean True if this request is loaded in a (i)frame
@@ -453,7 +455,14 @@
    // Convert username to lowercase. If storage backend
    // is case-insensitive we need to store always the same username (#1487113)
    if ($config['login_lc']) {
      $username = mb_strtolower($username);
      if ($config['login_lc'] == 2 || $config['login_lc'] === true) {
        $username = mb_strtolower($username);
      }
      else if (strpos($username, '@')) {
        // lowercase domain name
        list($local, $domain) = explode('@', $username);
        $username = $local . '@' . mb_strtolower($domain);
      }
    }
    // try to resolve email address from virtuser table
@@ -463,17 +472,13 @@
    // Here we need IDNA ASCII
    // Only rcube_contacts class is using domain names in Unicode
    $host = rcube_utils::idn_to_ascii($host);
    if (strpos($username, '@')) {
      // lowercase domain name
      list($local, $domain) = explode('@', $username);
      $username = $local . '@' . mb_strtolower($domain);
      $username = rcube_utils::idn_to_ascii($username);
    }
    $host     = rcube_utils::idn_to_ascii($host);
    $username = rcube_utils::idn_to_ascii($username);
    // user already registered -> overwrite username
    if ($user = rcube_user::query($username, $host))
    if ($user = rcube_user::query($username, $host)) {
      $username = $user->data['username'];
    }
    $storage = $this->get_storage();
@@ -1204,7 +1209,7 @@
        }
        else {
            if (!empty($date)) {
                $timestamp = rcube_strtotime($date);
                $timestamp = rcube_utils::strtotime($date);
            }
            if (empty($timestamp)) {
program/include/rcube.php
@@ -25,364 +25,372 @@
 * Base class of the Roundcube Framework
 * implemented as singleton
 *
 * @package Core
 * @package    Framework
 * @subpackage Core
 */
class rcube
{
  const INIT_WITH_DB = 1;
  const INIT_WITH_PLUGINS = 2;
    const INIT_WITH_DB = 1;
    const INIT_WITH_PLUGINS = 2;
  /**
   * Singleton instace of rcube
   *
   * @var rcmail
   */
  static protected $instance;
    /**
     * Singleton instace of rcube
     *
     * @var rcmail
     */
    static protected $instance;
  /**
   * Stores instance of rcube_config.
   *
   * @var rcube_config
   */
  public $config;
    /**
     * Stores instance of rcube_config.
     *
     * @var rcube_config
     */
    public $config;
  /**
   * Instace of database class.
   *
   * @var rcube_pdo
   */
  public $db;
    /**
     * Instace of database class.
     *
     * @var rcube_db
     */
    public $db;
  /**
   * Instace of Memcache class.
   *
   * @var Memcache
   */
  public $memcache;
    /**
     * Instace of Memcache class.
     *
     * @var Memcache
     */
    public $memcache;
  /**
   * Instace of rcube_session class.
   *
   * @var rcube_session
   */
  public $session;
   /**
     * Instace of rcube_session class.
     *
     * @var rcube_session
     */
    public $session;
  /**
   * Instance of rcube_smtp class.
   *
   * @var rcube_smtp
   */
  public $smtp;
    /**
     * Instance of rcube_smtp class.
     *
     * @var rcube_smtp
     */
    public $smtp;
  /**
   * Instance of rcube_storage class.
   *
   * @var rcube_storage
   */
  public $storage;
    /**
     * Instance of rcube_storage class.
     *
     * @var rcube_storage
     */
    public $storage;
  /**
   * Instance of rcube_output class.
   *
   * @var rcube_output
   */
  public $output;
    /**
     * Instance of rcube_output class.
     *
     * @var rcube_output
     */
    public $output;
  /**
   * Instance of rcube_plugin_api.
   *
   * @var rcube_plugin_api
   */
  public $plugins;
    /**
     * Instance of rcube_plugin_api.
     *
     * @var rcube_plugin_api
     */
    public $plugins;
  /* private/protected vars */
  protected $texts;
  protected $caches = array();
  protected $shutdown_functions = array();
  protected $expunge_cache = false;
    /* private/protected vars */
    protected $texts;
    protected $caches = array();
    protected $shutdown_functions = array();
    protected $expunge_cache = false;
  /**
   * This implements the 'singleton' design pattern
   *
   * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
   * @return rcube The one and only instance
   */
  static function get_instance($mode = 0)
  {
    if (!self::$instance) {
      self::$instance = new rcube();
      self::$instance->init($mode);
    }
    return self::$instance;
  }
  /**
   * Private constructor
   */
  protected function __construct()
  {
    // load configuration
    $this->config = new rcube_config();
    $this->plugins = new rcube_dummy_plugin_api;
    register_shutdown_function(array($this, 'shutdown'));
  }
  /**
   * Initial startup function
   */
  protected function init($mode = 0)
  {
    // initialize syslog
    if ($this->config->get('log_driver') == 'syslog') {
      $syslog_id = $this->config->get('syslog_id', 'roundcube');
      $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
      openlog($syslog_id, LOG_ODELAY, $syslog_facility);
    }
    // connect to database
    if ($mode & self::INIT_WITH_DB) {
      $this->get_dbh();
    }
    // create plugin API and load plugins
    if ($mode & self::INIT_WITH_PLUGINS) {
      $this->plugins = rcube_plugin_api::get_instance();
    }
  }
  /**
   * Get the current database connection
   *
   * @return rcube_pdo  Database connection object
   */
  public function get_dbh()
  {
    if (!$this->db) {
      $config_all = $this->config->all();
      $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
      $this->db->set_debug((bool)$config_all['sql_debug']);
    }
    return $this->db;
  }
  /**
   * Get global handle for memcache access
   *
   * @return object Memcache
   */
  public function get_memcache()
  {
    if (!isset($this->memcache)) {
      // no memcache support in PHP
      if (!class_exists('Memcache')) {
        $this->memcache = false;
        return false;
      }
      $this->memcache = new Memcache;
      $this->mc_available = 0;
      // add all configured hosts to pool
      $pconnect = $this->config->get('memcache_pconnect', true);
      foreach ($this->config->get('memcache_hosts', array()) as $host) {
        if (substr($host, 0, 7) != 'unix://') {
          list($host, $port) = explode(':', $host);
          if (!$port) $port = 11211;
        }
        else {
          $port = 0;
    /**
     * This implements the 'singleton' design pattern
     *
     * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
     *
     * @return rcube The one and only instance
     */
    static function get_instance($mode = 0)
    {
        if (!self::$instance) {
            self::$instance = new rcube();
            self::$instance->init($mode);
        }
        $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
      }
      // test connection and failover (will result in $this->mc_available == 0 on complete failure)
      $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
      if (!$this->mc_available)
        $this->memcache = false;
        return self::$instance;
    }
    return $this->memcache;
  }
    /**
     * Private constructor
     */
    protected function __construct()
    {
        // load configuration
        $this->config  = new rcube_config;
        $this->plugins = new rcube_dummy_plugin_api;
  /**
   * Callback for memcache failure
   */
  public function memcache_failure($host, $port)
  {
    static $seen = array();
    // only report once
    if (!$seen["$host:$port"]++) {
      $this->mc_available--;
      self::raise_error(array('code' => 604, 'type' => 'db',
        'line' => __LINE__, 'file' => __FILE__,
        'message' => "Memcache failure on host $host:$port"),
        true, false);
    }
  }
  /**
   * Initialize and get cache object
   *
   * @param string $name   Cache identifier
   * @param string $type   Cache type ('db', 'apc' or 'memcache')
   * @param string $ttl    Expiration time for cache items
   * @param bool   $packed Enables/disables data serialization
   *
   * @return rcube_cache Cache object
   */
  public function get_cache($name, $type='db', $ttl=0, $packed=true)
  {
    if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
      $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
        register_shutdown_function(array($this, 'shutdown'));
    }
    return $this->caches[$name];
  }
    /**
     * Initial startup function
     */
    protected function init($mode = 0)
    {
        // initialize syslog
        if ($this->config->get('log_driver') == 'syslog') {
            $syslog_id       = $this->config->get('syslog_id', 'roundcube');
            $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
            openlog($syslog_id, LOG_ODELAY, $syslog_facility);
        }
  /**
   * Create SMTP object and connect to server
   *
   * @param boolean True if connection should be established
   */
  public function smtp_init($connect = false)
  {
    $this->smtp = new rcube_smtp();
        // connect to database
        if ($mode & self::INIT_WITH_DB) {
            $this->get_dbh();
        }
    if ($connect)
      $this->smtp->connect();
  }
  /**
   * Initialize and get storage object
   *
   * @return rcube_storage Storage object
   */
  public function get_storage()
  {
    // already initialized
    if (!is_object($this->storage)) {
      $this->storage_init();
        // create plugin API and load plugins
        if ($mode & self::INIT_WITH_PLUGINS) {
            $this->plugins = rcube_plugin_api::get_instance();
        }
    }
    return $this->storage;
  }
    /**
     * Get the current database connection
     *
     * @return rcube_db Database object
     */
    public function get_dbh()
    {
        if (!$this->db) {
            $config_all = $this->config->all();
            $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
            $this->db->set_debug((bool)$config_all['sql_debug']);
        }
  /**
   * Initialize storage object
   */
  public function storage_init()
  {
    // already initialized
    if (is_object($this->storage)) {
      return;
        return $this->db;
    }
    $driver = $this->config->get('storage_driver', 'imap');
    $driver_class = "rcube_{$driver}";
    if (!class_exists($driver_class)) {
      self::raise_error(array(
        'code' => 700, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Storage driver class ($driver) not found!"),
        true, true);
    /**
     * Get global handle for memcache access
     *
     * @return object Memcache
     */
    public function get_memcache()
    {
        if (!isset($this->memcache)) {
            // no memcache support in PHP
            if (!class_exists('Memcache')) {
                $this->memcache = false;
                return false;
            }
            $this->memcache     = new Memcache;
            $this->mc_available = 0;
            // add all configured hosts to pool
            $pconnect = $this->config->get('memcache_pconnect', true);
            foreach ($this->config->get('memcache_hosts', array()) as $host) {
                if (substr($host, 0, 7) != 'unix://') {
                    list($host, $port) = explode(':', $host);
                    if (!$port) $port = 11211;
                }
                else {
                    $port = 0;
                }
                $this->mc_available += intval($this->memcache->addServer(
                    $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
            }
            // test connection and failover (will result in $this->mc_available == 0 on complete failure)
            $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
            if (!$this->mc_available) {
                $this->memcache = false;
            }
        }
        return $this->memcache;
    }
    // Initialize storage object
    $this->storage = new $driver_class;
    // for backward compat. (deprecated, will be removed)
    $this->imap = $this->storage;
    /**
     * Callback for memcache failure
     */
    public function memcache_failure($host, $port)
    {
        static $seen = array();
    // enable caching of mail data
    $storage_cache  = $this->config->get("{$driver}_cache");
    $messages_cache = $this->config->get('messages_cache');
    // for backward compatybility
    if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
        $storage_cache  = 'db';
        $messages_cache = true;
        // only report once
        if (!$seen["$host:$port"]++) {
            $this->mc_available--;
            self::raise_error(array(
                'code' => 604, 'type' => 'db',
                'line' => __LINE__, 'file' => __FILE__,
                'message' => "Memcache failure on host $host:$port"),
                true, false);
        }
    }
    if ($storage_cache)
        $this->storage->set_caching($storage_cache);
    if ($messages_cache)
        $this->storage->set_messages_caching(true);
    // set pagesize from config
    $pagesize = $this->config->get('mail_pagesize');
    if (!$pagesize) {
        $pagesize = $this->config->get('pagesize', 50);
    }
    $this->storage->set_pagesize($pagesize);
    /**
     * Initialize and get cache object
     *
     * @param string $name   Cache identifier
     * @param string $type   Cache type ('db', 'apc' or 'memcache')
     * @param string $ttl    Expiration time for cache items
     * @param bool   $packed Enables/disables data serialization
     *
     * @return rcube_cache Cache object
     */
    public function get_cache($name, $type='db', $ttl=0, $packed=true)
    {
        if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
            $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
        }
    // set class options
    $options = array(
      'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
      'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
      'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
      'debug'       => (bool) $this->config->get("{$driver}_debug"),
      'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
      'timeout'     => (int) $this->config->get("{$driver}_timeout"),
      'skip_deleted' => (bool) $this->config->get('skip_deleted'),
      'driver'      => $driver,
    );
    if (!empty($_SESSION['storage_host'])) {
      $options['host']     = $_SESSION['storage_host'];
      $options['user']     = $_SESSION['username'];
      $options['port']     = $_SESSION['storage_port'];
      $options['ssl']      = $_SESSION['storage_ssl'];
      $options['password'] = $this->decrypt($_SESSION['password']);
      $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
        return $this->caches[$name];
    }
    $options = $this->plugins->exec_hook("storage_init", $options);
    // for backward compat. (deprecated, to be removed)
    $options = $this->plugins->exec_hook("imap_init", $options);
    /**
     * Create SMTP object and connect to server
     *
     * @param boolean True if connection should be established
     */
    public function smtp_init($connect = false)
    {
        $this->smtp = new rcube_smtp();
    $this->storage->set_options($options);
    $this->set_storage_prop();
  }
  /**
   * Set storage parameters.
   * This must be done AFTER connecting to the server!
   */
  protected function set_storage_prop()
  {
    $storage = $this->get_storage();
    $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
    if ($default_folders = $this->config->get('default_folders')) {
      $storage->set_default_folders($default_folders);
        if ($connect) {
            $this->smtp->connect();
        }
    }
    if (isset($_SESSION['mbox'])) {
      $storage->set_folder($_SESSION['mbox']);
    /**
     * Initialize and get storage object
     *
     * @return rcube_storage Storage object
     */
    public function get_storage()
    {
        // already initialized
        if (!is_object($this->storage)) {
            $this->storage_init();
        }
        return $this->storage;
    }
    if (isset($_SESSION['page'])) {
      $storage->set_page($_SESSION['page']);
    /**
     * Initialize storage object
     */
    public function storage_init()
    {
        // already initialized
        if (is_object($this->storage)) {
            return;
        }
        $driver       = $this->config->get('storage_driver', 'imap');
        $driver_class = "rcube_{$driver}";
        if (!class_exists($driver_class)) {
            self::raise_error(array(
                'code' => 700, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Storage driver class ($driver) not found!"),
                true, true);
        }
        // Initialize storage object
        $this->storage = new $driver_class;
        // for backward compat. (deprecated, will be removed)
        $this->imap = $this->storage;
        // enable caching of mail data
        $storage_cache  = $this->config->get("{$driver}_cache");
        $messages_cache = $this->config->get('messages_cache');
        // for backward compatybility
        if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
            $storage_cache  = 'db';
            $messages_cache = true;
        }
        if ($storage_cache) {
            $this->storage->set_caching($storage_cache);
        }
        if ($messages_cache) {
            $this->storage->set_messages_caching(true);
        }
        // set pagesize from config
        $pagesize = $this->config->get('mail_pagesize');
        if (!$pagesize) {
            $pagesize = $this->config->get('pagesize', 50);
        }
        $this->storage->set_pagesize($pagesize);
        // set class options
        $options = array(
            'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
            'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
            'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
            'debug'       => (bool) $this->config->get("{$driver}_debug"),
            'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
            'timeout'     => (int) $this->config->get("{$driver}_timeout"),
            'skip_deleted' => (bool) $this->config->get('skip_deleted'),
            'driver'      => $driver,
        );
        if (!empty($_SESSION['storage_host'])) {
            $options['host']     = $_SESSION['storage_host'];
            $options['user']     = $_SESSION['username'];
            $options['port']     = $_SESSION['storage_port'];
            $options['ssl']      = $_SESSION['storage_ssl'];
            $options['password'] = $this->decrypt($_SESSION['password']);
            $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
        }
        $options = $this->plugins->exec_hook("storage_init", $options);
        // for backward compat. (deprecated, to be removed)
        $options = $this->plugins->exec_hook("imap_init", $options);
        $this->storage->set_options($options);
        $this->set_storage_prop();
    }
  }
    /**
     * Set storage parameters.
     * This must be done AFTER connecting to the server!
     */
    protected function set_storage_prop()
    {
        $storage = $this->get_storage();
        $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
        if ($default_folders = $this->config->get('default_folders')) {
            $storage->set_default_folders($default_folders);
        }
        if (isset($_SESSION['mbox'])) {
            $storage->set_folder($_SESSION['mbox']);
        }
        if (isset($_SESSION['page'])) {
            $storage->set_page($_SESSION['page']);
        }
    }
    /**
@@ -397,11 +405,16 @@
        $sess_name   = $this->config->get('session_name');
        $sess_domain = $this->config->get('session_domain');
        $sess_path   = $this->config->get('session_path');
        $lifetime    = $this->config->get('session_lifetime', 0) * 60;
        // set session domain
        if ($sess_domain) {
            ini_set('session.cookie_domain', $sess_domain);
        }
        // set session path
        if ($sess_path) {
            ini_set('session.cookie_path', $sess_path);
        }
        // set session garbage collecting time according to session_lifetime
        if ($lifetime) {
@@ -492,433 +505,469 @@
    }
  /**
   * Get localized text in the desired language
   *
   * @param mixed   $attrib  Named parameters array or label name
   * @param string  $domain  Label domain (plugin) name
   *
   * @return string Localized text
   */
  public function gettext($attrib, $domain=null)
  {
    // load localization files if not done yet
    if (empty($this->texts))
      $this->load_language();
    // extract attributes
    if (is_string($attrib))
      $attrib = array('name' => $attrib);
    $name = $attrib['name'] ? $attrib['name'] : '';
    // attrib contain text values: use them from now
    if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
        $this->texts[$name] = $setval;
    // check for text with domain
    if ($domain && ($text = $this->texts[$domain.'.'.$name]))
      ;
    // text does not exist
    else if (!($text = $this->texts[$name])) {
      return "[$name]";
    }
    // replace vars in text
    if (is_array($attrib['vars'])) {
      foreach ($attrib['vars'] as $var_key => $var_value)
        $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
    }
    // format output
    if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
      return ucfirst($text);
    else if ($attrib['uppercase'])
      return mb_strtoupper($text);
    else if ($attrib['lowercase'])
      return mb_strtolower($text);
    return strtr($text, array('\n' => "\n"));
  }
  /**
   * Check if the given text label exists
   *
   * @param string  $name       Label name
   * @param string  $domain     Label domain (plugin) name or '*' for all domains
   * @param string  $ref_domain Sets domain name if label is found
   *
   * @return boolean True if text exists (either in the current language or in en_US)
   */
  public function text_exists($name, $domain = null, &$ref_domain = null)
  {
    // load localization files if not done yet
    if (empty($this->texts))
      $this->load_language();
    if (isset($this->texts[$name])) {
        $ref_domain = '';
        return true;
    }
    // any of loaded domains (plugins)
    if ($domain == '*') {
      foreach ($this->plugins->loaded_plugins() as $domain)
        if (isset($this->texts[$domain.'.'.$name])) {
          $ref_domain = $domain;
          return true;
        }
    }
    // specified domain
    else if ($domain) {
      $ref_domain = $domain;
      return isset($this->texts[$domain.'.'.$name]);
    }
    return false;
  }
  /**
   * Load a localization package
   *
   * @param string Language ID
   */
  public function load_language($lang = null, $add = array())
  {
    $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
    // load localized texts
    if (empty($this->texts) || $lang != $_SESSION['language']) {
      $this->texts = array();
      // handle empty lines after closing PHP tag in localization files
      ob_start();
      // get english labels (these should be complete)
      @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
      @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
      if (is_array($labels))
        $this->texts = $labels;
      if (is_array($messages))
        $this->texts = array_merge($this->texts, $messages);
      // include user language files
      if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
        if (is_array($labels))
          $this->texts = array_merge($this->texts, $labels);
        if (is_array($messages))
          $this->texts = array_merge($this->texts, $messages);
      }
      ob_end_clean();
      $_SESSION['language'] = $lang;
    }
    // append additional texts (from plugin)
    if (is_array($add) && !empty($add))
      $this->texts += $add;
  }
  /**
   * Check the given string and return a valid language code
   *
   * @param string Language code
   * @return string Valid language code
   */
  protected function language_prop($lang)
  {
    static $rcube_languages, $rcube_language_aliases;
    // user HTTP_ACCEPT_LANGUAGE if no language is specified
    if (empty($lang) || $lang == 'auto') {
       $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
       $lang = str_replace('-', '_', $accept_langs[0]);
     }
    if (empty($rcube_languages)) {
      @include(INSTALL_PATH . 'program/localization/index.inc');
    }
    // check if we have an alias for that language
    if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
      $lang = $rcube_language_aliases[$lang];
    }
    // try the first two chars
    else if (!isset($rcube_languages[$lang])) {
      $short = substr($lang, 0, 2);
      // check if we have an alias for the short language code
      if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
        $lang = $rcube_language_aliases[$short];
      }
      // expand 'nn' to 'nn_NN'
      else if (!isset($rcube_languages[$short])) {
        $lang = $short.'_'.strtoupper($short);
      }
    }
    if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
      $lang = 'en_US';
    }
    return $lang;
  }
  /**
   * Read directory program/localization and return a list of available languages
   *
   * @return array List of available localizations
   */
  public function list_languages()
  {
    static $sa_languages = array();
    if (!sizeof($sa_languages)) {
      @include(INSTALL_PATH . 'program/localization/index.inc');
      if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
        while (($name = readdir($dh)) !== false) {
          if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
            continue;
          if ($label = $rcube_languages[$name])
            $sa_languages[$name] = $label;
        }
        closedir($dh);
      }
    }
    return $sa_languages;
  }
  /**
   * Encrypt using 3DES
   *
   * @param string $clear clear text input
   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
   * @param boolean $base64 whether or not to base64_encode() the result before returning
   *
   * @return string encrypted text
   */
  public function encrypt($clear, $key = 'des_key', $base64 = true)
  {
    if (!$clear)
      return '';
    /*-
     * Add a single canary byte to the end of the clear text, which
     * will help find out how much of padding will need to be removed
     * upon decryption; see http://php.net/mcrypt_generic#68082
    /**
     * Get localized text in the desired language
     *
     * @param mixed   $attrib  Named parameters array or label name
     * @param string  $domain  Label domain (plugin) name
     *
     * @return string Localized text
     */
    $clear = pack("a*H2", $clear, "80");
    public function gettext($attrib, $domain=null)
    {
        // load localization files if not done yet
        if (empty($this->texts)) {
            $this->load_language();
        }
    if (function_exists('mcrypt_module_open') &&
        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
      $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
      $cipher = $iv . mcrypt_generic($td, $clear);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      @include_once 'des.inc';
        // extract attributes
        if (is_string($attrib)) {
            $attrib = array('name' => $attrib);
        }
      if (function_exists('des')) {
        $des_iv_size = 8;
        $iv = $this->create_iv($des_iv_size);
        $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
      }
      else {
        self::raise_error(array(
          'code' => 500, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
        ), true, true);
      }
        $name = $attrib['name'] ? $attrib['name'] : '';
        // attrib contain text values: use them from now
        if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
            $this->texts[$name] = $setval;
        }
        // check for text with domain
        if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
        }
        // text does not exist
        else if (!($text = $this->texts[$name])) {
            return "[$name]";
        }
        // replace vars in text
        if (is_array($attrib['vars'])) {
            foreach ($attrib['vars'] as $var_key => $var_value) {
                $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
            }
        }
        // format output
        if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
            return ucfirst($text);
        }
        else if ($attrib['uppercase']) {
            return mb_strtoupper($text);
        }
        else if ($attrib['lowercase']) {
            return mb_strtolower($text);
        }
        return strtr($text, array('\n' => "\n"));
    }
    return $base64 ? base64_encode($cipher) : $cipher;
  }
  /**
   * Decrypt 3DES-encrypted string
   *
   * @param string $cipher encrypted text
   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
   * @param boolean $base64 whether or not input is base64-encoded
   *
   * @return string decrypted text
   */
  public function decrypt($cipher, $key = 'des_key', $base64 = true)
  {
    if (!$cipher)
      return '';
    /**
     * Check if the given text label exists
     *
     * @param string  $name       Label name
     * @param string  $domain     Label domain (plugin) name or '*' for all domains
     * @param string  $ref_domain Sets domain name if label is found
     *
     * @return boolean True if text exists (either in the current language or in en_US)
     */
    public function text_exists($name, $domain = null, &$ref_domain = null)
    {
        // load localization files if not done yet
        if (empty($this->texts)) {
            $this->load_language();
        }
    $cipher = $base64 ? base64_decode($cipher) : $cipher;
        if (isset($this->texts[$name])) {
            $ref_domain = '';
            return true;
        }
    if (function_exists('mcrypt_module_open') &&
        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
      $iv_size = mcrypt_enc_get_iv_size($td);
      $iv = substr($cipher, 0, $iv_size);
        // any of loaded domains (plugins)
        if ($domain == '*') {
            foreach ($this->plugins->loaded_plugins() as $domain) {
                if (isset($this->texts[$domain.'.'.$name])) {
                    $ref_domain = $domain;
                    return true;
                }
            }
        }
        // specified domain
        else if ($domain) {
            $ref_domain = $domain;
            return isset($this->texts[$domain.'.'.$name]);
        }
      // session corruption? (#1485970)
      if (strlen($iv) < $iv_size)
        return false;
    }
    /**
     * Load a localization package
     *
     * @param string Language ID
     * @param array  Additional text labels/messages
     */
    public function load_language($lang = null, $add = array())
    {
        $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
        // load localized texts
        if (empty($this->texts) || $lang != $_SESSION['language']) {
            $this->texts = array();
            // handle empty lines after closing PHP tag in localization files
            ob_start();
            // get english labels (these should be complete)
            @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
            @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
            if (is_array($labels))
                $this->texts = $labels;
            if (is_array($messages))
                $this->texts = array_merge($this->texts, $messages);
            // include user language files
            if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
                include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
                include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
                if (is_array($labels))
                    $this->texts = array_merge($this->texts, $labels);
                if (is_array($messages))
                    $this->texts = array_merge($this->texts, $messages);
            }
            ob_end_clean();
            $_SESSION['language'] = $lang;
        }
        // append additional texts (from plugin)
        if (is_array($add) && !empty($add)) {
            $this->texts += $add;
        }
    }
    /**
     * Check the given string and return a valid language code
     *
     * @param string Language code
     *
     * @return string Valid language code
     */
    protected function language_prop($lang)
    {
        static $rcube_languages, $rcube_language_aliases;
        // user HTTP_ACCEPT_LANGUAGE if no language is specified
        if (empty($lang) || $lang == 'auto') {
            $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
            $lang         = str_replace('-', '_', $accept_langs[0]);
        }
        if (empty($rcube_languages)) {
            @include(INSTALL_PATH . 'program/localization/index.inc');
        }
        // check if we have an alias for that language
        if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
            $lang = $rcube_language_aliases[$lang];
        }
        // try the first two chars
        else if (!isset($rcube_languages[$lang])) {
            $short = substr($lang, 0, 2);
            // check if we have an alias for the short language code
            if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
                $lang = $rcube_language_aliases[$short];
            }
            // expand 'nn' to 'nn_NN'
            else if (!isset($rcube_languages[$short])) {
                $lang = $short.'_'.strtoupper($short);
            }
        }
        if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
            $lang = 'en_US';
        }
        return $lang;
    }
    /**
     * Read directory program/localization and return a list of available languages
     *
     * @return array List of available localizations
     */
    public function list_languages()
    {
        static $sa_languages = array();
        if (!sizeof($sa_languages)) {
            @include(INSTALL_PATH . 'program/localization/index.inc');
            if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
                while (($name = readdir($dh)) !== false) {
                    if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) {
                        continue;
                    }
                    if ($label = $rcube_languages[$name]) {
                        $sa_languages[$name] = $label;
                    }
                }
                closedir($dh);
            }
        }
        return $sa_languages;
    }
    /**
     * Encrypt using 3DES
     *
     * @param string $clear clear text input
     * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
     * @param boolean $base64 whether or not to base64_encode() the result before returning
     *
     * @return string encrypted text
     */
    public function encrypt($clear, $key = 'des_key', $base64 = true)
    {
        if (!$clear) {
            return '';
        }
        /*-
         * Add a single canary byte to the end of the clear text, which
         * will help find out how much of padding will need to be removed
         * upon decryption; see http://php.net/mcrypt_generic#68082
         */
        $clear = pack("a*H2", $clear, "80");
        if (function_exists('mcrypt_module_open') &&
            ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
        ) {
            $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
            mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
            $cipher = $iv . mcrypt_generic($td, $clear);
            mcrypt_generic_deinit($td);
            mcrypt_module_close($td);
        }
        else {
            @include_once 'des.inc';
            if (function_exists('des')) {
                $des_iv_size = 8;
                $iv = $this->create_iv($des_iv_size);
                $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
            }
            else {
                self::raise_error(array(
                    'code' => 500, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
                    ), true, true);
            }
        }
        return $base64 ? base64_encode($cipher) : $cipher;
    }
    /**
     * Decrypt 3DES-encrypted string
     *
     * @param string $cipher encrypted text
     * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
     * @param boolean $base64 whether or not input is base64-encoded
     *
     * @return string decrypted text
     */
    public function decrypt($cipher, $key = 'des_key', $base64 = true)
    {
        if (!$cipher) {
            return '';
        }
        $cipher = $base64 ? base64_decode($cipher) : $cipher;
        if (function_exists('mcrypt_module_open') &&
            ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
        ) {
            $iv_size = mcrypt_enc_get_iv_size($td);
            $iv = substr($cipher, 0, $iv_size);
            // session corruption? (#1485970)
            if (strlen($iv) < $iv_size) {
                return '';
            }
            $cipher = substr($cipher, $iv_size);
            mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
            $clear = mdecrypt_generic($td, $cipher);
            mcrypt_generic_deinit($td);
            mcrypt_module_close($td);
        }
        else {
            @include_once 'des.inc';
            if (function_exists('des')) {
                $des_iv_size = 8;
                $iv = substr($cipher, 0, $des_iv_size);
                $cipher = substr($cipher, $des_iv_size);
                $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
            }
            else {
                self::raise_error(array(
                    'code' => 500, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
                    ), true, true);
            }
        }
        /*-
         * Trim PHP's padding and the canary byte; see note in
         * rcube::encrypt() and http://php.net/mcrypt_generic#68082
         */
        $clear = substr(rtrim($clear, "\0"), 0, -1);
        return $clear;
    }
    /**
     * Generates encryption initialization vector (IV)
     *
     * @param int Vector size
     *
     * @return string Vector string
     */
    private function create_iv($size)
    {
        // mcrypt_create_iv() can be slow when system lacks entrophy
        // we'll generate IV vector manually
        $iv = '';
        for ($i = 0; $i < $size; $i++) {
            $iv .= chr(mt_rand(0, 255));
        }
        return $iv;
    }
    /**
     * Build a valid URL to this instance of Roundcube
     *
     * @param mixed Either a string with the action or url parameters as key-value pairs
     * @return string Valid application URL
     */
    public function url($p)
    {
        // STUB: should be overloaded by the application
        return '';
      $cipher = substr($cipher, $iv_size);
      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
      $clear = mdecrypt_generic($td, $cipher);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      @include_once 'des.inc';
      if (function_exists('des')) {
        $des_iv_size = 8;
        $iv = substr($cipher, 0, $des_iv_size);
        $cipher = substr($cipher, $des_iv_size);
        $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
      }
      else {
        self::raise_error(array(
          'code' => 500, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
        ), true, true);
      }
    }
    /*-
     * Trim PHP's padding and the canary byte; see note in
     * rcube::encrypt() and http://php.net/mcrypt_generic#68082
    /**
     * Function to be executed in script shutdown
     * Registered with register_shutdown_function()
     */
    $clear = substr(rtrim($clear, "\0"), 0, -1);
    return $clear;
  }
  /**
   * Generates encryption initialization vector (IV)
   *
   * @param int Vector size
   * @return string Vector string
   */
  private function create_iv($size)
  {
    // mcrypt_create_iv() can be slow when system lacks entrophy
    // we'll generate IV vector manually
    $iv = '';
    for ($i = 0; $i < $size; $i++)
        $iv .= chr(mt_rand(0, 255));
    return $iv;
  }
  /**
   * Build a valid URL to this instance of Roundcube
   *
   * @param mixed Either a string with the action or url parameters as key-value pairs
   * @return string Valid application URL
   */
  public function url($p)
  {
      // STUB: should be overloaded by the application
      return '';
  }
  /**
   * Function to be executed in script shutdown
   * Registered with register_shutdown_function()
   */
  public function shutdown()
  {
    foreach ($this->shutdown_functions as $function)
      call_user_func($function);
    if (is_object($this->smtp))
      $this->smtp->disconnect();
    foreach ($this->caches as $cache) {
        if (is_object($cache))
            $cache->close();
    }
    if (is_object($this->storage)) {
      if ($this->expunge_cache)
        $this->storage->expunge_cache();
      $this->storage->close();
    }
  }
  /**
   * Registers shutdown function to be executed on shutdown.
   * The functions will be executed before destroying any
   * objects like smtp, imap, session, etc.
   *
   * @param callback Function callback
   */
  public function add_shutdown_function($function)
  {
    $this->shutdown_functions[] = $function;
  }
  /**
   * Construct shell command, execute it and return output as string.
   * Keywords {keyword} are replaced with arguments
   *
   * @param $cmd Format string with {keywords} to be replaced
   * @param $values (zero, one or more arrays can be passed)
   * @return output of command. shell errors not detectable
   */
  public static function exec(/* $cmd, $values1 = array(), ... */)
  {
    $args = func_get_args();
    $cmd = array_shift($args);
    $values = $replacements = array();
    // merge values into one array
    foreach ($args as $arg)
      $values += (array)$arg;
    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
    foreach ($matches as $tags) {
      list(, $tag, $option, $key) = $tags;
      $parts = array();
      if ($option) {
        foreach ((array)$values["-$key"] as $key => $value) {
          if ($value === true || $value === false || $value === null)
            $parts[] = $value ? $key : "";
          else foreach ((array)$value as $val)
            $parts[] = "$key " . escapeshellarg($val);
    public function shutdown()
    {
        foreach ($this->shutdown_functions as $function) {
            call_user_func($function);
        }
      }
      else {
        foreach ((array)$values[$key] as $value)
          $parts[] = escapeshellarg($value);
      }
      $replacements[$tag] = join(" ", $parts);
        if (is_object($this->smtp)) {
            $this->smtp->disconnect();
        }
        foreach ($this->caches as $cache) {
            if (is_object($cache)) {
                $cache->close();
            }
        }
        if (is_object($this->storage)) {
            if ($this->expunge_cache) {
                $this->storage->expunge_cache();
            }
            $this->storage->close();
        }
    }
    // use strtr behaviour of going through source string once
    $cmd = strtr($cmd, $replacements);
    return (string)shell_exec($cmd);
  }
    /**
     * Registers shutdown function to be executed on shutdown.
     * The functions will be executed before destroying any
     * objects like smtp, imap, session, etc.
     *
     * @param callback Function callback
     */
    public function add_shutdown_function($function)
    {
        $this->shutdown_functions[] = $function;
    }
    /**
     * Construct shell command, execute it and return output as string.
     * Keywords {keyword} are replaced with arguments
     *
     * @param $cmd Format string with {keywords} to be replaced
     * @param $values (zero, one or more arrays can be passed)
     *
     * @return output of command. shell errors not detectable
     */
    public static function exec(/* $cmd, $values1 = array(), ... */)
    {
        $args   = func_get_args();
        $cmd    = array_shift($args);
        $values = $replacements = array();
        // merge values into one array
        foreach ($args as $arg) {
            $values += (array)$arg;
        }
        preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
        foreach ($matches as $tags) {
            list(, $tag, $option, $key) = $tags;
            $parts = array();
            if ($option) {
                foreach ((array)$values["-$key"] as $key => $value) {
                    if ($value === true || $value === false || $value === null) {
                        $parts[] = $value ? $key : "";
                    }
                    else {
                        foreach ((array)$value as $val) {
                            $parts[] = "$key " . escapeshellarg($val);
                        }
                    }
                }
            }
            else {
                foreach ((array)$values[$key] as $value) {
                    $parts[] = escapeshellarg($value);
                }
            }
            $replacements[$tag] = join(" ", $parts);
        }
        // use strtr behaviour of going through source string once
        $cmd = strtr($cmd, $replacements);
        return (string)shell_exec($cmd);
    }
    /**
program/include/rcube_bc.inc
@@ -38,11 +38,6 @@
    return rcmail::get_instance()->db->table_name($table);
}
function get_sequence_name($sequence)
{
    return rcmail::get_instance()->db->sequence_name($sequence);
}
function rcube_label($p, $domain=null)
{
    return rcmail::get_instance()->gettext($p, $domain);
program/include/rcube_browser.php
@@ -20,8 +20,6 @@
*/
/**
 * rcube_browser
 *
 * Provide details about the client's browser based on the User-Agent header
 *
 * @package Core
program/include/rcube_cache.php
@@ -254,7 +254,7 @@
            }
            else if ($this->type == 'apc') {
                $data = apc_fetch($this->ckey($key));
            }
            }
            if ($data) {
                $md5sum = md5($data);
@@ -294,7 +294,7 @@
                }
                $this->cache[$key]      = $data;
                $this->cache_sums[$key] = $md5sum;
                $this->cache_sums[$key] = $md5sum;
            }
            else {
                $this->cache[$key] = null;
program/include/rcube_charset.php
@@ -181,6 +181,12 @@
        $to   = empty($to) ? strtoupper(RCMAIL_CHARSET) : self::parse_charset($to);
        $from = self::parse_charset($from);
        // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
        // In that case we can just skip the conversion (use UTF-8)
        if ($from == 'UTF-16' && !preg_match('/[^\x00-\x7F]/', $str)) {
            $from = 'UTF-8';
        }
        if ($from == $to || empty($str) || empty($from)) {
            return $str;
        }
program/include/rcube_config.php
@@ -42,6 +42,7 @@
        'default_folders'      => 'default_imap_folders',
        'mail_pagesize'        => 'pagesize',
        'addressbook_pagesize' => 'pagesize',
        'reply_mode'           => 'top_posting',
    );
@@ -324,7 +325,7 @@
        if (strlen($key) != 24) {
            rcube::raise_error(array(
                'code' => 500, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
            ), true, true);
        }
@@ -348,7 +349,7 @@
            else
                rcube::raise_error(array(
                    'code' => 500, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Invalid mail_header_delimiter setting"
                ), true, false);
        }
program/include/rcube_db.php
@@ -576,6 +576,10 @@
            return intval($input);
        }
        if (is_null($input)) {
            return 'NULL';
        }
        // create DB handle if not available
        if (!$this->dbh) {
            $this->db_connect('r');
program/include/rcube_imap.php
@@ -359,11 +359,11 @@
        return array(
            $this->search_string,
            $this->search_set,
            $this->search_charset,
            $this->search_sort_field,
            $this->search_sorted,
        );
            $this->search_set,
            $this->search_charset,
            $this->search_sort_field,
            $this->search_sorted,
        );
    }
@@ -2138,14 +2138,17 @@
    /**
     * Sends the whole message source to stdout
     *
     * @param int  $uid       Message UID
     * @param bool $formatted Enables line-ending formatting
     */
    public function print_raw_body($uid)
    public function print_raw_body($uid, $formatted = true)
    {
        if (!$this->check_connection()) {
            return;
        }
        $this->conn->handlePartBody($this->folder, $uid, true, NULL, NULL, true);
        $this->conn->handlePartBody($this->folder, $uid, true, null, null, true, null, $formatted);
    }
@@ -2217,6 +2220,10 @@
    {
        if (!strlen($folder)) {
            $folder = $this->folder;
        }
        if (!$this->check_connection()) {
            return false;
        }
        // make sure folder exists
@@ -3847,12 +3854,12 @@
    protected function rsort($folder, $delimiter, &$list, &$out)
    {
        while (list($key, $name) = each($list)) {
            if (strpos($name, $folder.$delimiter) === 0) {
                // set the type of folder name variable (#1485527)
                $out[] = (string) $name;
                unset($list[$key]);
                $this->rsort($name, $delimiter, $list, $out);
            }
            if (strpos($name, $folder.$delimiter) === 0) {
                // set the type of folder name variable (#1485527)
                $out[] = (string) $name;
                unset($list[$key]);
                $this->rsort($name, $delimiter, $list, $out);
            }
        }
        reset($list);
    }
program/include/rcube_imap_cache.php
@@ -5,7 +5,7 @@
 | program/include/rcube_imap_cache.php                                  |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -350,11 +350,11 @@
    function get_message($mailbox, $uid, $update = true, $cache = true)
    {
        // Check internal cache
        if ($this->icache['message']
            && $this->icache['message']['mailbox'] == $mailbox
            && $this->icache['message']['object']->uid == $uid
        if ($this->icache['__message']
            && $this->icache['__message']['mailbox'] == $mailbox
            && $this->icache['__message']['object']->uid == $uid
        ) {
            return $this->icache['message']['object'];
            return $this->icache['__message']['object'];
        }
        $sql_result = $this->db->query(
@@ -386,7 +386,7 @@
            // Save current message from internal cache
            $this->save_icache();
            $this->icache['message'] = array(
            $this->icache['__message'] = array(
                'object'  => $message,
                'mailbox' => $mailbox,
                'exists'  => $found,
@@ -459,20 +459,28 @@
     */
    function change_flag($mailbox, $uids, $flag, $enabled = false)
    {
        if (empty($uids)) {
            return;
        }
        $flag = strtoupper($flag);
        $idx  = (int) array_search($flag, $this->flags);
        $uids = (array) $uids;
        if (!$idx) {
            return;
        }
        // Internal cache update
        if ($uids && count($uids) == 1 && ($uid = current($uids))
            && ($message = $this->icache['message'])
            && $message['mailbox'] == $mailbox && $message['object']->uid == $uid
        if (($message = $this->icache['__message'])
            && $message['mailbox'] === $mailbox
            && in_array($message['object']->uid, $uids)
        ) {
            $message['object']->flags[$flag] = $enabled;
            return;
            if (count($uids) == 1) {
                return;
            }
        }
        $this->db->query(
@@ -481,7 +489,7 @@
            .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
            ." WHERE user_id = ?"
                ." AND mailbox = ?"
                .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : "")
                .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "")
                ." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"),
            $this->userid, $mailbox);
    }
@@ -503,10 +511,11 @@
        }
        else {
            // Remove the message from internal cache
            if (!empty($uids) && !is_array($uids) && ($message = $this->icache['message'])
                && $message['mailbox'] == $mailbox && $message['object']->uid == $uids
            if (!empty($uids) && ($message = $this->icache['__message'])
                && $message['mailbox'] === $mailbox
                && in_array($message['object']->uid, (array)$uids)
            ) {
                $this->icache['message'] = null;
                $this->icache['__message'] = null;
            }
            $this->db->query(
@@ -608,13 +617,13 @@
        // get expiration timestamp
        $ts = get_offset_time($ttl, -1);
        $this->db->query("DELETE FROM ".get_table_name('cache_messages')
        $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages')
              ." WHERE changed < " . $this->db->fromunixtime($ts));
        $this->db->query("DELETE FROM ".get_table_name('cache_index')
        $this->db->query("DELETE FROM ".$this->db->table_name('cache_index')
              ." WHERE changed < " . $this->db->fromunixtime($ts));
        $this->db->query("DELETE FROM ".get_table_name('cache_thread')
        $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread')
              ." WHERE changed < " . $this->db->fromunixtime($ts));
    }
@@ -762,6 +771,11 @@
    {
        $object    = $index['object'];
        $is_thread = is_a($object, 'rcube_result_thread');
        // sanity check
        if (empty($object)) {
            return false;
        }
        // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
        $mbox_data = $this->imap->folder_data($mailbox);
@@ -1078,7 +1092,7 @@
    private function save_icache()
    {
        // Save current message from internal cache
        if ($message = $this->icache['message']) {
        if ($message = $this->icache['__message']) {
            // clean up some object's data
            $object = $this->message_object_prepare($message['object']);
@@ -1089,7 +1103,7 @@
                $this->add_message($message['mailbox'], $object, !$message['exists']);
            }
            $this->icache['message']['md5sum'] = $md5sum;
            $this->icache['__message']['md5sum'] = $md5sum;
        }
    }
program/include/rcube_imap_generic.php
@@ -530,6 +530,7 @@
                }
                else {
                    $authc = $user;
                    $user  = '';
                }
                $auth_sasl = Auth_SASL::factory('digestmd5');
                $reply = base64_encode($auth_sasl->getResponse($authc, $pass,
@@ -568,6 +569,7 @@
            }
            else {
                $authc = $user;
                $user  = '';
            }
            $reply = base64_encode($user . chr(0) . $authc . chr(0) . $pass);
@@ -2377,7 +2379,7 @@
        return $this->handlePartBody($mailbox, $id, $is_uid, $part);
    }
    function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL)
    function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL, $formatted=true)
    {
        if (!$this->select($mailbox)) {
            return false;
@@ -2494,7 +2496,7 @@
                        continue;
                    $line = convert_uudecode($line);
                // default
                } else {
                } else if ($formatted) {
                    $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
                }
@@ -2538,7 +2540,7 @@
    {
        unset($this->data['APPENDUID']);
        if (!$mailbox) {
        if ($mailbox === null || $mailbox === '') {
            return false;
        }
@@ -2603,7 +2605,7 @@
    {
        unset($this->data['APPENDUID']);
        if (!$mailbox) {
        if ($mailbox === null || $mailbox === '') {
            return false;
        }
@@ -2612,6 +2614,7 @@
        if (file_exists(realpath($path))) {
            $in_fp = fopen($path, 'r');
        }
        if (!$in_fp) {
            $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
            return false;
program/include/rcube_ldap.php
@@ -139,6 +139,11 @@
                    unset($this->coltypes[$childcol]);  // remove address child col from global coltypes list
                }
            }
            // at least one address type must be specified
            if (empty($this->coltypes['address']['subtypes'])) {
                $this->coltypes['address']['subtypes'] = array('home');
            }
        }
        else if ($this->coltypes['address']) {
            $this->coltypes['address'] += array('type' => 'textarea', 'childs' => null, 'size' => 40);
@@ -767,9 +772,9 @@
        }
        // use VLV pseudo-search for autocompletion
        $rcmail = rcmail::get_instance();
        $rcube = rcube::get_instance();
        if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $rcmail->config->get('contactlist_fields')))
        if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $rcube->config->get('contactlist_fields')))
        {
            // add general filter to query
            if (!empty($this->prop['filter']) && empty($this->filter))
@@ -2027,12 +2032,12 @@
        # a0 = type context-specific/constructed with a length of 06 (6) bytes following
        # 02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
        # 02 = type integer with 2 bytes following (contentCount):  01 00
        # whith a search string present:
        # 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
        # 81 indicates a user string is present where as a a0 indicates just a offset search
        # 81 = type context-specific/constructed with a length of 06 (6) bytes following
        # the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
        # encoding of integer values (note: these values are in
        # two-complement form so since offset will never be negative bit 8 of the
@@ -2042,7 +2047,7 @@
        # of the second (to the left of first octet) octet:
        # a) shall not all be ones; and
        # b) shall not all be zero
        if ($search)
        {
            $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
@@ -2062,7 +2067,7 @@
            // now compute length over $str
            $str = self::_ber_addseq($str, 'a0');
        }
        // now tack on records per page
        $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;
program/include/rcube_message.php
@@ -50,13 +50,14 @@
     */
    private $mime;
    private $opt = array();
    private $inline_parts = array();
    private $parse_alternative = false;
    public $uid = null;
    public $uid;
    public $folder;
    public $headers;
    public $parts = array();
    public $mime_parts = array();
    public $inline_parts = array();
    public $attachments = array();
    public $subject = '';
    public $sender = null;
@@ -68,16 +69,21 @@
     *
     * Provide a uid, and parse message structure.
     *
     * @param string $uid The message UID.
     * @param string $uid    The message UID.
     * @param string $folder Folder name
     *
     * @see self::$app, self::$storage, self::$opt, self::$parts
     */
    function __construct($uid)
    function __construct($uid, $folder = null)
    {
        $this->uid  = $uid;
        $this->app  = rcube::get_instance();
        $this->storage = $this->app->get_storage();
        $this->folder  = strlen($folder) ? $folder : $this->storage->get_folder();
        $this->storage->set_options(array('all_headers' => true));
        // Set current folder
        $this->storage->set_folder($this->folder);
        $this->headers = $this->storage->get_message($uid);
@@ -179,10 +185,12 @@
                }
                return $fp ? true : $part->body;
            }
            // get from IMAP
            $this->storage->set_folder($this->folder);
            return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv);
        } else
            return null;
        }
    }
@@ -637,8 +645,10 @@
    function tnef_decode(&$part)
    {
        // @TODO: attachment may be huge, hadle it via file
        if (!isset($part->body))
        if (!isset($part->body)) {
            $this->storage->set_folder($this->folder);
            $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
        }
        $parts = array();
        $tnef = new tnef_decoder;
@@ -673,8 +683,10 @@
    function uu_decode(&$part)
    {
        // @TODO: messages may be huge, hadle body via file
        if (!isset($part->body))
        if (!isset($part->body)) {
            $this->storage->set_folder($this->folder);
            $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
        }
        $parts = array();
        // FIXME: line length is max.65?
program/include/rcube_mime.php
@@ -541,10 +541,10 @@
                    $prefix = $regs[0];
                    $level = strlen($prefix);
                    $line  = rtrim(substr($line, $level));
                    $line  = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix ");
                    $line  = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix ");
                }
                else if ($line) {
                    $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n");
                    $line = self::wordwrap(rtrim($line), $length - 2, " \r\n");
                    // space-stuffing
                    $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
                }
program/include/rcube_output_html.php
@@ -67,6 +67,11 @@
        $this->set_env('task', $task);
        $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
        // add cookie info
        $this->set_env('cookie_domain', ini_get('session.cookie_domain'));
        $this->set_env('cookie_path', ini_get('session.cookie_path'));
        $this->set_env('cookie_secure', ini_get('session.cookie_secure'));
        // load the correct skin (in case user-defined)
        $skin = $this->config->get('skin');
        $this->set_skin($skin);
@@ -395,7 +400,7 @@
                'line' => __LINE__,
                'file' => __FILE__,
                'message' => 'Error loading template for '.$realname
                ), true, true);
                ), true, $write);
            return false;
        }
@@ -691,6 +696,11 @@
                if ($attrib['name'] || $attrib['command']) {
                    return $this->button($attrib);
                }
                break;
            // frame
            case 'frame':
                return $this->frame($attrib);
                break;
            // show a label
@@ -1270,6 +1280,30 @@
    }
    /**
     * Returns iframe object, registers some related env variables
     *
     * @param array $attrib HTML attributes
     *
     * @return string IFRAME element
     */
    public function frame($attrib)
    {
        if (!$attrib['id']) {
            $attrib['id'] = 'rcmframe';
        }
        if (!$attrib['name']) {
            $attrib['name'] = $attrib['id'];
        }
        $this->set_env('contentframe', $attrib['id']);
        $this->set_env('blankpage', $attrib['src'] ? $this->abs_url($attrib['src']) : 'program/resources/blank.gif');
        return html::iframe($attrib);
    }
    /*  ************* common functions delivering gui objects **************  */
@@ -1378,6 +1412,9 @@
        if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
            $url = $_SERVER['QUERY_STRING'];
        // Disable autocapitalization on iPad/iPhone (#1488609)
        $attrib['autocapitalize'] = 'off';
        // set atocomplete attribute
        $user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
        $host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
program/include/rcube_plugin.php
@@ -336,7 +336,7 @@
  public function local_skin_path()
  {
    $rcmail = rcube::get_instance();
    foreach (array($rcmail->config->get('skin'),'default') as $skin) {
    foreach (array($rcmail->config->get('skin'), 'larry') as $skin) {
      $skin_path = 'skins/' . $skin;
      if (is_dir(realpath(slashify($this->home) . $skin_path)))
        break;
program/include/rcube_plugin_api.php
@@ -32,12 +32,12 @@
class rcube_plugin_api
{
  static private $instance;
  public $dir;
  public $url = 'plugins/';
  public $task = '';
  public $output;
  public $handlers = array();
  private $plugins = array();
  private $tasks = array();
program/include/rcube_session.php
@@ -531,7 +531,7 @@
  public function set_keep_alive($keep_alive)
  {
    $this->keep_alive = $keep_alive;
    if ($this->lifetime < $keep_alive)
        $this->set_lifetime($keep_alive + 30);
  }
@@ -551,7 +551,7 @@
  {
    return $this->ip;
  }
  /**
   * Setter for cookie encryption secret
   */
@@ -568,7 +568,8 @@
  {
    $this->ip_check = $check;
  }
  /**
   * Setter for the cookie name used for session cookie
   */
@@ -605,7 +606,7 @@
          $result = true;
        }
      }
    }
    }
    if (!$result)
      $this->log("Session authentication failed for " . $this->key . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
@@ -637,7 +638,7 @@
  }
  /**
   *
   * Writes debug information to the log
   */
  function log($line)
  {
program/include/rcube_shared.inc
@@ -108,11 +108,11 @@
/**
 * Remove slash at the end of the string
 * Remove slashes at the end of the string
 */
function unslashify($str)
{
  return preg_replace('/\/$/', '', $str);
  return preg_replace('/\/+$/', '', $str);
}
@@ -231,7 +231,7 @@
{
    $keys = array();
    if (!empty($array)) {
    if (!empty($array) && is_array($array)) {
        foreach ($array as $key => $child) {
            $keys[] = $key;
            foreach (array_keys_recursive($child) as $val) {
@@ -255,7 +255,7 @@
/**
 * Remove single and double quotes from given string
 * Remove single and double quotes from a given string
 *
 * @param string Input value
 *
@@ -299,6 +299,29 @@
        }
        return "$name <$email>";
    }
    return $email;
}
/**
 * Format e-mail address
 *
 * @param string $email E-mail address
 *
 * @return string Formatted e-mail address
 */
function format_email($email)
{
    $email = trim($email);
    $parts = explode('@', $email);
    $count = count($parts);
    if ($count > 1) {
        $parts[$count-1] = mb_strtolower($parts[$count-1]);
        $email = implode('@', $parts);
    }
    return $email;
@@ -399,7 +422,6 @@
{
    $filename = preg_replace(
        array(
            '/MDB2_(.+)/',
            '/Mail_(.+)/',
            '/Net_(.+)/',
            '/Auth_(.+)/',
@@ -407,7 +429,6 @@
            '/^utf8$/',
        ),
        array(
            'Mail/\\1',
            'Mail/\\1',
            'Net/\\1',
            'Auth/\\1',
program/include/rcube_smtp.php
@@ -423,7 +423,7 @@
        $lines[] = $key . ': ' . $value;
      }
    }
    return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
  }
program/include/rcube_storage.php
@@ -195,7 +195,7 @@
     */
    public function set_folder($folder)
    {
        if ($this->folder == $folder) {
        if ($this->folder === $folder) {
            return;
        }
@@ -502,8 +502,11 @@
    /**
     * Sends the whole message source to stdout
     *
     * @param int  $uid       Message UID
     * @param bool $formatted Enables line-ending formatting
     */
    abstract function print_raw_body($uid);
    abstract function print_raw_body($uid, $formatted = true);
    /**
program/include/rcube_user.php
@@ -443,7 +443,7 @@
        }
        $data = $rcube->plugins->exec_hook('user_create',
            array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
            array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
        // plugin aborted this operation
        if ($data['abort'])
program/include/rcube_utils.php
@@ -84,17 +84,17 @@
        // from PEAR::Validate
        $regexp = '&^(?:
            ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                              #1 quoted name
            ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))     #2 OR dot-atom (RFC5322)
            $&xi';
            ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
            ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
            $&xi';
        if (!preg_match($regexp, $local_part)) {
            return false;
        }
        // Check domain part
        if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) {
            return true; // IP address
        // Validate domain part
        if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
            return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address
        }
        else {
            // If not an IP address
@@ -108,6 +108,11 @@
                if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
                    return false;
                }
            }
            // last domain part
            if (preg_match('/[^a-zA-Z]/', array_pop($domain_array))) {
                return false;
            }
            $rcube = rcube::get_instance();
@@ -141,6 +146,52 @@
        return false;
    }
    /**
     * Validates IPv4 or IPv6 address
     *
     * @param string $ip IP address in v4 or v6 format
     *
     * @return bool True if the address is valid
     */
    public static function check_ip($ip)
    {
        // IPv6, but there's no build-in IPv6 support
        if (strpos($ip, ':') !== false && !defined('AF_INET6')) {
            $parts = explode(':', $domain_part);
            $count = count($parts);
            if ($count > 8 || $count < 2) {
                return false;
            }
            foreach ($parts as $idx => $part) {
                $length = strlen($part);
                if (!$length) {
                    // there can be only one ::
                    if ($found_empty) {
                        return false;
                    }
                    $found_empty = true;
                }
                // last part can be an IPv4 address
                else if ($idx == $count - 1) {
                    if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
                        return @inet_pton($part) !== false;
                    }
                }
                else if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
                    return false;
                }
            }
            return true;
        }
        return @inet_pton($ip) !== false;
    }
    /**
     * Check whether the HTTP referer matches the current request
     *
@@ -149,8 +200,8 @@
    public static function check_referer()
    {
        $uri = parse_url($_SERVER['REQUEST_URI']);
        $referer = parse_url(rcube_request_header('Referer'));
        return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path'];
        $referer = parse_url(self::request_header('Referer'));
        return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path'];
    }
program/include/rcube_vcard.php
@@ -313,7 +313,7 @@
      case 'birthday':
      case 'anniversary':
        if (($val = rcube_strtotime($value)) && ($fn = self::$fieldmap[$field]))
        if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
          $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
        break;
@@ -555,6 +555,7 @@
          if ((list($key, $value) = explode('=', $attr)) && $value) {
            $value = trim($value);
            if ($key == 'ENCODING') {
              $value = strtoupper($value);
              // add next line(s) to value string if QP line end detected
              if ($value == 'QUOTED-PRINTABLE') {
                while (preg_match('/=$/', $lines[$i]))
program/js/app.js
@@ -976,7 +976,7 @@
            // do reply-list, when list is detected and popup menu wasn't used 
            url._all = (!props && this.commands['reply-list'] ? 'list' : 'all');
          else if (command == 'reply-list')
            url._all = list;
            url._all = 'list';
          this.goto_url('compose', url, true);
        }
@@ -1447,29 +1447,21 @@
  this.doc_mouse_up = function(e)
  {
    var model, list, li, id;
    var model, list, id;
    // ignore event if jquery UI dialog is open
    if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
      return;
    if (list = this.message_list) {
      if (!rcube_mouse_is_over(e, list.list.parentNode))
        list.blur();
      else
        list.focus();
    if (list = this.message_list)
      model = this.env.mailboxes;
    }
    else if (list = this.contact_list) {
      if (!rcube_mouse_is_over(e, list.list.parentNode))
        list.blur();
      else
        list.focus();
    else if (list = this.contact_list)
      model = this.env.contactfolders;
    }
    else if (this.ksearch_value) {
    else if (this.ksearch_value)
      this.ksearch_blur();
    }
    if (list && !rcube_mouse_is_over(e, list.list.parentNode))
      list.blur();
    // handle mouse release when dragging
    if (this.drag_active && model && this.env.last_folder_target) {
@@ -1546,14 +1538,17 @@
    if (list.multi_selecting || !this.env.contentframe)
      return;
    if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) {
      if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) {
        if (this.preview_timer)
          clearTimeout(this.preview_timer);
        if (this.preview_read_timer)
          clearTimeout(this.preview_read_timer);
        this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
      }
    if (list.get_single_selection())
      return;
    var win = this.get_frame_window(this.env.contentframe);
    if (win && win.location.href.indexOf(this.env.blankpage)>=0) {
      if (this.preview_timer)
        clearTimeout(this.preview_timer);
      if (this.preview_read_timer)
        clearTimeout(this.preview_read_timer);
      this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
    }
  };
@@ -1918,12 +1913,12 @@
    if (!id)
      return;
    var target = window,
    var win, target = window,
      action = preview ? 'preview': 'show',
      url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
    if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (preview && (win = this.get_frame_window(this.env.contentframe))) {
      target = win;
      url += '&_framed=1';
    }
@@ -1960,18 +1955,35 @@
  this.show_contentframe = function(show)
  {
    var frm, win;
    if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) {
      if (!show && (win = window.frames[this.env.contentframe])) {
    var frame, win, name = this.env.contentframe;
    if (name && (frame = this.get_frame_element(name))) {
      if (!show && (win = this.get_frame_window(name))) {
        if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
          win.location.href = this.env.blankpage;
      }
      else if (!bw.safari && !bw.konq)
        frm[show ? 'show' : 'hide']();
      }
        $(frame)[show ? 'show' : 'hide']();
    }
    if (!show && this.busy)
      this.set_busy(false, null, this.env.frame_lock);
  };
  this.get_frame_element = function(id)
  {
    var frame;
    if (id && (frame = document.getElementById(id)))
      return frame;
  };
  this.get_frame_window = function(id)
  {
    var frame = this.get_frame_element(id);
    if (frame && frame.name && window.frames)
      return window.frames[frame.name];
  };
  this.lock_frame = function()
@@ -2017,7 +2029,7 @@
  // list messages of a specific mailbox
  this.list_mailbox = function(mbox, page, sort, url)
  {
    var target = window;
    var win, target = window;
    if (typeof url != 'object')
      url = {};
@@ -2056,8 +2068,8 @@
      return;
    }
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (win = this.get_frame_window(this.env.contentframe)) {
      target = win;
      url._framed = 1;
    }
@@ -2652,34 +2664,37 @@
  // set a specific flag to one or more messages
  this.mark_message = function(flag, uid)
  {
    var a_uids = [], r_uids = [], len, n, id,
      selection = this.message_list ? this.message_list.get_selection() : [];
    var a_uids = [], r_uids = [], len, n, id, selection,
      list = this.message_list;
    if (uid)
      a_uids[0] = uid;
    else if (this.env.uid)
      a_uids[0] = this.env.uid;
    else if (this.message_list) {
    else if (list) {
      selection = list.get_selection();
      for (n=0, len=selection.length; n<len; n++) {
          a_uids.push(selection[n]);
      }
    }
    if (!this.message_list)
    if (!list)
      r_uids = a_uids;
    else
    else {
      list.focus();
      for (n=0, len=a_uids.length; n<len; n++) {
        id = a_uids[n];
        if ((flag=='read' && this.message_list.rows[id].unread)
            || (flag=='unread' && !this.message_list.rows[id].unread)
            || (flag=='delete' && !this.message_list.rows[id].deleted)
            || (flag=='undelete' && this.message_list.rows[id].deleted)
            || (flag=='flagged' && !this.message_list.rows[id].flagged)
            || (flag=='unflagged' && this.message_list.rows[id].flagged))
        if ((flag=='read' && list.rows[id].unread)
            || (flag=='unread' && !list.rows[id].unread)
            || (flag=='delete' && !list.rows[id].deleted)
            || (flag=='undelete' && list.rows[id].deleted)
            || (flag=='flagged' && !list.rows[id].flagged)
            || (flag=='unflagged' && list.rows[id].flagged))
        {
          r_uids.push(id);
        }
      }
    }
    // nothing to do
    if (!r_uids.length && !this.select_all_mode)
@@ -3298,8 +3313,7 @@
      input_message = $("[name='_message']"),
      message = input_message.val(),
      is_html = ($("input[name='_is_html']").val() == '1'),
      sig = this.env.identity,
      sig_separator = this.env.sig_above && (this.env.compose_mode == 'reply' || this.env.compose_mode == 'forward') ? '---' : '-- ';
      sig = this.env.identity;
    // enable manual signature insert
    if (this.env.signatures && this.env.signatures[id]) {
@@ -3312,12 +3326,8 @@
    if (!is_html) {
      // remove the 'old' signature
      if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
        sig = this.env.signatures[sig].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
        sig = this.env.signatures[sig].text;
        sig = sig.replace(/\r\n/g, '\n');
        if (!sig.match(/^--[ -]\n/m))
          sig = sig_separator + '\n' + sig;
        p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
        if (p >= 0)
@@ -3325,11 +3335,8 @@
      }
      // add the new signature string
      if (show_sig && this.env.signatures && this.env.signatures[id]) {
        sig = this.env.signatures[id]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
        sig = this.env.signatures[id].text;
        sig = sig.replace(/\r\n/g, '\n');
        if (!sig.match(/^--[ -]\n/m))
          sig = sig_separator + '\n' + sig;
        if (this.env.sig_above) {
          if (p >= 0) { // in place of removed signature
@@ -3394,21 +3401,8 @@
        }
      }
      if (this.env.signatures[id]) {
        if (this.env.signatures[id].is_html) {
          sig = this.env.signatures[id].text;
          if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
            sig = sig_separator + '<br />' + sig;
        }
        else {
          sig = this.env.signatures[id].text;
          if (!sig.match(/^--[ -]\r?\n/m))
            sig = sig_separator + '\n' + sig;
          sig = '<pre>' + sig + '</pre>';
        }
        sigElem.innerHTML = sig;
      }
      if (this.env.signatures[id])
        sigElem.innerHTML = this.env.signatures[id].html;
    }
    this.env.identity = id;
@@ -4031,8 +4025,7 @@
    // if a group is currently selected, and there is at least one contact selected
    // thend we can enable the group-remove-selected command
    this.enable_command('group-remove-selected', typeof this.env.group != 'undefined' && list.selection.length > 0);
    this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0);
    this.enable_command('compose', this.env.group || list.selection.length > 0);
    this.enable_command('edit', id && writable);
    this.enable_command('delete', list.selection.length && writable);
@@ -4042,7 +4035,7 @@
  this.list_contacts = function(src, group, page)
  {
    var folder, url = {},
    var win, folder, url = {},
      target = window;
    if (!src)
@@ -4074,8 +4067,8 @@
      return;
    }
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (win = this.get_frame_window(this.env.contentframe)) {
      target = win;
      url._framed = 1;
    }
@@ -4131,11 +4124,11 @@
  // load contact record
  this.load_contact = function(cid, action, framed)
  {
    var url = {}, target = window;
    var win, url = {}, target = window;
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    if (win = this.get_frame_window(this.env.contentframe)) {
      url._framed = 1;
      target = window.frames[this.env.contentframe];
      target = win;
      this.show_contentframe(true);
      // load dummy content
@@ -4753,11 +4746,11 @@
  // load advanced search page
  this.advanced_search = function()
  {
    var url = {_form: 1, _action: 'search'}, target = window;
    var win, url = {_form: 1, _action: 'search'}, target = window;
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    if (win = this.get_frame_window(this.env.contentframe)) {
      url._framed = 1;
      target = window.frames[this.env.contentframe];
      target = win;
      this.contact_list.clear_selection();
    }
@@ -4879,13 +4872,13 @@
  // preferences section select and load options frame
  this.section_select = function(list)
  {
    var id = list.get_single_selection(), target = window,
    var win, id = list.get_single_selection(), target = window,
      url = {_action: 'edit-prefs', _section: id};
    if (id) {
      if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      if (win = this.get_frame_window(this.env.contentframe)) {
        url._framed = 1;
        target = window.frames[this.env.contentframe];
        target = win;
      }
      this.location_href(url, target, true);
    }
@@ -4908,13 +4901,12 @@
    if (action == 'edit-identity' && (!id || id == this.env.iid))
      return false;
    var target = window,
    var win, target = window,
      url = {_action: action, _iid: id};
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    if (win = this.get_frame_window(this.env.contentframe)) {
      url._framed = 1;
      target = window.frames[this.env.contentframe];
      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
      target = win;
    }
    if (action && (id || action == 'add-identity')) {
@@ -5290,14 +5282,14 @@
  // when user select a folder in manager
  this.show_folder = function(folder, path, force)
  {
    var target = window,
    var win, target = window,
      url = '&_action=edit-folder&_mbox='+urlencode(folder);
    if (path)
      url += '&_path='+urlencode(path);
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
      target = window.frames[this.env.contentframe];
    if (win = this.get_frame_window(this.env.contentframe)) {
      target = win;
      url += '&_framed=1';
    }
@@ -6607,6 +6599,12 @@
    return 0;
  };
  // Cookie setter
  this.set_cookie = function(name, value, expires)
  {
    setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
  }
}  // end object rcube_webmail
@@ -6637,6 +6635,8 @@
  }
};
rcube_webmail.prototype.get_cookie = getCookie;
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
program/js/common.js
@@ -494,12 +494,15 @@
      atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+',
      quoted_pair = '\\x5c[\\x00-\\x7f]',
      quoted_string = '\\x22('+qtext+'|'+quoted_pair+')*\\x22',
      ipv4 = '\\[(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\\]',
      ipv6 = '\\[IPv6:[0-9a-f:.]+\\]',
      ip_addr = '(' + ipv4 + ')|(' + ipv6 + ')',
      // Use simplified domain matching, because we need to allow Unicode characters here
      // So, e-mail address should be validated also on server side after idn_to_ascii() use
      //domain_literal = '\\x5b('+dtext+'|'+quoted_pair+')*\\x5d',
      //sub_domain = '('+atom+'|'+domain_literal+')',
      // allow punycode/unicode top-level domain
      domain = '([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})',
      domain = '(('+ip_addr+')|(([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})))',
      // ICANN e-mail test (http://idn.icann.org/E-mail_test)
      icann_domains = [
        '\\u0645\\u062b\\u0627\\u0644\\x2e\\u0625\\u062e\\u062a\\u0628\\u0627\\u0631',
@@ -526,7 +529,6 @@
  return false;
};
// recursively copy an object
function rcube_clone_object(obj)
@@ -635,6 +637,7 @@
  return unescape(dc.substring(begin + prefix.length, end));
};
// deprecated aliases, to be removed, use rcmail.set_cookie/rcmail.get_cookie
roundcube_browser.prototype.set_cookie = setCookie;
roundcube_browser.prototype.get_cookie = getCookie;
program/js/googiespell.js
@@ -25,7 +25,7 @@
function GoogieSpell(img_dir, server_url, has_dict)
{
    var ref = this,
        cookie_value = getCookie('language');
        cookie_value = rcmail.get_cookie('language');
    GOOGIE_CUR_LANG = cookie_value != null ? cookie_value : GOOGIE_DEFAULT_LANG;
@@ -150,7 +150,7 @@
    //Set cookie
    var now = new Date();
    now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
    setCookie('language', lan_code, now);
    rcmail.set_cookie('language', lan_code, now);
};
this.setForceWidthHeight = function(width, height)
program/js/list.js
@@ -231,8 +231,8 @@
    }
  }
  // Un-focus already focused elements
  $(document.activeElement).blur();
  // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620)
  $(':focus:not(body)').blur();
  $('iframe').each(function() { this.blur(); });
  if (e || (e = window.event))
program/lib/html2text.php
@@ -89,7 +89,7 @@
 *  out that extra spaces should be compressed--a problem addressed with
 *  Marcus Bointon's fixes but that I had not yet incorporated.
 *
 *    Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
 *  Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
 *  suggesting a valuable fix with <a> tag handling.
 *
 *  Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
@@ -200,7 +200,7 @@
    var $ent_search = array(
        '/&(nbsp|#160);/i',                      // Non-breaking space
        '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
                                                 // Double quotes
                                         // Double quotes
        '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
        '/&gt;/i',                               // Greater-than
        '/&lt;/i',                               // Less-than
@@ -437,11 +437,11 @@
    function set_base_url( $url = '' )
    {
        if ( empty($url) ) {
            if ( !empty($_SERVER['HTTP_HOST']) ) {
                $this->url = 'http://' . $_SERVER['HTTP_HOST'];
            } else {
                $this->url = '';
            }
            if ( !empty($_SERVER['HTTP_HOST']) ) {
                $this->url = 'http://' . $_SERVER['HTTP_HOST'];
            } else {
                $this->url = '';
            }
        } else {
            // Strip any trailing slashes for consistency (relative
            // URLs may already start with a slash like "/file.html")
@@ -515,7 +515,7 @@
        $text = preg_replace($this->ent_search, $this->ent_replace, $text);
        // Replace known html entities
        $text = html_entity_decode($text, ENT_COMPAT, 'UTF-8');
        $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
        // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
        $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
@@ -535,7 +535,7 @@
        // for PHP versions >= 4.0.2. Default width is 75
        // If width is 0 or less, don't wrap the text.
        if ( $this->width > 0 ) {
            $text = wordwrap($text, $this->width);
            $text = wordwrap($text, $this->width);
        }
    }
@@ -554,16 +554,16 @@
     */
    function _build_link_list( $link, $display )
    {
        if (!$this->_do_links || empty($link)) {
            return $display;
        }
        // Ignored link types
        if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
            return $display;
        if (!$this->_do_links || empty($link)) {
            return $display;
        }
        if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
        // Ignored link types
        if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
            return $display;
        }
        if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
            $url = $link;
        }
        else {
program/lib/washtml.php
@@ -214,8 +214,11 @@
      $key = strtolower($key);
      $value = $node->getAttribute($key);
      if (isset($this->_html_attribs[$key]) ||
         ($key == 'href' && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value)))
         ($key == 'href' && !preg_match('!^javascript!i', $value)
           && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value))
      ) {
        $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
      }
      else if ($key == 'style' && ($style = $this->wash_style($value))) {
        $quot = strpos($style, '"') !== false ? "'" : '"';
        $t .= ' style=' . $quot . $style . $quot;
@@ -237,7 +240,8 @@
        else if (preg_match('/^data:.+/i', $value)) { // RFC2397
          $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
        }
      } else
      }
      else
        $washed .= ($washed?' ':'') . $key;
    }
    return $t . ($washed && $this->config['show_washed']?' x-washed="'.$washed.'"':'');
program/localization/en_US/labels.inc
@@ -1,7 +1,6 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | language/en_US/labels.inc                                             |
 |                                                                       |
@@ -15,9 +14,6 @@
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 +-----------------------------------------------------------------------+
 @version $Id$
*/
$labels = array();
@@ -163,6 +159,7 @@
$labels['flagged'] = 'Flagged';
$labels['unanswered'] = 'Unanswered';
$labels['deleted'] = 'Deleted';
$labels['undeleted'] = 'Not deleted';
$labels['invert'] = 'Invert';
$labels['filter'] = 'Filter';
$labels['list'] = 'List';
@@ -384,7 +381,8 @@
$labels['signature'] = 'Signature';
$labels['dstactive']  = 'Daylight saving time';
$labels['htmleditor'] = 'Compose HTML messages';
$labels['htmlonreply'] = 'on reply to HTML message only';
$labels['htmlonreply'] = 'on reply to HTML message';
$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message';
$labels['htmlsignature'] = 'HTML signature';
$labels['previewpane'] = 'Show preview pane';
$labels['skin'] = 'Interface skin';
@@ -432,8 +430,9 @@
$labels['newmessage'] = 'New Message';
$labels['signatureoptions'] = 'Signature Options';
$labels['whenreplying'] = 'When replying';
$labels['replytopposting'] = 'start new message above original';
$labels['replybottomposting'] = 'start new message below original';
$labels['replyempty'] = 'do not quote the original message';
$labels['replytopposting'] = 'start new message above the quote';
$labels['replybottomposting'] = 'start new message below the quote';
$labels['replyremovesignature'] = 'When replying remove original signature from message';
$labels['autoaddsignature'] = 'Automatically add signature';
$labels['newmessageonly'] = 'new message only';
program/localization/pl_PL/labels.inc
@@ -134,6 +134,7 @@
$labels['flagged'] = 'Oznaczone';
$labels['unanswered'] = 'Bez odpowiedzi';
$labels['deleted'] = 'Usunięte';
$labels['undeleted'] = 'Nieusunięte';
$labels['invert'] = 'Odwróć';
$labels['filter'] = 'Filtr';
$labels['list'] = 'Lista';
program/steps/addressbook/export.inc
@@ -86,7 +86,7 @@
        foreach ($row as $key => $values) {
            list($field, $section) = explode(':', $key);
            foreach ((array)$values as $value) {
                if (is_array($value) || strlen($value))
                if (is_array($value) || @strlen($value))
                    $vcard->set($field, $value, strtoupper($section));
            }
        }
program/steps/addressbook/search.inc
@@ -237,9 +237,12 @@
    $OUTPUT->command('set_env', 'source', '');
    $OUTPUT->command('set_env', 'group', '');
    // unselect currently selected directory/group
    if (!$sid)
    if (!$sid) {
        // unselect currently selected directory/group
        $OUTPUT->command('unselect_directory');
        // enable "Save search" command
        $OUTPUT->command('enable_command', 'search-create', true);
    }
    $OUTPUT->command('update_group_commands');
    // send response
program/steps/mail/compose.inc
@@ -139,7 +139,7 @@
// set current mailbox in client environment
$OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder());
$OUTPUT->set_env('sig_above', $RCMAIL->config->get('sig_above', false));
$OUTPUT->set_env('top_posting', $RCMAIL->config->get('top_posting', false));
$OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0);
$OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ',')));
// default font for HTML editor
@@ -252,7 +252,8 @@
if (count($MESSAGE->identities))
{
  foreach ($MESSAGE->identities as $idx => $ident) {
    $email = mb_strtolower(rcube_idn_to_utf8($ident['email']));
    $ident['email'] = format_email($ident['email']);
    $email = format_email(rcube_idn_to_utf8($ident['email']));
    $MESSAGE->identities[$idx]['email_ascii'] = $ident['email'];
    $MESSAGE->identities[$idx]['ident']       = format_email_recipient($ident['email'], $ident['name']);
@@ -277,7 +278,7 @@
    $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
    foreach ($a_to as $addr) {
      if (!empty($addr['mailto'])) {
        $a_recipients[] = strtolower($addr['mailto']);
        $a_recipients[] = format_email($addr['mailto']);
        $a_names[]      = $addr['name'];
      }
    }
@@ -286,7 +287,7 @@
      $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
      foreach ($a_cc as $addr) {
        if (!empty($addr['mailto'])) {
          $a_recipients[] = strtolower($addr['mailto']);
          $a_recipients[] = format_email($addr['mailto']);
          $a_names[]      = $addr['name'];
        }
      }
@@ -294,16 +295,12 @@
  }
  $from_idx         = null;
  $default_identity = null;
  $found_idx        = null;
  $default_identity = 0; // default identity is always first on the list
  $return_path      = $MESSAGE->headers->others['return-path'];
  // Select identity
  foreach ($MESSAGE->identities as $idx => $ident) {
    // save default identity ID
    if ($ident['standard']) {
      $default_identity = $idx;
    }
    // use From header
    if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
      if ($MESSAGE->headers->from == $ident['ident']) {
@@ -318,11 +315,20 @@
    }
    // use replied message recipients
    else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
      // match identity name, prefer default identity
      if ($from_idx === null || ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name'])) {
      if ($found_idx === null) {
        $found_idx = $idx;
      }
      // match identity name
      if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
        $from_idx = $idx;
        break;
      }
    }
  }
  // If matching by name+address doesn't found any amtches, get first found address (identity)
  if ($from_idx === null) {
    $from_idx = $found_idx;
  }
  // Fallback using Return-Path
@@ -335,12 +341,7 @@
    }
  }
  // Still no ID, use default/first identity
  if ($from_idx === null) {
    $from_idx = $default_identity !== null ? $default_identity : key(reset($MESSAGE->identities));
  }
  $ident   = $MESSAGE->identities[$from_idx];
  $ident   = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity];
  $from_id = $ident['identity_id'];
  $MESSAGE->compose['from_email'] = $ident['email'];
@@ -433,7 +434,7 @@
      if (empty($addr_part['mailto']))
        continue;
      $mailto = mb_strtolower(rcube_idn_to_utf8($addr_part['mailto']));
      $mailto = format_email(rcube_idn_to_utf8($addr_part['mailto']));
      if (!in_array($mailto, $a_recipients)
        && ($header == 'to' || empty($MESSAGE->compose['from_email']) || $mailto != $MESSAGE->compose['from_email'])
@@ -529,7 +530,7 @@
function rcmail_compose_header_from($attrib)
{
  global $MESSAGE, $OUTPUT;
  global $MESSAGE, $OUTPUT, $RCMAIL, $compose_mode;
  // pass the following attributes to the form class
  $field_attrib = array('name' => '_from');
@@ -540,6 +541,8 @@
  if (count($MESSAGE->identities))
  {
    $a_signatures = array();
    $separator    = $RCMAIL->config->get('sig_above')
      && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD) ? '---' : '-- ';
    $field_attrib['onchange'] = JS_OBJECT_NAME.".change_identity(this)";
    $select_from = new html_select($field_attrib);
@@ -553,13 +556,27 @@
      // add signature to array
      if (!empty($sql_arr['signature']) && empty($COMPOSE['param']['nosig']))
      {
        $a_signatures[$identity_id]['text'] = $sql_arr['signature'];
        $a_signatures[$identity_id]['is_html'] = ($sql_arr['html_signature'] == 1) ? true : false;
        if ($a_signatures[$identity_id]['is_html'])
        {
            $h2t = new html2text($a_signatures[$identity_id]['text'], false, false);
            $a_signatures[$identity_id]['plain_text'] = trim($h2t->get_text());
        $text = $html = $sql_arr['signature'];
        if ($sql_arr['html_signature']) {
            $h2t  = new html2text($sql_arr['signature'], false, false);
            $text = trim($h2t->get_text());
        }
        else {
            $html = htmlentities($html, ENT_NOQUOTES, RCMAIL_CHARSET);
        }
        if (!preg_match('/^--[ -]\r?\n/m', $text)) {
            $text = $separator . "\n" . $text;
            $html = $separator . "<br>" . $html;
        }
        if (!$sql_arr['html_signature']) {
            $html = "<pre>" . $html . "</pre>";
        }
        $a_signatures[$identity_id]['text'] = $text;
        $a_signatures[$identity_id]['html'] = $html;
      }
    }
@@ -593,9 +610,12 @@
    $useHtml = $MESSAGE->has_html_part(false);
  }
  else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
    $useHtml = ($html_editor == 1 || ($html_editor == 2 && $MESSAGE->has_html_part(false)));
    $useHtml = ($html_editor == 1 || ($html_editor >= 2 && $MESSAGE->has_html_part(false)));
  }
  else { // RCUBE_COMPOSE_FORWARD or NEW
  else if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
    $useHtml = ($html_editor == 1 || ($html_editor == 3 && $MESSAGE->has_html_part(false)));
  }
  else {
    $useHtml = ($html_editor == 1);
  }
@@ -624,7 +644,7 @@
      rcmail_write_forward_attachment($MESSAGE);
  }
  // reply/edit/draft/forward
  else if ($compose_mode) {
  else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || $RCMAIL->config->get('reply_mode') != -1)) {
    $isHtml = rcmail_compose_editor_mode();
    if (!empty($MESSAGE->parts)) {
@@ -889,8 +909,9 @@
    $prefix .= "\n";
    $suffix = '';
    if ($RCMAIL->config->get('top_posting'))
    if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
      $prefix = "\n\n\n" . $prefix;
    }
  }
  else {
    // save inline images to files
@@ -904,7 +925,7 @@
    $prefix = '<p>' . Q($prefix) . "</p>\n";
    $prefix .= '<blockquote>';
    if ($RCMAIL->config->get('top_posting')) {
    if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
      $prefix = '<br>' . $prefix;
      $suffix = '</blockquote>';
    }
program/steps/mail/func.inc
@@ -90,11 +90,13 @@
  // set current mailbox and some other vars in client environment
  $OUTPUT->set_env('mailbox', $mbox_name);
  $OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize());
  $OUTPUT->set_env('quota', $RCMAIL->storage->get_capability('QUOTA'));
  $OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter());
  $OUTPUT->set_env('threading', $threading);
  $OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD'));
  $OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
  if ($RCMAIL->storage->get_capability('QUOTA')) {
    $OUTPUT->set_env('quota', true);
  }
  if ($CONFIG['delete_junk'])
    $OUTPUT->set_env('delete_junk', true);
@@ -1053,12 +1055,17 @@
  global $OUTPUT;
  $html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
  $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
  if (!get_boolean($attrib['no-switch'])) {
    $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
  }
  unset($attrib['no-switch']);
  $OUTPUT->add_gui_object('all_headers_row', 'all-headers');
  $OUTPUT->add_gui_object('all_headers_box', 'headers-source');
  return html::div($attrib, $html);
  return count($attrib) > 1 ? html::div($attrib, $html) : $html;
}
@@ -1712,8 +1719,10 @@
  $select_filter->add(rcube_label('unread'), 'UNSEEN');
  $select_filter->add(rcube_label('flagged'), 'FLAGGED');
  $select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
  if (!$CONFIG['skip_deleted'])
  if (!$CONFIG['skip_deleted']) {
    $select_filter->add(rcube_label('deleted'), 'DELETED');
    $select_filter->add(rcube_label('undeleted'), 'UNDELETED');
  }
  $select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
  $select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
  $select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
program/steps/mail/headers.inc
@@ -24,7 +24,8 @@
    $source = $RCMAIL->storage->get_raw_headers($uid);
    if ($source !== false) {
        $source = htmlspecialchars(trim($source));
        $source = trim(rcube_charset::clean($source));
        $source = htmlspecialchars($source);
        $source = preg_replace(
            array(
                '/\n[\t\s]+/',
program/steps/mail/sendmail.inc
@@ -511,14 +511,9 @@
  $h2t = new html2text($plugin['body'], false, true, 0);
  $plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n");
  $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
  if (!$plainTextPart) {
    // empty message body breaks attachment handling in drafts
    $plainTextPart = "\r\n";
  }
  else {
    // make sure all line endings are CRLF (#1486712)
    $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
  }
  // make sure all line endings are CRLF (#1486712)
  $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
  $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
    array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
@@ -542,10 +537,6 @@
    $message_body = rc_wordwrap($message_body, $LINE_LENGTH, "\r\n");
  $message_body = wordwrap($message_body, 998, "\r\n", true);
  if (!strlen($message_body)) {
    // empty message body breaks attachment handling in drafts
    $message_body = "\r\n";
  }
  $MAIL_MIME->setTXTBody($message_body, false, true);
}
program/steps/mail/viewsource.inc
@@ -44,7 +44,7 @@
    header("Content-Disposition: attachment; filename=\"$filename\"");
  }
  $RCMAIL->storage->print_raw_body($uid);
  $RCMAIL->storage->print_raw_body($uid, empty($_GET['_save']));
}
else
{
program/steps/settings/folders.inc
@@ -44,8 +44,8 @@
        if ($result) {
            // Handle subscription of protected folder (#1487656)
            if ($CONFIG['protect_default_folders'] == true
                && in_array($mbox, $CONFIG['default_folders'])
            if ($RCMAIL->config->get('protect_default_folders')
                && in_array($mbox, (array)$RCMAIL->config->get('default_folders'))
            ) {
                $OUTPUT->command('disable_subscription', $mbox);
            }
@@ -321,8 +321,8 @@
                }
            }
        }
        // check if the folder is shared, then disable subscription option on it
        if (!$disabled && $folder['virtual'] && !empty($namespace)) {
        // check if the folder is shared, then disable subscription option on it (if not subscribed already)
        if (!$disabled && !$subscribed && $folder['virtual'] && !empty($namespace)) {
            $tmp_ns = array_merge((array)$namespace['other'], (array)$namespace['shared']);
            foreach ($tmp_ns as $item) {
                if (strpos($folder['id'], $item[0]) === 0) {
@@ -411,8 +411,10 @@
$OUTPUT->set_pagetitle(rcube_label('folders'));
$OUTPUT->include_script('list.js');
$OUTPUT->set_env('quota', $STORAGE->get_capability('QUOTA'));
$OUTPUT->set_env('prefix_ns', $STORAGE->get_namespace('prefix'));
if ($STORAGE->get_capability('QUOTA')) {
    $OUTPUT->set_env('quota', true);
}
// add some labels to client
$OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'folderdeleting',
program/steps/settings/func.inc
@@ -470,6 +470,7 @@
      $select_htmleditor->add(rcube_label('never'), 0);
      $select_htmleditor->add(rcube_label('always'), 1);
      $select_htmleditor->add(rcube_label('htmlonreply'), 2);
      $select_htmleditor->add(rcube_label('htmlonreplyandforward'), 3);
      $blocks['main']['options']['htmleditor'] = array(
        'title' => html::label($field_id, Q(rcube_label('htmleditor'))),
@@ -544,16 +545,17 @@
      );
    }
    if (!isset($no_override['top_posting'])) {
      $field_id = 'rcmfd_top_posting';
      $select_replymode = new html_select(array('name' => '_top_posting', 'id' => $field_id,
        'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex==0)"));
    if (!isset($no_override['reply_mode'])) {
      $field_id = 'rcmfd_reply_mode';
      $select_replymode = new html_select(array('name' => '_reply_mode', 'id' => $field_id,
        'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex<2)"));
      $select_replymode->add(rcube_label('replyempty'), -1);
      $select_replymode->add(rcube_label('replybottomposting'), 0);
      $select_replymode->add(rcube_label('replytopposting'), 1);
      $blocks['main']['options']['top_posting'] = array(
      $blocks['main']['options']['reply_mode'] = array(
        'title' => html::label($field_id, Q(rcube_label('whenreplying'))),
        'content' => $select_replymode->show($config['top_posting']?1:0),
        'content' => $select_replymode->show(intval($config['reply_mode'])),
      );
    }
@@ -597,7 +599,7 @@
    if (!isset($no_override['sig_above'])) {
      $field_id = 'rcmfd_sig_above';
      $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => !$config['top_posting']));
      $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => $config['reply_mode'] < 1));
      $select_sigabove->add(rcube_label('belowquote'), 0);
      $select_sigabove->add(rcube_label('abovequote'), 1);
program/steps/settings/save_folder.inc
@@ -80,7 +80,10 @@
    }
}
if (!$error) {
if ($error) {
    $OUTPUT->command('display_message', $error, 'error');
}
else {
    $folder['name']     = $name_imap;
    $folder['oldname']  = $old_imap;
    $folder['class']    = '';
program/steps/settings/save_prefs.inc
@@ -82,9 +82,9 @@
      'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']) ? TRUE : FALSE,
      'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']) ? TRUE : FALSE,
      'show_sig'           => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1,
      'top_posting'        => !empty($_POST['_top_posting']),
      'reply_mode'         => isset($_POST['_reply_mode']) ? intval($_POST['_reply_mode']) : 0,
      'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
      'sig_above'          => !empty($_POST['_sig_above']) && !empty($_POST['_top_posting']),
      'sig_above'          => !empty($_POST['_sig_above']) && $_POST['_reply_mode'] < 1,
      'default_font'       => get_input_value('_default_font', RCUBE_INPUT_POST),
      'forward_attachment' => !empty($_POST['_forward_attachment']),
    );
skins/classic/functions.js
@@ -28,11 +28,6 @@
  $('a', tab).removeAttr('onclick').click(function() { return false; });
}
function rcube_show_advanced(visible)
{
  $('tr.advanced').css('display', (visible ? (bw.ie ? 'block' : 'table-row') : 'none'));
}
// Fieldsets-to-tabs converter
// Warning: don't place "caller" <script> inside page element (id)
function rcube_init_tabs(id, current)
@@ -530,7 +525,7 @@
  if ((row = document.getElementById('compose-' + id))) {
    var div = document.getElementById('compose-div'),
      headers_div = document.getElementById('compose-headers-div');
    row.style.display = (document.all && !window.opera) ? 'block' : 'table-row';
    $(row).show();
    div.style.top = (parseInt(headers_div.offsetHeight, 10) + 3) + 'px';
    this.resize_compose_body();
  }
@@ -550,11 +545,11 @@
  for (var i=0; i<links.length; i++)
    if (links[i].style.display != 'none')
      for (var j=i+1; j<links.length; j++)
        if (links[j].style.display != 'none')
        if (links[j].style.display != 'none')
          if ((ns = this.next_sibling(links[i]))) {
            ns.style.display = '';
            break;
          }
            ns.style.display = '';
            break;
          }
  document.getElementById('_' + id).value = '';
skins/classic/includes/links.html
@@ -1,3 +1,4 @@
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<link rel="index" href="$__comm_path" />
<link rel="shortcut icon" href="/images/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="/common.css" />
skins/classic/splitter.js
@@ -47,7 +47,7 @@
      rcube_event.add_listener({element: window, event:'resize', object:this, method:'onResize'});
    // read saved position from cookie
    var cookie = bw.get_cookie(this.id);
    var cookie = rcmail.get_cookie(this.id);
    if (cookie && !isNaN(cookie)) {
      this.pos = parseFloat(cookie);
      this.resize();
@@ -197,7 +197,7 @@
  {
    var exp = new Date();
    exp.setYear(exp.getFullYear() + 1);
    bw.set_cookie(this.id, this.pos, exp);
    rcmail.set_cookie(this.id, this.pos, exp);
  };
} // end class rcube_splitter
skins/classic/templates/compose.html
@@ -79,7 +79,7 @@
                <a href="#bcc" onclick="return rcmail_ui.hide_header_form('bcc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
                <label for="_bcc"><roundcube:label name="bcc" /></label>
            </td>
            <td colspan="2" class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
            <td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
        </tr><tr id="compose-replyto">
            <td class="title top">
                <a href="#replyto" onclick="return rcmail_ui.hide_header_form('replyto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
skins/classic/templates/message.html
@@ -23,11 +23,9 @@
<div id="mailboxlist-container">
<div id="mailboxlist-title" class="boxtitle"><roundcube:label name="mailboxlist" /></div>
<div class="boxlistcontent">
<roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" />
    <roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" />
</div>
<div class="boxfooter">
  <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " />
</div>
<div class="boxfooter"></div>
</div>
</div>
@@ -56,15 +54,6 @@
    var mailviewsplitv = new rcube_splitter({id:'mailviewsplitterv', p1: 'mailboxlist-container', p2: 'messageframe', orientation: 'v', relative: true, start: 165});
    rcmail.add_onload('mailviewsplitv.init()');
</script>
<div id="mailboxoptionsmenu" class="popupmenu">
  <ul>
    <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
    <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
    <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
    <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
  </ul>
</div>
</body>
</html>
skins/classic/templates/messageerror.html
@@ -42,11 +42,9 @@
<div id="mailboxlist-container">
<div class="boxtitle"><roundcube:label name="mailboxlist" /></div>
<div class="boxlistcontent">
<roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" />
    <roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" />
</div>
<div class="boxfooter">
  <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " />
</div>
<div class="boxfooter"></div>
</div>
</div>
@@ -62,15 +60,6 @@
    var mailviewsplitv = new rcube_splitter({id:'mailviewsplitterv', p1: 'mailboxlist-container', p2: 'messageframe', orientation: 'v', relative: true, start: 165});
    rcmail.add_onload('mailviewsplitv.init()');
</script>
<div id="mailboxoptionsmenu" class="popupmenu">
  <ul>
    <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
    <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
    <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
    <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
  </ul>
</div>
</body>
<roundcube:endif />
skins/larry/addressbook.css
@@ -34,7 +34,6 @@
    position: absolute;
    top: -6px;
    left: 0;
    right: 260px;
    height: 40px;
    white-space: nowrap;
    z-index: 10;
skins/larry/ie7hacks.css
@@ -7,9 +7,15 @@
 * License. It is allowed to copy, distribute, transmit and to adapt the work
 * by keeping credits to the original autors in the README file.
 * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
 *
 * $Id$
 */
/* #1488618 */
#mainscreen {
  height: expression((parseInt(document.documentElement.clientHeight)-108)+'px');
}
#mainscreen.offset {
  height: expression((parseInt(document.documentElement.clientHeight)-150)+'px');
}
input.button {
    display: inline;
@@ -23,7 +29,7 @@
.boxfooter .listbutton .inner,
.attachmentslist li a.delete,
.attachmentslist li a.cancelupload,
#messagepreviewheader .iconlink {
#messageheader .iconlink {
    /* workaround for text-indent which also offsets the background image */
    text-indent: 0;
    font-size: 0;
@@ -39,7 +45,7 @@
.pagenav a.button,
.pagenav a.button span.inner,
#messagepreviewheader .iconlink,
#messageheader .iconlink,
#uploadform a.iconlink {
    display: inline;
}
@@ -61,7 +67,7 @@
    text-align: left;
}
#messagepreviewheader .iconlink {
#messageheader .iconlink {
    color: #fff;
    height: 14px;
}
skins/larry/iehacks.css
@@ -143,7 +143,7 @@
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#005d76', endColorstr='#004558', GradientType=0);
}
#messageheader, #partheader, #composeheaders {
#partheader, #composeheaders {
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e9e9e9', GradientType=0);
}
skins/larry/images/contactpic_32px.png

skins/larry/images/contactpic_48px.png
skins/larry/includes/links.html
@@ -1,3 +1,4 @@
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<link rel="shortcut icon" href="/images/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="/styles.css" />
<roundcube:if condition="in_array(env:task, array('mail','addressbook','settings'))" />
skins/larry/mail.css
@@ -38,16 +38,16 @@
    bottom: 28px;
}
#mailview-top.fullheight {
    border-radius: 4px 4px 0 0;
}
#mailview-bottom {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 26px;
}
#mailview-top.fullheight {
    border-radius: 4px 4px 0 0;
}
#folderlist-header {
@@ -341,7 +341,6 @@
#messagetoolbar {
    position: absolute;
    top: -6px;
    right: 390px;
    left: 0;
    height: 40px;
    white-space: nowrap;
@@ -362,7 +361,7 @@
    position: absolute;
    right: 0;
    top: 0;
    width: 240px;
    width: 400px;
}
#mailpreviewtoggle {
@@ -383,11 +382,7 @@
/*** message list ***/
#messagelist thead td:first-child {
    border-radius: 4px 0 0 0;
}
#messagelist thead td:last-child {
    border-radius: 0 4px 0 0;
    border-radius: 4px 0 0 0; /* for Chrome */
}
#messagelist tr td.attachment,
@@ -680,15 +675,14 @@
#messagecontent {
    position: absolute;
    top: 140px;
    top: 0;
    left: 0;
    width: 100%;
    bottom: 0;
    bottom: 28px;
    overflow: auto;
    border-radius: 4px 4px 0 0;
}
#messageheader,
#partheader,
#composeheaders {
    position: relative;
@@ -712,7 +706,7 @@
h3.subject {
    font-size: 14px;
    margin: 0 8em 0 0;
    margin: 0 13em 0 0;
    padding: 8px 8px 4px 8px;
    white-space: nowrap;
    overflow: hidden;
@@ -787,6 +781,7 @@
    background: -ms-linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
    background: linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
    border-right: 1px solid #dfdfdf;
    border-radius: 3px 0 0 0; /* for Opera */
}
#previewheaderstoggle .iconlink {
@@ -801,28 +796,29 @@
#previewheaderstoggle.remove .iconlink {
    top: auto;
    bottom: 5px;
    bottom: 15px;
    background-position: -5px -242px;
}
div.more-headers {
    cursor: pointer;
    height: 10px;
    background: url(images/buttons.png) center -1619px no-repeat;
#previewheaderstoggle .iconlink.allheaders {
    display: none;
}
div.hide-headers {
    background-position: center -1629px;
#previewheaderstoggle.remove .iconlink.allheaders {
    top: auto;
    bottom: 2px;
    display: inline-block;
    background-position: -27px -242px;
}
#all-headers {
    position: relative;
    margin: 0 10px;
    margin: 2px 0;
    padding: 0;
    height: 180px;
    border: 1px solid #bbb;
    background-color: #f0f0f0;
    overflow: hidden;
    border-radius: 4px;
    background: #fff;
}
#headers-source {
@@ -832,25 +828,30 @@
    left: 0;
    right: 0;
    bottom: 0;
    padding: 2px 5px;
    padding: 2px;
    overflow: auto;
    text-align: left;
    color: #333;
    color: #666;
}
#messagepreviewheader {
#messageheader {
    position: relative;
    height: auto;
    margin: 0 8px 0 0;
    padding: 0 0 6px 72px;
    padding: 0 0 0 72px;
    border-bottom: 2px solid #f0f0f0;
}
#messagepreviewheader h3.subject {
#messagecontent #messageheader {
    padding: 0 0 0 90px;
    min-height: 68px;
}
#messageheader h3.subject {
    padding: 8px 8px 2px 0;
}
#messagepreviewheader #contactphoto {
#messageheader #contactphoto {
    display: block;
    position: absolute;
    top: 11px;
@@ -862,52 +863,40 @@
    border-radius: 3px;
}
#messagepreviewheader #contactphoto img {
#messageheader #contactphoto img {
    width: 32px;
    height: auto;
    border-radius: 3px;
}
#messageheader #contactphoto {
    display: block;
    position: absolute;
    top: 40px;
    right: 10px;
#messagecontent #messageheader #contactphoto {
    top: 11px;
    left: 31px;
    width: 48px;
    height: 48px;
    overflow: hidden;
    background: url(images/contactpic_48px.png) center center no-repeat #fff;
    border-radius: 4px;
}
#messageheader #contactphoto img {
#messagecontent #messageheader #contactphoto img {
    width: 48px;
    height: auto;
    border-radius: 4px;
}
#messagepreviewheader #countcontrols,
#messageheader #countcontrols {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 20em;
    right: 0;
    text-align: right;
    white-space: nowrap;
}
#messageheader .pagenav .countdisplay {
    min-width: 0;
    padding-right: 0.5em;
    white-space: nowrap;
}
#messagecontent .leftcol,
#messagepreview .leftcol {
    margin-right: 252px;
    overflow-x: auto;
}
#messagecontent .rightcol,
#messagepreview .rightcol {
    float: right;
/*
@@ -921,6 +910,7 @@
    min-height: 200px;
    background: #f0f0f0;
    padding: 8px;
    border-radius: 4px;
}
#messagebody {
skins/larry/styles.css
@@ -647,6 +647,7 @@
.uibox {
    border: 1px solid #a3a3a3;
    border-radius: 4px;
    overflow: hidden;
    box-shadow: 0 0 2px #999;
    -o-box-shadow: 0 0 2px #999;
    -webkit-box-shadow: 0 0 2px #999;
@@ -660,7 +661,7 @@
    left: 0;
    bottom: 0;
    width: 100%;
    min-width: 1150px;
    min-width: 1024px;
}
.scroller {
@@ -698,7 +699,8 @@
    left: 0;
    width: 100%;
    bottom: 0;
    overflow: auto;
    overflow-x: hidden;
    overflow-y: auto;
}
.listbox .scroller.withfooter {
skins/larry/svggradient.php
@@ -11,6 +11,8 @@
 * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
 */
ini_set('error_reporting', E_ALL &~ (E_NOTICE | E_STRICT));
header('Content-Type: image/svg+xml');
header("Expires: ".gmdate("D, d M Y H:i:s", time()+864000)." GMT");
header("Cache-Control: max-age=864000");
skins/larry/svggradients.css
@@ -133,7 +133,7 @@
    background-image: url(svggradient.php?c=005d76;004558);
}
#messageheader, #partheader, #composeheaders {
#partheader, #composeheaders {
    background-image: url(svggradient.php?c=ffffff;e9e9e9);
}
skins/larry/templates/compose.html
@@ -131,7 +131,7 @@
        </span>
        <roundcube:endif />
        <span class="composeoption">
            <label><label for="rcmcomposepriority"><roundcube:label name="priority" />
            <label for="rcmcomposepriority"><roundcube:label name="priority" />
                <roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></label>
        </span>
        <span class="composeoption">
skins/larry/templates/message.html
@@ -24,20 +24,38 @@
<!-- folders list -->
<div id="mailboxcontainer" class="uibox listbox">
<div class="scroller">
<roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" />
</div>
    <div class="scroller">
        <roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" />
    </div>
</div>
</div>
</div><!-- end mailview-left -->
<div id="mailview-right">
<div id="mailview-right" class="uibox" style="top: 42px">
<div id="mailview-top">
<div id="messageheader" class="uibox">
<h2 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h2>
<roundcube:object name="messageHeaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject" />
<roundcube:object name="messageFullHeaders" id="full-headers" />
<div id="messagecontent">
<div id="messageheader">
<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span><span id="headerstoggleall" class="iconlink allheaders"></span></a>
<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
<table class="headers-table" id="preview-shortheaders"><tbody><tr>
<roundcube:if condition="env:mailbox == config:drafts_mbox || env:mailbox == config:sent_mbox">
    <td class="header-title"><roundcube:label name="to" /></td>
    <td class="header from"><roundcube:object name="messageHeaders" valueOf="to" addicon="/images/addcontact.png" /></td>
<roundcube:else />
    <td class="header-title"><roundcube:label name="from" /></td>
    <td class="header from"><roundcube:object name="messageHeaders" valueOf="from" addicon="/images/addcontact.png" /></td>
<roundcube:endif />
    <td class="header-title"><roundcube:label name="date" /></td>
    <td class="header from"><roundcube:object name="messageHeaders" valueOf="date" /></td>
</tr></tbody></table>
<roundcube:object name="messageHeaders" id="preview-allheaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject,replyto" />
<roundcube:object name="messageFullHeaders" no-switch="true" />
<!-- record navigation -->
<div id="countcontrols" class="pagenav">
@@ -46,24 +64,21 @@
    <roundcube:button command="nextmessage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextmessage" content="&amp;gt;" />
</div>
<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
</div><!-- end messageheader -->
<div id="messagepreview">
    <div class="rightcol">
        <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
    </div>
    <div class="leftcol">
        <roundcube:object name="messageObjects" id="message-objects" />
        <roundcube:object name="messageBody" id="messagebody" />
    </div>
</div>
<div id="messagecontent" class="uibox">
<div class="rightcol">
<roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
</div>
<div class="leftcol">
<roundcube:object name="messageObjects" id="message-objects" />
<roundcube:object name="messageBody" id="messagebody" />
</div>
</div>
</div><!-- end messagecontent -->
</div><!-- end mailview-top -->
<div id="mailview-bottom" class="uibox">
<roundcube:object name="message" id="message" class="statusbar" />
</div>
</div><!-- end mailview-right -->
skins/larry/templates/messageerror.html
@@ -27,8 +27,6 @@
</div>
<div id="mailview-right">
<!-- toolbar -->
<div id="messagetoolbar" class="fullwidth">
    <div id="mailtoolbar" class="toolbar">
@@ -36,11 +34,11 @@
    </div>
</div>
<div id="mailview-top" class="uibox watermark"></div>
<div id="mailview-right" class="uibox" style="top: 42px">
<div id="mailview-bottom" class="uibox">
    <roundcube:object name="message" id="message" class="statusbar" />
</div>
<div id="messagecontent" class="watermark"></div>
<roundcube:object name="message" id="message" class="statusbar" />
</div><!-- end mailview-right -->
skins/larry/templates/messagepreview.html
@@ -6,10 +6,10 @@
</head>
<body class="iframe fullheight">
<div id="messagepreviewheader">
<div id="messageheader">
<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span></a>
<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span><span id="headerstoggleall" class="iconlink allheaders"></a>
<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
<table class="headers-table" id="preview-shortheaders"><tbody><tr>
@@ -25,6 +25,7 @@
</tr></tbody></table>
<roundcube:object name="messageHeaders" id="preview-allheaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject,replyto" />
<roundcube:object name="messageFullHeaders" no-switch="true" />
<!-- record navigation -->
<div id="countcontrols" class="pagenav">
skins/larry/ui.js
@@ -74,9 +74,8 @@
      if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
        layout_messageview();
        rcmail.addEventListener('aftershow-headers', function() { layout_messageview(); });
        rcmail.addEventListener('afterhide-headers', function() { layout_messageview(); });
        $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false });
        $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false; });
        $('#headerstoggleall').click(function(e){ toggle_all_headers(this); return false; });
      }
      else if (rcmail.env.action == 'compose') {
        rcmail.addEventListener('aftertoggle-editor', function(){ window.setTimeout(function(){ layout_composeview() }, 200); });
@@ -162,6 +161,12 @@
        new rcube_scroller('#directorylist-content', '#directorylist-header', '#directorylist-footer');
      }
    }
    // set min-width to show all toolbar buttons
    var screen = $('.minwidth');
    if (screen.length) {
      screen.css('min-width', $('.toolbar').width() + $('#quicksearchbar').parent().width() + 20);
    }
    // turn a group of fieldsets into tabs
@@ -254,11 +259,11 @@
   */
  function resize()
  {
    if (rcmail.env.task == 'mail' && (rcmail.env.action == 'show' || rcmail.env.action == 'preview')) {
      layout_messageview();
    }
    if (rcmail.env.task == 'mail' && rcmail.env.action == 'compose') {
      layout_composeview();
    if (rcmail.env.task == 'mail') {
      if (rcmail.env.action == 'show' || rcmail.env.action == 'preview')
        layout_messageview();
      else if (rcmail.env.action == 'compose')
        layout_composeview();
    }
    // make iframe footer buttons float if scrolling is active
@@ -267,13 +272,13 @@
        body = $(document.body),
        floating = footer.hasClass('floating'),
        overflow = body.outerHeight(true) > $(window).height();
      if (overflow != floating) {
        var action = overflow ? 'addClass' : 'removeClass';
        footer[action]('floating');
        body[action]('floatingbuttons');
      }
    })
    });
  }
  /**
@@ -315,7 +320,6 @@
   */
  function layout_messageview()
  {
    $('#messagecontent').css('top', ($('#messageheader').outerHeight() + 10) + 'px');
    $('#message-objects div a').addClass('button');
    if (!$('#attachment-list li').length) {
@@ -461,7 +465,7 @@
    var button = $(e.target),
      frame = $('#mailpreviewframe'),
      visible = !frame.is(':visible'),
      splitter = mailviewsplit.pos || parseInt(bw.get_cookie('mailviewsplitter') || 320),
      splitter = mailviewsplit.pos || parseInt(rcmail.get_cookie('mailviewsplitter') || 320),
      topstyles, bottomstyles, uid;
    frame.toggle();
@@ -508,13 +512,31 @@
  {
    $('#preview-shortheaders').toggle();
    var full = $('#preview-allheaders').toggle(),
      button = $('a#previewheaderstoggle');
      button = $('#previewheaderstoggle');
    if (!$('#headerstoggleall').length)
      $('#all-headers').toggle();
    // add toggle button to full headers table
    if (full.is(':visible'))
      button.attr('href', '#hide').removeClass('add').addClass('remove')
    else
      button.attr('href', '#details').removeClass('remove').addClass('add')
    if (full.is(':visible')) {
      button.attr('href', '#hide').removeClass('add').addClass('remove');
    }
    else {
      button.attr('href', '#details').removeClass('remove').addClass('add');
    }
  }
  /**
   * Show/hide all message headers
   */
  function toggle_all_headers(button)
  {
    rcmail.command('show-headers', '', button);
    $(button).remove();
    $('#previewheaderstoggle span').css({bottom: '5px'});
    return false;
  }
@@ -847,6 +869,8 @@
      // Select/unselect tab
      $('#tab'+idx).toggleClass('selected', idx==index);
    });
    resize();
  }
  /**
@@ -974,7 +998,7 @@
      $(window).resize(onResize);
    // read saved position from cookie
    var cookie = bw.get_cookie(this.id);
    var cookie = rcmail.get_cookie(this.id);
    if (cookie && !isNaN(cookie)) {
      this.pos = parseFloat(cookie);
      this.resize();
@@ -1135,7 +1159,7 @@
  {
    var exp = new Date();
    exp.setYear(exp.getFullYear() + 1);
    bw.set_cookie(this.id, this.pos, exp);
    rcmail.set_cookie(this.id, this.pos, exp);
  };
} // end class rcube_splitter
tests/Framework/BaseReplacer.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_base_replacer class
 *
 * @package Tests
 */
class Framework_BaseReplacer extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_base_replacer('test');
        $this->assertInstanceOf('rcube_base_replacer', $object, "Class constructor");
    }
}
tests/Framework/Browser.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_browser class
 *
 * @package Tests
 */
class Framework_Browser extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_browser();
        $this->assertInstanceOf('rcube_browser', $object, "Class constructor");
    }
}
tests/Framework/Cache.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_cache class
 *
 * @package Tests
 */
class Framework_Cache extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_cache('db', 1);
        $this->assertInstanceOf('rcube_cache', $object, "Class constructor");
    }
}
tests/Framework/Charset.php
New file
@@ -0,0 +1,28 @@
<?php
/**
 * Test class to test rcube_charset class
 *
 * @package Tests
 */
class Framework_Charset extends PHPUnit_Framework_TestCase
{
    /**
     * Data for test_clean()
     */
    function data_clean()
    {
        return array(
            array('', '', 'Empty string'),
        );
    }
    /**
     * @dataProvider data_clean
     */
    function test_clean($input, $output, $title)
    {
        $this->assertEquals(rcube_charset::clean($input), $output, $title);
    }
}
tests/Framework/ContentFilter.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_content_filter class
 *
 * @package Tests
 */
class Framework_ContentFilter extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_content_filter();
        $this->assertInstanceOf('rcube_content_filter', $object, "Class constructor");
    }
}
tests/Framework/Html.php
New file
@@ -0,0 +1,46 @@
<?php
/**
 * Test class to test rcube_html class
 *
 * @package Tests
 */
class Framework_Html extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new html;
        $this->assertInstanceOf('html', $object, "Class constructor");
    }
    /**
     * Data for test_quote()
     */
    function data_quote()
    {
        return array(
            array('abc', 'abc'),
            array('?', '?'),
            array('"', '&quot;'),
            array('<', '&lt;'),
            array('>', '&gt;'),
            array('&', '&amp;'),
            array('&amp;', '&amp;amp;'),
            array('&amp;', '&amp;', true),
        );
    }
    /**
     * Test for quote()
     * @dataProvider data_quote
     */
    function test_quote($str, $result, $validate = false)
    {
        $this->assertEquals(html::quote($str, $validate), $result);
    }
}
tests/Framework/Image.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_image class
 *
 * @package Tests
 */
class Framework_Image extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_image('test');
        $this->assertInstanceOf('rcube_image', $object, "Class constructor");
    }
}
tests/Framework/Imap.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_imap class
 *
 * @package Tests
 */
class Framework_Imap extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_imap;
        $this->assertInstanceOf('rcube_imap', $object, "Class constructor");
    }
}
tests/Framework/ImapGeneric.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_imap_generic class
 *
 * @package Tests
 */
class Framework_ImapGeneric extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_imap_generic;
        $this->assertInstanceOf('rcube_imap_generic', $object, "Class constructor");
    }
}
tests/Framework/MessageHeader.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_message_header class
 *
 * @package Tests
 */
class Framework_MessageHeader extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_message_header;
        $this->assertInstanceOf('rcube_message_header', $object, "Class constructor");
    }
}
tests/Framework/MessagePart.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_message_part class
 *
 * @package Tests
 */
class Framework_MessagePart extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_message_part;
        $this->assertInstanceOf('rcube_message_part', $object, "Class constructor");
    }
}
tests/Framework/Mime.php
New file
@@ -0,0 +1,123 @@
<?php
/**
 * Test class to test rcube_mime class
 *
 * @package Tests
 */
class Framework_Mime extends PHPUnit_Framework_TestCase
{
    /**
     * Test decoding of single e-mail address strings
     * Uses rcube_mime::decode_address_list()
     */
    function test_decode_single_address()
    {
        $headers = array(
            0  => 'test@domain.tld',
            1  => '<test@domain.tld>',
            2  => 'Test <test@domain.tld>',
            3  => 'Test Test <test@domain.tld>',
            4  => 'Test Test<test@domain.tld>',
            5  => '"Test Test" <test@domain.tld>',
            6  => '"Test Test"<test@domain.tld>',
            7  => '"Test \\" Test" <test@domain.tld>',
            8  => '"Test<Test" <test@domain.tld>',
            9  => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
            10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
            // comments in address (#1487673)
            11 => 'Test (comment) <test@domain.tld>',
            12 => '"Test" (comment) <test@domain.tld>',
            13 => '"Test (comment)" (comment) <test@domain.tld>',
            14 => '(comment) <test@domain.tld>',
            15 => 'Test <test@(comment)domain.tld>',
            16 => 'Test Test ((comment)) <test@domain.tld>',
            17 => 'test@domain.tld (comment)',
            18 => '"Test,Test" <test@domain.tld>',
            // 1487939
            19 => 'Test <"test test"@domain.tld>',
            20 => '<"test test"@domain.tld>',
            21 => '"test test"@domain.tld',
        );
        $results = array(
            0  => array(1, '', 'test@domain.tld'),
            1  => array(1, '', 'test@domain.tld'),
            2  => array(1, 'Test', 'test@domain.tld'),
            3  => array(1, 'Test Test', 'test@domain.tld'),
            4  => array(1, 'Test Test', 'test@domain.tld'),
            5  => array(1, 'Test Test', 'test@domain.tld'),
            6  => array(1, 'Test Test', 'test@domain.tld'),
            7  => array(1, 'Test " Test', 'test@domain.tld'),
            8  => array(1, 'Test<Test', 'test@domain.tld'),
            9  => array(1, 'Test', 'test@domain.tld'),
            10 => array(1, 'Test', 'test@domain.tld'),
            11 => array(1, 'Test', 'test@domain.tld'),
            12 => array(1, 'Test', 'test@domain.tld'),
            13 => array(1, 'Test (comment)', 'test@domain.tld'),
            14 => array(1, '', 'test@domain.tld'),
            15 => array(1, 'Test', 'test@domain.tld'),
            16 => array(1, 'Test Test', 'test@domain.tld'),
            17 => array(1, '', 'test@domain.tld'),
            18 => array(1, 'Test,Test', 'test@domain.tld'),
            19 => array(1, 'Test', '"test test"@domain.tld'),
            20 => array(1, '', '"test test"@domain.tld'),
            21 => array(1, '', '"test test"@domain.tld'),
        );
        foreach ($headers as $idx => $header) {
            $res = rcube_mime::decode_address_list($header);
            $this->assertEquals($results[$idx][0], count($res), "Rows number in result for header: " . $header);
            $this->assertEquals($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
            $this->assertEquals($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
        }
    }
    /**
     * Test decoding of header values
     * Uses rcube_mime::decode_mime_string()
     */
    function test_header_decode_qp()
    {
        $test = array(
            // #1488232: invalid character "?"
            'quoted-printable (1)' => array(
                'in'  => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
                'out' => 'Certifica=C3=A7=C3=A3?',
            ),
            'quoted-printable (2)' => array(
                'in'  => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
                'out' => 'Certifica=C3=A7=C3=A3',
            ),
            'quoted-printable (3)' => array(
                'in'  => '=?utf-8?Q??= =?utf-8?Q??=',
                'out' => '',
            ),
            'quoted-printable (4)' => array(
                'in'  => '=?utf-8?Q??= a =?utf-8?Q??=',
                'out' => ' a ',
            ),
            'quoted-printable (5)' => array(
                'in'  => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
                'out' => 'ab',
            ),
            'quoted-printable (6)' => array(
                'in'  => '=?utf-8?Q?   ?= =?utf-8?Q?a?=',
                'out' => '   a',
            ),
            'quoted-printable (7)' => array(
                'in'  => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
                'out' => '   a',
            ),
        );
        foreach ($test as $idx => $item) {
            $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
            $res = quoted_printable_encode($res);
            $this->assertEquals($item['out'], $res, "Header decoding for: " . $idx);
        }
    }
}
tests/Framework/Rcube.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube class
 *
 * @package Tests
 */
class Framework_Rcube extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = rcube::get_instance();
        $this->assertInstanceOf('rcube', $object, "Class singleton");
    }
}
tests/Framework/ResultIndex.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_result_index class
 *
 * @package Tests
 */
class Framework_ResultIndex extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_result_index;
        $this->assertInstanceOf('rcube_result_index', $object, "Class constructor");
    }
}
tests/Framework/ResultSet.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_result_set class
 *
 * @package Tests
 */
class Framework_ResultSet extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_result_set;
        $this->assertInstanceOf('rcube_result_set', $object, "Class constructor");
    }
}
tests/Framework/ResultThread.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_result_thread class
 *
 * @package Tests
 */
class Framework_ResultThread extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_result_thread;
        $this->assertInstanceOf('rcube_result_thread', $object, "Class constructor");
    }
}
tests/Framework/Shared.php
New file
@@ -0,0 +1,204 @@
<?php
/**
 * Test class to test rcube_shared functions
 *
 * @package Tests
 */
class Framework_Shared extends PHPUnit_Framework_TestCase
{
    /**
     * rcube_shared.inc: in_array_nocase()
     */
    function test_in_array_nocase()
    {
        $haystack = array('Test');
        $needle = 'test';
        $result = in_array_nocase($needle, $haystack);
        $this->assertTrue($result, $title);
        $result = in_array_nocase($needle, null);
        $this->assertFalse($result, $title);
    }
    /**
     * rcube_shared.inc: get_boolean()
     */
    function test_get_boolean()
    {
        $input = array(
            false, 'false', '0', 'no', 'off', 'nein', 'FALSE', '', null,
        );
        foreach ($input as $idx => $value) {
            $this->assertFalse(get_boolean($value), "Invalid result for $idx test item");
        }
        $input = array(
            true, 'true', '1', 1, 'yes', 'anything', 1000,
        );
        foreach ($input as $idx => $value) {
            $this->assertTrue(get_boolean($value), "Invalid result for $idx test item");
        }
    }
    /**
     * rcube_shared.inc: parse_bytes()
     */
    function test_parse_bytes()
    {
        $data = array(
            '1'      => 1,
            '1024'   => 1024,
            '2k'     => 2 * 1024,
            '2 k'     => 2 * 1024,
            '2kb'    => 2 * 1024,
            '2kB'    => 2 * 1024,
            '2m'     => 2 * 1048576,
            '2 m'     => 2 * 1048576,
            '2mb'    => 2 * 1048576,
            '2mB'    => 2 * 1048576,
            '2g'     => 2 * 1024 * 1048576,
            '2 g'     => 2 * 1024 * 1048576,
            '2gb'    => 2 * 1024 * 1048576,
            '2gB'    => 2 * 1024 * 1048576,
        );
        foreach ($data as $value => $expected) {
            $result = parse_bytes($value);
            $this->assertEquals($expected, $result, "Invalid parse_bytes() result for $value");
        }
    }
    /**
     * rcube_shared.inc: slashify()
     */
    function test_slashify()
    {
        $data = array(
            'test'    => 'test/',
            'test/'   => 'test/',
            ''        => '/',
            "\\"      => "\\/",
        );
        foreach ($data as $value => $expected) {
            $result = slashify($value);
            $this->assertEquals($expected, $result, "Invalid slashify() result for $value");
        }
    }
    /**
     * rcube_shared.inc: unslashify()
     */
    function test_unslashify()
    {
        $data = array(
            'test'      => 'test',
            'test/'     => 'test',
            '/'         => '',
            "\\/"       => "\\",
            'test/test' => 'test/test',
            'test//'    => 'test',
        );
        foreach ($data as $value => $expected) {
            $result = unslashify($value);
            $this->assertEquals($expected, $result, "Invalid unslashify() result for $value");
        }
    }
    /**
     * rcube_shared.inc: get_offset_sec()
     */
    function test_get_offset_sec()
    {
        $data = array(
            '1s'    => 1,
            '1m'    => 1 * 60,
            '1h'    => 1 * 60 * 60,
            '1d'    => 1 * 60 * 60 * 24,
            '1w'    => 1 * 60 * 60 * 24 * 7,
            '1y'    => (int) '1y',
            100     => 100,
            '100'   => 100,
        );
        foreach ($data as $value => $expected) {
            $result = get_offset_sec($value);
            $this->assertEquals($expected, $result, "Invalid get_offset_sec() result for $value");
        }
    }
    /**
     * rcube_shared.inc: array_keys_recursive()
     */
    function test_array_keys_recursive()
    {
        $input = array(
            'one' => array(
                'two' => array(
                    'three' => array(),
                    'four' => 'something',
                ),
            ),
            'five' => 'test',
        );
        $result     = array_keys_recursive($input);
        $input_str  = 'one,two,three,four,five';
        $result_str = implode(',', $result);
        $this->assertEquals($input_str, $result_str, "Invalid array_keys_recursive() result");
    }
    /**
     * rcube_shared.inc: format_email()
     */
    function test_format_email()
    {
        $data = array(
            ''                 => '',
            'test'             => 'test',
            'test@test.tld'    => 'test@test.tld',
            'test@[127.0.0.1]' => 'test@[127.0.0.1]',
            'TEST@TEST.TLD'    => 'TEST@test.tld',
        );
        foreach ($data as $value => $expected) {
            $result = format_email($value);
            $this->assertEquals($expected, $result, "Invalid format_email() result for $value");
        }
    }
    /**
     * rcube_shared.inc: format_email_recipient()
     */
    function test_format_email_recipient()
    {
        $data = array(
            ''                          => array(''),
            'test'                      => array('test'),
            'test@test.tld'             => array('test@test.tld'),
            'test@[127.0.0.1]'          => array('test@[127.0.0.1]'),
            'TEST@TEST.TLD'             => array('TEST@TEST.TLD'),
            'TEST <test@test.tld>'      => array('test@test.tld', 'TEST'),
            '"TEST\"" <test@test.tld>'  => array('test@test.tld', 'TEST"'),
        );
        foreach ($data as $expected => $value) {
            $result = format_email_recipient($value[0], $value[1]);
            $this->assertEquals($expected, $result, "Invalid format_email_recipient()");
        }
    }
}
tests/Framework/Smtp.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_smtp class
 *
 * @package Tests
 */
class Framework_Smtp extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_smtp;
        $this->assertInstanceOf('rcube_smtp', $object, "Class constructor");
    }
}
tests/Framework/Spellchecker.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_spellchecker class
 *
 * @package Tests
 */
class Framework_Spellchecker extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $object = new rcube_spellchecker;
        $this->assertInstanceOf('rcube_spellchecker', $object, "Class constructor");
    }
}
tests/Framework/StringReplacer.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_string_replacer class
 *
 * @package Tests
 */
class Framework_StringReplacer extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $sr = new rcube_string_replacer;
        $this->assertInstanceOf('rcube_string_replacer', $sr, "Class constructor");
    }
}
tests/Framework/User.php
New file
@@ -0,0 +1,20 @@
<?php
/**
 * Test class to test rcube_user class
 *
 * @package Tests
 */
class Framework_User extends PHPUnit_Framework_TestCase
{
    /**
     * Class constructor
     */
    function test_class()
    {
        $user = new rcube_user;
        $this->assertInstanceOf('rcube_user', $user, "Class constructor");
    }
}
tests/Framework/Utils.php
New file
@@ -0,0 +1,196 @@
<?php
/**
 * Test class to test rcube_utils class
 *
 * @package Tests
 */
class Framework_Utils extends PHPUnit_Framework_TestCase
{
    /**
     * Valid email addresses for test_valid_email()
     */
    function data_valid_email()
    {
        return array(
            array('email@domain.com', 'Valid email'),
            array('firstname.lastname@domain.com', 'Email contains dot in the address field'),
            array('email@subdomain.domain.com', 'Email contains dot with subdomain'),
            array('firstname+lastname@domain.com', 'Plus sign is considered valid character'),
            array('email@[123.123.123.123]', 'Square bracket around IP address'),
            array('email@[IPv6:::1]', 'Square bracket around IPv6 address (1)'),
            array('email@[IPv6:::1.2.3.4]', 'Square bracket around IPv6 address (2)'),
            array('email@[IPv6:2001:2d12:c4fe:5afe::1]', 'Square bracket around IPv6 address (3)'),
            array('"email"@domain.com', 'Quotes around email is considered valid'),
            array('1234567890@domain.com', 'Digits in address are valid'),
            array('email@domain-one.com', 'Dash in domain name is valid'),
            array('_______@domain.com', 'Underscore in the address field is valid'),
            array('email@domain.name', '.name is valid Top Level Domain name'),
            array('email@domain.co.jp', 'Dot in Top Level Domain name also considered valid (use co.jp as example here)'),
            array('firstname-lastname@domain.com', 'Dash in address field is valid'),
        );
    }
    /**
     * Invalid email addresses for test_invalid_email()
     */
    function data_invalid_email()
    {
        return array(
            array('plainaddress', 'Missing @ sign and domain'),
            array('#@%^%#$@#$@#.com', 'Garbage'),
            array('@domain.com', 'Missing username'),
            array('Joe Smith <email@domain.com>', 'Encoded html within email is invalid'),
            array('email.domain.com', 'Missing @'),
            array('email@domain@domain.com', 'Two @ sign'),
            array('.email@domain.com', 'Leading dot in address is not allowed'),
            array('email.@domain.com', 'Trailing dot in address is not allowed'),
            array('email..email@domain.com', 'Multiple dots'),
            array('あいうえお@domain.com', 'Unicode char as address'),
            array('email@domain.com (Joe Smith)', 'Text followed email is not allowed'),
            array('email@domain', 'Missing top level domain (.com/.net/.org/etc)'),
            array('email@-domain.com', 'Leading dash in front of domain is invalid'),
//            array('email@domain.web', '.web is not a valid top level domain'),
            array('email@123.123.123.123', 'IP address without brackets'),
            array('email@2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets'),
            array('email@IPv6:2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets (2)'),
            array('email@[111.222.333.44444]', 'Invalid IP format'),
            array('email@[111.222.255.257]', 'Invalid IP format (2)'),
            array('email@[.222.255.257]', 'Invalid IP format (3)'),
            array('email@[::1]', 'Invalid IPv6 format (1)'),
            array('email@[IPv6:2001:23x2:1]', 'Invalid IPv6 format (2)'),
            array('email@[IPv6:1111:2222:33333::4444:5555]', 'Invalid IPv6 format (3)'),
            array('email@[IPv6:1111::3333::4444:5555]', 'Invalid IPv6 format (4)'),
            array('email@domain..com', 'Multiple dot in the domain portion is invalid'),
        );
    }
    /**
     * @dataProvider data_valid_email
     */
    function test_valid_email($email, $title)
    {
        $this->assertTrue(rcube_utils::check_email($email, false), $title);
    }
    /**
     * @dataProvider data_invalid_email
     */
    function test_invalid_email($email, $title)
    {
        $this->assertFalse(rcube_utils::check_email($email, false), $title);
    }
    /**
     * Valid IP addresses for test_valid_ip()
     */
    function data_valid_ip()
    {
        return array(
            array('0.0.0.0'),
            array('123.123.123.123'),
            array('::'),
            array('::1'),
            array('::1.2.3.4'),
            array('2001:2d12:c4fe:5afe::1'),
        );
    }
    /**
     * Valid IP addresses for test_invalid_ip()
     */
    function data_invalid_ip()
    {
        return array(
            array(''),
            array(0),
            array('123.123.123.1234'),
            array('1.1.1.1.1'),
            array('::1.2.3.260'),
            array('::1.0'),
            array('2001::c4fe:5afe::1'),
        );
    }
    /**
     * @dataProvider data_valid_ip
     */
    function test_valid_ip($ip)
    {
        $this->assertTrue(rcube_utils::check_ip($ip));
    }
    /**
     * @dataProvider data_invalid_ip
     */
    function test_invalid_ip($ip)
    {
        $this->assertFalse(rcube_utils::check_ip($ip));
    }
    /**
     * Data for test_rep_specialchars_output()
     */
    function data_rep_specialchars_output()
    {
        return array(
            array('', '', 'abc', 'abc'),
            array('', '', '?', '?'),
            array('', '', '"', '&quot;'),
            array('', '', '<', '&lt;'),
            array('', '', '>', '&gt;'),
            array('', '', '&', '&amp;'),
            array('', '', '&amp;', '&amp;amp;'),
            array('', '', '<a>', '&lt;a&gt;'),
            array('', 'remove', '<a>', ''),
        );
    }
    /**
     * Test for rep_specialchars_output
     * @dataProvider data_rep_specialchars_output
     */
    function test_rep_specialchars_output($type, $mode, $str, $res)
    {
        $result = rcube_utils::rep_specialchars_output(
            $str, $type ? $type : 'html', $mode ? $mode : 'strict');
        $this->assertEquals($result, $res);
    }
    /**
     * rcube_utils::mod_css_styles()
     */
    function test_mod_css_styles()
    {
        $css = file_get_contents(TESTS_DIR . 'src/valid.css');
        $mod = rcube_utils::mod_css_styles($css, 'rcmbody');
        $this->assertRegExp('/#rcmbody\s+\{/', $mod, "Replace body style definition");
        $this->assertRegExp('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
        $this->assertRegExp('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
        $this->assertRegExp('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
    }
    /**
     * rcube_utils::mod_css_styles()
     */
    function test_mod_css_styles_xss()
    {
        $mod = rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
        $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
        $mod = rcube_utils::mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
        $this->assertEquals("/* evil! */", $mod, "No import statements");
        $mod = rcube_utils::mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
        $this->assertEquals("/* evil! */", $mod, "No expression properties");
        $mod = rcube_utils::mod_css_styles("left:exp/*  */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
        $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
        $mod = rcube_utils::mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
        $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
    }
}
tests/Framework/VCard.php
New file
@@ -0,0 +1,73 @@
<?php
/**
 * Unit tests for class rcube_vcard
 *
 * @package Tests
 */
class Framework_VCard extends PHPUnit_Framework_TestCase
{
    function _srcpath($fn)
    {
        return realpath(dirname(__FILE__) . '/../src/' . $fn);
    }
    function test_parse_one()
    {
        $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
        $this->assertTrue($vcard->business, "Identify as business record");
        $this->assertEquals("Apple Computer AG", $vcard->displayname, "FN => displayname");
        $this->assertEquals("", $vcard->firstname, "No person name set");
    }
    function test_parse_two()
    {
        $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
        $this->assertFalse($vcard->business, "Identify as private record");
        $this->assertEquals("John Doë", $vcard->displayname, "Decode according to charset attribute");
        $this->assertEquals("roundcube.net", $vcard->organization, "Test organization field");
        $this->assertCount(2, $vcard->email, "List two e-mail addresses");
        $this->assertEquals("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
    }
    function test_import()
    {
        $input = file_get_contents($this->_srcpath('apple.vcf'));
        $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
        $vcards = rcube_vcard::import($input);
        $this->assertCount(2, $vcards, "Detected 2 vcards");
        $this->assertEquals("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
        $this->assertEquals("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
        // http://trac.roundcube.net/ticket/1485542
        $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
        $this->assertEquals("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
    }
    function test_import_photo_encoding()
    {
        $input = file_get_contents($this->_srcpath('photo.vcf'));
        $vcards = rcube_vcard::import($input);
        $vcard = $vcards[0]->get_assoc();
        $this->assertCount(1, $vcards, "Detected 1 vcard");
        // ENCODING=b case (#1488683)
        $this->assertEquals("/9j/4AAQSkZJRgABAQA", substr(base64_encode($vcard['photo']), 0, 19), "Photo decoding");
        $this->assertEquals("Müller", $vcard['surname'], "Unicode characters");
    }
    function test_encodings()
    {
        $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
        $vcards = rcube_vcard::import($input);
        $this->assertEquals("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
    }
}
tests/HtmlToText.php
File was renamed from tests/html_to_text.php
@@ -5,18 +5,12 @@
 *
 * @package Tests
 */
class rcube_test_html2text extends UnitTestCase
class HtmlToText extends PHPUnit_Framework_TestCase
{
    function __construct()
    function data_html2text()
    {
        $this->UnitTestCase("HTML-to-Text conversion tests");
    }
    function test_html2text()
    {
        $data = array(
        return array(
            0 => array(
                'title' => 'Test entry',
                'in'    => '',
@@ -48,14 +42,18 @@
                'out'   => 'Ś',
            ),
        );
        $ht = new html2text(null, false, false);
        foreach ($data as $idx => $item) {
            $ht->set_html($item['in']);
            $res = $ht->get_text();
            $this->assertEqual($item['out'], $res, $item['title'] . "($idx)");
        }
    }
    /**
     * @dataProvider data_html2text
     */
    function test_html2text($title, $in, $out)
    {
        $ht = new html2text(null, false, false);
        $ht->set_html($in);
        $res = $ht->get_text();
        $this->assertEquals($out, $res, $title);
    }
}
tests/MailFunc.php
New file
@@ -0,0 +1,172 @@
<?php
/**
 * Test class to test steps/mail/func.inc functions
 *
 * @package Tests
 */
class MailFunc extends PHPUnit_Framework_TestCase
{
    function setUp()
    {
        // simulate environment to successfully include func.inc
        $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
        $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
        $RCMAIL->action = 'autocomplete';
        $RCMAIL->storage_init(false);
        require_once INSTALL_PATH . 'program/steps/mail/func.inc';
        $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
    }
    /**
     * Helper method to create a HTML message part object
     */
    function get_html_part($body)
    {
        $part = new rcube_message_part;
        $part->ctype_primary = 'text';
        $part->ctype_secondary = 'html';
        $part->body = file_get_contents(TESTS_DIR . $body);
        $part->replaces = array();
        return $part;
    }
    /**
     * Test sanitization of a "normal" html message
     */
    function test_html()
    {
        $part = $this->get_html_part('src/htmlbody.txt');
        $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
        // render HTML in normal mode
        $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
        $this->assertRegExp('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
        $this->assertRegExp('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
        $this->assertNotRegExp('/ex3.jpg/', $html, "No references to external images");
        $this->assertNotRegExp('/<meta [^>]+>/', $html, "No meta tags allowed");
        //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
        $this->assertNotRegExp('/<form [^>]+>/', $html, "No form tags allowed");
        $this->assertRegExp('/Subscription form/', $html, "Include <form> contents");
        $this->assertRegExp('/<!-- link ignored -->/', $html, "No external links allowed");
        $this->assertRegExp('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
        $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
        // render HTML in safe mode
        $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
        $this->assertRegExp('/<style [^>]+>/', $html2, "Allow styles in safe mode");
        $this->assertRegExp('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
        $this->assertRegExp("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
        $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
        $this->assertRegExp('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
    }
    /**
     * Test the elimination of some trivial XSS vulnerabilities
     */
    function test_html_xss()
    {
        $part = $this->get_html_part('src/htmlxss.txt');
        $washed = rcmail_print_body($part, array('safe' => true));
        $this->assertNotRegExp('/src="skins/', $washed, "Remove local references");
        $this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes");
        $html = rcmail_html4inline($washed, 'foo');
        $this->assertNotRegExp('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
        $this->assertNotRegExp('/alert/', $html, "Remove alerts");
    }
    /**
     * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
     * reported at http://www.securityfocus.com/bid/26800/
     */
    function test_html_xss2()
    {
        $part = $this->get_html_part('src/BID-26800.txt');
        $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
        $this->assertNotRegExp('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
        $this->assertNotRegExp('/font-style:italic/', $washed, "Allow valid styles");
    }
    /**
     * Test washtml class on non-unicode characters (#1487813)
     */
    function test_washtml_utf8()
    {
        $part = $this->get_html_part('src/invalidchars.html');
        $washed = rcmail_print_body($part);
        $this->assertRegExp('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
    }
    /**
     * Test links pattern replacements in plaintext messages
     */
    function test_plaintext()
    {
        $part = new rcube_message_part;
        $part->ctype_primary = 'text';
        $part->ctype_secondary = 'plain';
        $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
        $html = rcmail_print_body($part, array('safe' => true));
        $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
        $this->assertRegExp('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
        $this->assertRegExp('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
    }
    /**
     * Test mailto links in html messages
     */
    function test_mailto()
    {
        $part = $this->get_html_part('src/mailto.txt');
        // render HTML in normal mode
        $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
        $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
        $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
    }
    /**
     * Test the elimination of HTML comments
     */
    function test_html_comments()
    {
        $part = $this->get_html_part('src/htmlcom.txt');
        $washed = rcmail_print_body($part, array('safe' => true));
        // #1487759
        $this->assertRegExp('|<p>test1</p>|', $washed, "Buggy HTML comments");
        // but conditional comments (<!--[if ...) should be removed
        $this->assertNotRegExp('|<p>test2</p>|', $washed, "Conditional HTML comments");
    }
    /**
     * Test URI base resolving in HTML messages
     */
    function test_resolve_base()
    {
        $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
        $html = rcmail_resolve_base($html);
        $this->assertRegExp('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
        $this->assertRegExp('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
        $this->assertRegExp('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
        // base resolving exceptions
        $this->assertRegExp('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
        $this->assertRegExp('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
    }
}
tests/bootstrap.php
New file
@@ -0,0 +1,35 @@
<?php
/*
 +-----------------------------------------------------------------------+
 | tests/bootstrap.php                                                   |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2009-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Environment initialization script for unit tests                    |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
*/
if (php_sapi_name() != 'cli')
  die("Not in shell mode (php-cli)");
if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
define('TESTS_DIR', dirname(__FILE__) . '/');
if (@is_dir(TESTS_DIR . 'config')) {
    define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
}
require_once(INSTALL_PATH . 'program/include/iniset.php');
rcmail::get_instance()->config->set('devel_mode', false);
tests/maildecode.php
File was deleted
tests/mailfunc.php
File was deleted
tests/modcss.php
File was deleted
tests/phpunit.xml
New file
@@ -0,0 +1,37 @@
<phpunit backupGlobals="false"
    bootstrap="bootstrap.php"
    colors="true">
    <testsuites>
        <testsuite name="All Tests">
            <file>Framework/BaseReplacer.php</file>
            <file>Framework/Browser.php</file>
            <file>Framework/Cache.php</file>
            <file>Framework/Charset.php</file>
            <file>Framework/ContentFilter.php</file>
            <file>Framework/Html.php</file>
            <file>Framework/Imap.php</file>
            <file>Framework/ImapGeneric.php</file>
            <file>Framework/Image.php</file>
            <file>Framework/MessageHeader.php</file>
            <file>Framework/MessagePart.php</file>
            <file>Framework/Mime.php</file>
            <file>Framework/Rcube.php</file>
            <file>Framework/ResultIndex.php</file>
            <file>Framework/ResultSet.php</file>
            <file>Framework/ResultThread.php</file>
            <file>Framework/Shared.php</file>
            <file>Framework/Smtp.php</file>
            <file>Framework/Spellchecker.php</file>
            <file>Framework/StringReplacer.php</file>
            <file>Framework/User.php</file>
            <file>Framework/Utils.php</file>
            <file>Framework/VCard.php</file>
            <file>HtmlToText.php</file>
            <file>MailFunc.php</file>
        </testsuite>
        <testsuite name="managesieve">
            <file>./../plugins/managesieve/tests/Parser.php</file>
            <file>./../plugins/managesieve/tests/Tokenizer.php</file>
        </testsuite>
    </testsuites>
</phpunit>
tests/runtests.sh
File was deleted
tests/src/photo.vcf
New file
@@ -0,0 +1,45 @@
BEGIN:VCARD
VERSION:3.0
N:Müller;Jörg;;;
FN:Apple Computer AG
ORG:Apple Computer AG;
PHOTO;ENCODING=b:
  /9j/4AAQSkZJRgABAQAAAQABAAD/7QAcUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAD/2wBDAAEB
  AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
  AQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
  AQEBAQEBAQEBAQEBAQEBAQH/wAARCAAwADADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA
  AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEI
  I0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlq
  c3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
  19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL
  /8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLR
  ChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE
  hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn
  6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/igAoAKAPmH43ftT+CfgzqNt4bNjeeLvGV2IHXw7
  pVxDbLZx3LBbdtU1GVLhbN7jIMFvHa3VzIpWRoY4mWQ9dDCTrrmuoQ/mavfvZXV7dW2jkr4ynQfL
  Zzn1inZL1lZ6+ST87H0lp1zLe6fY3k9s1nNd2dtczWjv5j2ss8KSyWzybU3tA7mJn2JuKk7Vzgcr
  Vm1e9m1fvrv8zqi+aKbVm0nbtdXt8i5SGFAHHeOfH3hH4b6DP4k8Z61a6JpUBCCW4LPNdTsCUtbK
  1iD3F5dSYO2C3jd8AuwVFZhdOnOrLlhFyf4Lzb6IzqVYUo81SSiundvslu3/AEz5i0n9u74Fanqo
  064m8UaNbvII49X1TRUGnnORvmFje3t5BGTjDtatwcyCPBrreArpX9xvqlJ3/FJP7zlWYUHKzVRL
  +ZxVvnaTa+4+QLvVPgJ4U+LutfFXx78QJfi3q954iuvEOieHvBmkyzaVbO1x5ulPruq6pLawTvp1
  uLdIrK08xFmgXzl2oI67LV50o0qdP2K5VGUptKXnyxi29XfVtb6HDehGtKpUm6zcnNKCfK9brnlK
  3pZJ37pH3/8ACj9qb4T/ABd1FdD0TUr3SPETozwaJ4ht47C5vQgLONOnjnuLS8dVBYwJOt1tDMIN
  oLV51XCVqK5pJSj1cW3b1uk1vvqvM9Kji6VZ8qbjJ7KVlf0abTf4n0dXMdQUAfhf+2J8UNW8ffFz
  W9Cnlki0XwDqOp+GdNsFdhB9qsr6a31DUDHu2tcXbwojSEbhFCka4Uc+9hKUadGMl8VRKcn11V0v
  RJng4urKpWkntTlKCXTRtN+re58n11HKFAH6PfsGfBvQfEl3rHxR8Q2y38vhrUrfT/DNpIT5FvqY
  iF1carIgI8ye2R4Y7RXykbySTbS6xlfOx9aUVGlF2503N+W1vnrc9HAUYzlKrJX5GuVf3t7+dvu3
  vc/WKvIPXCgD8FP2s/BF/wCC/jd4wlu0It/Fmp6h4usJf4JINZ1G7ndVbu0UpZZBztY446V9BhZq
  dCnZ/DFQfrFJHz+Kg4V6l/tSc15qTb/rzPmqug5woA++v2J/j74d+HN9rHgLxndrpmjeJr62vtJ1
  mbP2Sw1dY/s0ltfOM+Rb30Yh8u5ZTHFPEFlZEk3Dgx2HlVUZw1lBNOPVp9vNa6X22137sFiI0ZSh
  N2jNpqXSLSe/k+/R+p+w1eMe0FAHxz+2P8DLr4r+CIPEPhy2Nx4y8FJdXVnaxrmfWdGlAk1DSosK
  WkuozEt5p8fHmTLNADuuQR24KuqU3GTtCpbXtLo32T2b9GcWNw7qwU4q84X06yi9Wl531XfXyPxK
  ME4nNsYZRciUwG3MbicTh/LMJix5glD/ACGPbv3/AC4zxXtniFvUNJ1XSmjXVNM1DTWmUvCuoWVz
  ZtKgxloxcRxmRRkZZcgZGTzSTT2afo7hqtz6w/ZB+BV78UPHtl4n1eykHgfwdewajfXE0bCDVtVt
  mE+n6PAzAJOBOkdxqAUssdsnlSDNwoPLi66pU3FP95NNJdk95P06d35XOvCUHWqJtfu4NOT7vdR8
  7vfsvVH7gV4R7oUAFAHKReA/A8Gsy+IofBnhSHxBO7ST67F4d0iPWZnb7zy6mlmL2R2/iZ5yT3NX
  7Spbl9pPl/l55W+69iPZUubm9nDmvfm5I81+97XuX9a8MeGvEtsLLxF4e0PX7MMGFprWk2Gq2wZe
  jCC+t54tw7HZkdqUZzg7wlKL7xk4v700OUIT0lCMl2lFS/NMuaXpOlaHZQ6Zoumafo+nW4It9P0u
  yttPsoATkiG1tI4oIgTyQkagnmk5Sk7ybk+7bb+96jjGMVaMVFdopJfcjQpDP//Z
X-ABShowAs:COMPANY
X-ABUID:2E4CB084-4767-4C85-BBCA-805B1DCB1C8E\:ABPerson
END:VCARD
tests/vcards.php
File was deleted