From b334a01791689a16c8d6b10955df850b891ff40e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 20 Sep 2013 02:18:54 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 CHANGELOG                                   |   27 +-
 program/include/rcmail_output_html.php      |   17 +
 plugins/password/drivers/virtualmin.php     |    4 
 program/steps/mail/compose.inc              |   27 ++
 program/steps/settings/edit_prefs.inc       |    9 
 skins/larry/settings.css                    |   20 ++
 program/js/editor.js                        |    3 
 program/steps/mail/show.inc                 |    5 
 program/steps/mail/sendmail.inc             |    5 
 skins/larry/addressbook.css                 |    5 
 program/steps/settings/save_prefs.inc       |    1 
 skins/classic/mail.css                      |    6 
 program/steps/mail/attachments.inc          |    5 
 tests/Framework/Browser.php                 |  203 +++++++++++++++++++++++++
 program/steps/settings/func.inc             |   48 +++--
 skins/larry/templates/importcontacts.html   |    2 
 program/lib/Roundcube/rcube_config.php      |    2 
 skins/classic/templates/message.html        |    2 
 program/include/rcmail_output_json.php      |    7 
 plugins/password/config.inc.php.dist        |    3 
 program/js/app.js                           |   36 ++-
 config/defaults.inc.php                     |    6 
 skins/classic/templates/messagepreview.html |    2 
 skins/larry/ui.js                           |   13 +
 24 files changed, 381 insertions(+), 77 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 85963d8..8d5870c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,9 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Make default font size for HTML messages configurable (request #118)
+- Display full attachment name using title attribute when name is too long to display (#1489320)
+- Fix XSS issue in addressbook group name field [CVE-2013-5646] (#1489333)
 - Fix attachment icon issue when rare font/language is used (#1489326)
 - After message is sent refresh messages list of replied message folder (#1489249)
 - Add option force specified domain in user login - username_domain_forced (#1489264)
@@ -96,8 +99,8 @@
 - Fix base URL resolving on attribute values with no quotes (#1489275)
 - Fix wrong handling of links with '|' character (#1489276)
 - Fix colorspace issue on image conversion using ImageMagick (#1489270)
-- Fix XSS vulnerability when editing a message "as new" or draft (#1489251)
-- Fix XSS vulnerability when saving HTML signatures (#1489251)
+- Fix XSS vulnerability when editing a message "as new" or draft [CVE-2013-5645] (#1489251)
+- Fix XSS vulnerability when saving HTML signatures [CVE-2013-5645] (#1489251)
 - Fix rewrite rule in .htaccess (#1489240)
 - Fix detecting Turkish language in ISO-8859-9 encoding (#1489252)
 - Fix identity-selection using Return-Path headers (#1489241)
@@ -317,7 +320,7 @@
 - Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
 - Fix unwanted horizontal scrollbar in message preview header (#1488866)
 - Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#1488844)
-- Fix XSS vulnerability in vbscript: and data:text links handling (#1488850)
+- Fix XSS vulnerability in vbscript: and data:text links handling [CVE-2012-6121] (#1488850)
 - Fix absolute positioning in HTML messages (#1488819)
 - Fix cache (in)validation after setting \Deleted flag
 - Fix keybord events on messages list in opera browser (#1488823)
@@ -372,8 +375,8 @@
 - 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 XSS issue where plain signatures wasn't secured in HTML mode [CVE-2012-4668] (#1488613)
+- Fix XSS issue where href="javascript:" wasn't secured [CVE-2012-3508] (#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)
@@ -408,7 +411,7 @@
 - Fix removing contact photo using LDAP addressbook (#1488420)
 - Fix storing X-ANNIVERSARY date in vCard format (#1488527)
 - Update to Mail_Mime-1.8.5 (#1488521)
-- Fix XSS vulnerability in message subject handling using Larry skin (#1488519)
+- Fix XSS vulnerability in message subject handling using Larry skin [CVE-2012-3507] (#1488519)
 - Fix handling of links with various URI schemes e.g. "skype:" (#1488106)
 - Fix handling of links inside PRE elements on html to text conversion
 - Fix indexing of links on html to text conversion
@@ -535,7 +538,7 @@
 - Improved handling of some malformed values encoded with quoted-printable (#1488232)
 - Add possibility to do LDAP bind before searching for bind DN
 - Fix handling of empty <U> tags in HTML messages (#1488225)
-- Add content filter for embedded attachments to protect from XSS on IE (#1487895)
+- Add content filter for embedded attachments to protect from XSS on IE [CVE-2012-1253] (#1487895)
 - Use strpos() instead of strstr() when possible (#1488211)
 - Fix handling HTML entities when converting HTML to text (#1488212)
 - Fix fit_string_to_size() renders browser and ui unresponsive (#1488207)
@@ -703,7 +706,7 @@
 
 RELEASE 0.5.4
 -------------
-- Fix XSS vulnerability in UI messages (#1488030)
+- Fix XSS vulnerability in UI messages [CVE-2011-2937] (#1488030)
 
 RELEASE 0.5.3
 -------------
@@ -753,8 +756,8 @@
 - Security: add optional referer check to prevent CSRF in GET requests
 - Fix email_dns_check setting not used for identities/contacts (#1487740)
 - Fix ICANN example addresses doesn't validate (#1487742)
-- Security: protect login form submission from CSRF
-- Security: prevent from relaying malicious requests through modcss.inc
+- Security: protect login form submission from CSRF [CVE-2011-1491]
+- Security: prevent from relaying malicious requests through modcss.inc [CVE-2011-1492]
 - Fix handling of non-image attachments in multipart/related messages (#1487750)
 - Fix IDNA support when IDN/INTL modules are in use (#1487742)
 - Fix handling of invalid HTML comments in messages (#1487759)
@@ -1197,7 +1200,7 @@
 ---------------
 - Fix import of vCard entries with params (#1485453)
 - Fix HTML messages output with empty block elements (#1485974)
-- Use request tokens to protect POST requests from CSRF
+- Use request tokens to protect POST requests from CSRF [CVE-2009-4076, CVE-2009-4077]
 - Added hook when killing a session
 - Added hook to write_log function (#1485971)
 - Performance improvements by use UID commands (#1485690)
@@ -1324,7 +1327,7 @@
 - Fix large search results on server without SORT capability (#1485668)
 - Get rid of preg_replace() with eval modifier and create_function usage (#1485686)
 - Bring back <base> and <link> tags in HTML messages
-- Fix XSS vulnerability through background attributes as reported by Julien Cayssol
+- Fix XSS vulnerability through background attributes [CVE-2009-0413]
 - Fix problems with backslash as IMAP hierarchy delimiter (#1484467)
 - Secure vcard export by getting rid of preg's 'e' modifier use (#1485689)
 - Fix authentication when submitting form with existing session (#1485679)
diff --git a/config/defaults.inc.php b/config/defaults.inc.php
index bf7c4df..97c8f3b 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -245,6 +245,8 @@
 
 // replace Roundcube logo with this image
 // specify an URL relative to the document root of this Roundcube installation
+// an array can be used to specify different logos for specific template files, '*' for default logo
+// for example array("*" => "/images/roundcube_logo.png", "messageprint" => "/images/roundcube_logo_print.png")
 $config['skin_logo'] = null;
 
 // automatically create a new Roundcube user when log-in the first time.
@@ -982,5 +984,9 @@
 // Georgia, Helvetica, Impact, Tahoma, Terminal, Times New Roman, Trebuchet MS, Verdana
 $config['default_font'] = 'Verdana';
 
+// Default font size for composed HTML message.
+// Supported sizes: 8pt, 10pt, 12pt, 14pt, 18pt, 24pt, 36pt
+$config['default_font_size'] = '10pt';
+
 // Enables display of email address with name instead of a name (and address in title)
 $config['message_show_email'] = false;
diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist
index 82f6617..bfea529 100644
--- a/plugins/password/config.inc.php.dist
+++ b/plugins/password/config.inc.php.dist
@@ -320,8 +320,7 @@
 // 5: domain-username
 // 6: username_domain
 // 7: domain_username
-// 8: username@domain; mbox.username
-$config['password_virtualmin_format'] = 8;
+$config['password_virtualmin_format'] = 0;
 
 
 // pw_usermod Driver options
diff --git a/plugins/password/drivers/virtualmin.php b/plugins/password/drivers/virtualmin.php
index 2c7aee6..2d2f73f 100644
--- a/plugins/password/drivers/virtualmin.php
+++ b/plugins/password/drivers/virtualmin.php
@@ -48,10 +48,6 @@
             $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);
         }
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
index a2ec29c..6db5593 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -924,8 +924,21 @@
                 }
                 else if ($object == 'logo') {
                     $attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
-                    if ($logo = $this->config->get('skin_logo'))
-                        $attrib['src'] = $logo;
+
+                    if ($logo = $this->config->get('skin_logo')) {
+                        if (is_array($logo)) {
+                            if ($template_logo = $logo[$this->template_name]) {
+                                $attrib['src'] = $template_logo;
+                            }
+                            elseif ($template_logo = $logo['*']) {
+                                $attrib['src'] = $template_logo;
+                            }
+                        }
+                        else {
+                            $attrib['src'] = $logo;
+                        }
+                    }
+
                     $content = html::img($attrib);
                 }
                 else if ($object == 'productname') {
diff --git a/program/include/rcmail_output_json.php b/program/include/rcmail_output_json.php
index def6ee4..d0e1eec 100644
--- a/program/include/rcmail_output_json.php
+++ b/program/include/rcmail_output_json.php
@@ -227,6 +227,13 @@
         if (!empty($this->callbacks))
             $response['callbacks'] = $this->callbacks;
 
+        // trigger generic hook where plugins can put additional content to the response
+        $hook = $this->app->plugins->exec_hook("render_response", array('response' => $response));
+
+        // save some memory
+        $response = $hook['response'];
+        unset($hook['response']);
+
         echo self::json_serialize($response);
     }
 
diff --git a/program/js/app.js b/program/js/app.js
index 42c6611..337a121 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -1881,7 +1881,7 @@
         html = expando;
       else if (c == 'subject') {
         if (bw.ie) {
-          col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
+          col.onmouseover = function() { rcube_webmail.long_subject_title_ex(this, message.depth+1); };
           if (bw.ie8)
             tree = '<span></span>' + tree; // #1487821
         }
@@ -3427,7 +3427,8 @@
       message = input_message.val(),
       is_html = ($("input[name='_is_html']").val() == '1'),
       sig = this.env.identity,
-      delim = this.env.recipients_delimiter,
+      delim = this.env.recipients_separator,
+      rx_delim = RegExp.escape(delim),
       headers = ['replyto', 'bcc'];
 
     // update reply-to/bcc fields with addresses defined in identities
@@ -3444,16 +3445,18 @@
       }
 
       // cleanup
-      rx = new RegExp(RegExp.escape(delim) + '\\s*' + RegExp(delim), 'g');
-      input_val = input_val.replace(rx, delim)
-      rx = new RegExp('^\\s*' + RegExp.escape(delim) + '\\s*$');
-      input_val = input_val.replace(rx, '')
+      rx = new RegExp(rx_delim + '\\s*' + rx_delim, 'g');
+      input_val = input_val.replace(rx, delim);
+      rx = new RegExp('^[\\s' + rx_delim + ']+');
+      input_val = input_val.replace(rx, '');
 
       // add new address(es)
-      if (new_val) {
-        rx = new RegExp(RegExp.escape(delim) + '\\s*$');
-        if (input_val && !rx.test(input_val))
-          input_val += delim + ' ';
+      if (new_val && input_val.indexOf(new_val) == -1 && input_val.indexOf(new_val.replace(/"/g, '')) == -1) {
+        if (input_val) {
+          rx = new RegExp('[' + rx_delim + '\\s]+$')
+          input_val = input_val.replace(rx, '') + delim + ' ';
+        }
+
         input_val += new_val + delim + ' ';
       }
 
@@ -3639,7 +3642,12 @@
       att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
         + (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + att.html;
 
-    var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html);
+    var indicator, li = $('<li>');
+
+    li.attr('id', name)
+      .addClass(att.classname)
+      .html(att.html)
+      .on('mouseover', function() { rcube_webmail.long_subject_title_ex(this, 0); });
 
     // replace indicator's li
     if (upload_id && (indicator = document.getElementById(upload_id))) {
@@ -4345,7 +4353,7 @@
         boxtitle.append('&nbsp;&raquo;&nbsp;');
       }
 
-      boxtitle.append($('<span>'+prop.name+'</span>'));
+      boxtitle.append($('<span>').text(prop.name));
     }
 
     this.triggerEvent('groupupdate', prop);
@@ -6986,11 +6994,11 @@
   if (!elem.title) {
     var $elem = $(elem);
     if ($elem.width() + indent * 15 > $elem.parent().width())
-      elem.title = $elem.html();
+      elem.title = $elem.text();
   }
 };
 
-rcube_webmail.long_subject_title_ie = function(elem, indent)
+rcube_webmail.long_subject_title_ex = function(elem, indent)
 {
   if (!elem.title) {
     var $elem = $(elem),
diff --git a/program/js/editor.js b/program/js/editor.js
index e403d1f..e1ef368 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -80,6 +80,9 @@
   if (rcmail.env.default_font)
     $(tinyMCE.get(rcmail.env.composebody).getBody()).css('font-family', rcmail.env.default_font);
 
+  if (rcmail.env.default_font_size)
+    $(tinyMCE.get(rcmail.env.composebody).getBody()).css('font-size', rcmail.env.default_font_size);
+
   if (elem && elem.type == 'select-one') {
     rcmail.change_identity(elem);
     // Focus previously focused element
diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php
index ac3ea67..a374175 100644
--- a/program/lib/Roundcube/rcube_config.php
+++ b/program/lib/Roundcube/rcube_config.php
@@ -214,7 +214,7 @@
                     $success = true;
                 }
                 // deprecated name of config variable
-                else if (is_array($rcmail_config)) {
+                if (is_array($rcmail_config)) {
                     $this->merge($rcmail_config);
                     $success = true;
                 }
diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc
index f83f689..85aa954 100644
--- a/program/steps/mail/attachments.inc
+++ b/program/steps/mail/attachments.inc
@@ -118,9 +118,12 @@
           'alt' => rcube_label('delete')
         ));
       }
-      else {
+      else if ($COMPOSE['textbuttons']) {
         $button = Q(rcube_label('delete'));
       }
+      else {
+        $button = '';
+      }
 
       $content = html::a(array(
         'href' => "#delete",
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index e9f638c..30c9f79 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -148,6 +148,9 @@
   $OUTPUT->set_env('default_font', $font);
 }
 
+// default font size for HTML editor
+$OUTPUT->set_env('default_font_size', $RCMAIL->config->get('default_font_size'));
+
 // get reference message and set compose mode
 if ($msg_uid = $COMPOSE['param']['draft_uid']) {
   $compose_mode = RCUBE_COMPOSE_DRAFT;
@@ -1370,8 +1373,9 @@
   if (!$attrib['id'])
     $attrib['id'] = 'rcmAttachmentList';
 
-  $out = "\n";
+  $out    = "\n";
   $jslist = array();
+  $button = '';
 
   if (is_array($COMPOSE['attachments'])) {
     if ($attrib['deleteicon']) {
@@ -1380,27 +1384,38 @@
         'alt' => rcube_label('delete')
       ));
     }
-    else
+    else if (rcube_utils::get_boolean($attrib['textbuttons'])) {
       $button = Q(rcube_label('delete'));
+    }
 
     foreach ($COMPOSE['attachments'] as $id => $a_prop) {
       if (empty($a_prop))
         continue;
 
-      $out .= html::tag('li', array('id' => 'rcmfile'.$id, 'class' => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name'])),
+      $out .= html::tag('li',
+        array(
+          'id'          => 'rcmfile'.$id,
+          'class'       => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name']),
+          'onmouseover' => "rcube_webmail.long_subject_title_ex(this, 0)",
+        ),
         html::a(array(
             'href' => "#delete",
             'title' => rcube_label('delete'),
             'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id),
-            'class' => 'delete'),
-          $button) . Q($a_prop['name']));
+            'class' => 'delete'
+          ),
+          $button
+        ) . Q($a_prop['name'])
+      );
 
-        $jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']);
+      $jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']);
     }
   }
 
   if ($attrib['deleteicon'])
     $COMPOSE['deleteicon'] = $CONFIG['skin_path'] . $attrib['deleteicon'];
+  else if (rcube_utils::get_boolean($attrib['textbuttons']))
+    $COMPOSE['textbuttons'] = true;
   if ($attrib['cancelicon'])
     $OUTPUT->set_env('cancelicon', $CONFIG['skin_path'] . $attrib['cancelicon']);
   if ($attrib['loadingicon'])
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 779fb87..3f4475e 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -473,8 +473,9 @@
 $message_body = get_input_value('_message', RCUBE_INPUT_POST, TRUE, $message_charset);
 
 if ($isHtml) {
-  $font   = rcube_fontdefs($RCMAIL->config->get('default_font'));
-  $bstyle = $font && is_string($font) ? " style='font-family: $font'" : '';
+  $font_family = rcube_fontdefs($RCMAIL->config->get('default_font', 'Arial'));
+  $font_size = $RCMAIL->config->get('default_font_size');
+  $bstyle = ' style="font:' . $font_size . ' ' . $font_family . ';"';
 
   // append doctype and html/body wrappers
   $message_body = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">' .
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 59f4d55..9d85f9c 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -175,9 +175,9 @@
         $ol .= html::tag('li', null, Q(sprintf("%s (%s)", $filename, $size)));
       }
       else {
-        if (mb_strlen($filename) > 50) {
+        if ($attrib['maxlength'] && mb_strlen($filename) > $attrib['maxlength']) {
           $title    = $filename;
-          $filename = abbreviate_string($filename, 50);
+          $filename = abbreviate_string($filename, $attrib['maxlength']);
         }
         else {
           $title = '';
@@ -190,6 +190,7 @@
             'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
             'onclick' => sprintf('return %s.command(\'load-attachment\',\'%s\',this)',
               JS_OBJECT_NAME, $attach_prop->mime_id),
+            'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)',
             'title' => Q($title),
             ), Q($filename));
         $ol .= html::tag('li', array('class' => $class, 'id' => $id), $link);
diff --git a/program/steps/settings/edit_prefs.inc b/program/steps/settings/edit_prefs.inc
index 468e499..adf6b16 100644
--- a/program/steps/settings/edit_prefs.inc
+++ b/program/steps/settings/edit_prefs.inc
@@ -40,24 +40,21 @@
 
   $out = $form_start;
 
-  foreach ($SECTIONS[$CURR_SECTION]['blocks'] as $block) {
+  foreach ($SECTIONS[$CURR_SECTION]['blocks'] as $class => $block) {
     if (!empty($block['options'])) {
       $table = new html_table(array('cols' => 2));
 
       foreach ($block['options'] as $option) {
-        if ($option['advanced'])
-	      $table->set_row_attribs('advanced');
-
         if (isset($option['title'])) {
           $table->add('title', $option['title']);
-  	      $table->add(null, $option['content']);
+          $table->add(null, $option['content']);
         }
         else {
           $table->add(array('colspan' => 2), $option['content']);
         }
       }
 
-      $out .= html::tag('fieldset', null, html::tag('legend', null, $block['name']) . $table->show($attrib));
+      $out .= html::tag('fieldset', $class, html::tag('legend', null, $block['name']) . $table->show($attrib));
     }
     else if (!empty($block['content'])) {
       $out .= html::tag('fieldset', null, html::tag('legend', null, $block['name']) . $block['content']);
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index fdc07be..53c98ed 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -158,6 +158,7 @@
                 'main'    => array('name' => Q(rcube_label('mainoptions'))),
                 'skin'    => array('name' => Q(rcube_label('skin'))),
                 'browser' => array('name' => Q(rcube_label('browseroptions'))),
+                'advanced'=> array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             // language selection
@@ -367,6 +368,7 @@
             $blocks = array(
                 'main'        => array('name' => Q(rcube_label('mainoptions'))),
                 'new_message' => array('name' => Q(rcube_label('newmessage'))),
+                'advanced'    => array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             // show config parameter for preview pane
@@ -488,6 +490,7 @@
         case 'mailview':
             $blocks = array(
                 'main' => array('name' => Q(rcube_label('mainoptions'))),
+                'advanced'   => array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             // show checkbox to open message view in new window
@@ -543,7 +546,7 @@
 
                 $field_id = 'rcmfd_default_charset';
 
-                $blocks['main']['options']['default_charset'] = array(
+                $blocks['advanced']['options']['default_charset'] = array(
                     'title' => html::label($field_id, Q(rcube_label('defaultcharset'))),
                     'content' => $RCMAIL->output->charset_selector(array(
                         'id' => $field_id, 'name' => '_default_charset', 'selected' => $config['default_charset']
@@ -605,6 +608,7 @@
                 'main'       => array('name' => Q(rcube_label('mainoptions'))),
                 'sig'        => array('name' => Q(rcube_label('signatureoptions'))),
                 'spellcheck' => array('name' => Q(rcube_label('spellcheckoptions'))),
+                'advanced'   => array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             // show checkbox to compose messages in a new window
@@ -673,8 +677,7 @@
                 $select->add(rcube_label('miscfolding'), 1);
                 $select->add(rcube_label('2047folding'), 2);
 
-                $blocks['main']['options']['mime_param_folding'] = array(
-                    'advanced' => true,
+                $blocks['advanced']['options']['mime_param_folding'] = array(
                     'title'    => html::label($field_id, Q(rcube_label('mimeparamfolding'))),
                     'content'  => $select->show($config['mime_param_folding']),
                 );
@@ -688,8 +691,7 @@
                 $field_id = 'rcmfd_force_7bit';
                 $input    = new html_checkbox(array('name' => '_force_7bit', 'id' => $field_id, 'value' => 1));
 
-                $blocks['main']['options']['force_7bit'] = array(
-                    'advanced' => true,
+                $blocks['advanced']['options']['force_7bit'] = array(
                     'title'    => html::label($field_id, Q(rcube_label('force7bit'))),
                     'content'  => $input->show($config['force_7bit']?1:0),
                 );
@@ -842,23 +844,28 @@
                     continue 2;
                 }
 
-                $field_id = 'rcmfd_default_font';
-                $fonts    = rcube_fontdefs();
-                $selected = $config['default_font'];
+                // Default font size
+                $field_id = 'rcmfd_default_font_size';
+                $select_default_font_size = new html_select(array('name' => '_default_font_size', 'id' => $field_id));
 
-                $select = '<select name="_default_font" id="'.$field_id.'">';
-                $select .= '<option value=""' . (!$selected ? ' selected="selected"' : '') . '>---</option>';
-                foreach ($fonts as $fname => $font) {
-                    $select .= '<option value="'.$fname.'"'
-                        . ($fname == $selected ? ' selected="selected"' : '')
-                        . ' style=\'font-family: ' . $font . '\'>'
-                        . Q($fname) . '</option>';
+                $fontsizes = array('8pt', '10pt', '12pt', '14pt', '18pt', '24pt', '36pt');
+                foreach ($fontsizes as $size) {
+                    $select_default_font_size->add($size, $size);
                 }
-                $select .= '</select>';
+
+                // Default font
+                $field_id = 'rcmfd_default_font';
+                $select_default_font = new html_select(array('name' => '_default_font', 'id' => $field_id));
+
+                $fonts = rcube_fontdefs();
+                foreach ($fonts as $fname => $font) {
+                    $select_default_font->add($fname, $fname);
+                }
 
                 $blocks['main']['options']['default_font'] = array(
                     'title' => html::label($field_id, Q(rcube_label('defaultfont'))),
-                    'content' => $select
+                    'content' => $select_default_font->show($RCMAIL->config->get('default_font', 1)) .
+                        $select_default_font_size->show($RCMAIL->config->get('default_font_size', 1))
                 );
             }
         break;
@@ -866,7 +873,8 @@
         // Addressbook config
         case 'addressbook':
             $blocks = array(
-                'main' => array('name' => Q(rcube_label('mainoptions'))),
+                'main'     => array('name' => Q(rcube_label('mainoptions'))),
+                'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             if (!isset($no_override['default_addressbook'])
@@ -962,7 +970,8 @@
         // Special IMAP folders
         case 'folders':
             $blocks = array(
-                'main' => array('name' => Q(rcube_label('mainoptions'))),
+                'main'     => array('name' => Q(rcube_label('mainoptions'))),
+                'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             if (!isset($no_override['show_real_foldernames'])) {
@@ -1043,6 +1052,7 @@
             $blocks = array(
                 'main'        => array('name' => Q(rcube_label('mainoptions'))),
                 'maintenance' => array('name' => Q(rcube_label('maintenance'))),
+                'advanced'    => array('name' => Q(rcube_label('advancedoptions'))),
             );
 
             if (!isset($no_override['read_when_deleted'])) {
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index 3e8b1d1..717c7ad 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -89,6 +89,7 @@
       'reply_mode'         => isset($_POST['_reply_mode']) ? intval($_POST['_reply_mode']) : 0,
       'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
       'default_font'       => get_input_value('_default_font', RCUBE_INPUT_POST),
+      'default_font_size'  => get_input_value('_default_font_size', RCUBE_INPUT_POST),
       'forward_attachment' => !empty($_POST['_forward_attachment']),
     );
 
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index f712069..4336774 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -1594,9 +1594,7 @@
   height: 18px;
   line-height: 16px;
   font-size: 11px;
-  padding-left: 2px;
-  padding-top: 2px;
-  padding-right: 4px;
+  padding: 2px 2px 1px 2px;
   border-bottom: 1px solid #EBEBEB;
   white-space: nowrap;
   overflow: hidden;
@@ -1609,8 +1607,10 @@
   text-indent: -5000px;
   width: 17px;
   height: 16px;
+  padding-bottom: 2px;
   display: inline-block;
   text-decoration: none;
+  vertical-align: middle;
 }
 
 #compose-attachments li img
diff --git a/skins/classic/templates/message.html b/skins/classic/templates/message.html
index 757c0a6..bd4fbf2 100644
--- a/skins/classic/templates/message.html
+++ b/skins/classic/templates/message.html
@@ -49,7 +49,7 @@
   </div>
 <roundcube:object name="messageHeaders" class="headers-table" cellspacing="0" cellpadding="2" addicon="/images/icons/silhouette.png" summary="Message headers" />
 <roundcube:object name="messageFullHeaders" id="full-headers" />
-<roundcube:object name="messageAttachments" id="attachment-list" />
+<roundcube:object name="messageAttachments" id="attachment-list" maxlength="50" />
 <roundcube:object name="messageObjects" id="message-objects" />
 <roundcube:object name="messageBody" id="messagebody" />
 </div>
diff --git a/skins/classic/templates/messagepreview.html b/skins/classic/templates/messagepreview.html
index b42a063..82414c4 100644
--- a/skins/classic/templates/messagepreview.html
+++ b/skins/classic/templates/messagepreview.html
@@ -20,7 +20,7 @@
   </div>
 <roundcube:object name="messageHeaders" class="headers-table" cellspacing="0" cellpadding="2" addicon="/images/icons/silhouette.png" summary="Message headers" />
 <roundcube:object name="messageFullHeaders" id="full-headers" />
-<roundcube:object name="messageAttachments" id="attachment-list" />
+<roundcube:object name="messageAttachments" id="attachment-list" maxlength="50" />
 </div>
 
 <roundcube:object name="messageObjects" id="message-objects" />
diff --git a/skins/larry/addressbook.css b/skins/larry/addressbook.css
index 6bf9426..39d0cce 100644
--- a/skins/larry/addressbook.css
+++ b/skins/larry/addressbook.css
@@ -387,3 +387,8 @@
 	overflow: auto;
 	padding: 10px;
 }
+
+#import-box p,
+#import-box .propform {
+  max-width: 50em;
+}
diff --git a/skins/larry/settings.css b/skins/larry/settings.css
index 59037ac..6afa48c 100644
--- a/skins/larry/settings.css
+++ b/skins/larry/settings.css
@@ -48,6 +48,26 @@
 	border-radius: 4px 4px 0 0;
 }
 
+#preferences-details fieldset.advanced legend {
+	position: relative;
+	display: block;
+	width: 100%;
+	cursor: pointer;
+}
+
+#preferences-details fieldset.advanced .propform {
+	display: none;
+}
+
+#preferences-details fieldset.advanced .advanced-toggle {
+	position: absolute;
+	top: 2px;
+	right: 6px;
+	text-decoration: none;
+	color: #666;
+	font-size: 11px;
+}
+
 #sections-table tbody td.section,
 #settings-sections span.listitem a,
 #settings-sections span.tablink a {
diff --git a/skins/larry/templates/importcontacts.html b/skins/larry/templates/importcontacts.html
index d3d0f2b..69b138b 100644
--- a/skins/larry/templates/importcontacts.html
+++ b/skins/larry/templates/importcontacts.html
@@ -18,7 +18,7 @@
 <h2 class="boxtitle"><roundcube:label name="importcontacts" /></h2>
 
 <div id="import-box" class="boxcontent">
-<roundcube:object name="importstep" />
+<roundcube:object name="importstep" class="propform" />
 <br/>
 <p class="formbuttons">
 	<roundcube:object name="importnav" class="button" />
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index ae14d81..d558f16 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -195,6 +195,19 @@
         new rcube_splitter({ id:'prefviewsplitter', p1:'#sectionslist', p2:'#preferences-box',
           orientation:'v', relative:true, start:266, min:180, size:12 }).init();
       }
+      else if (rcmail.env.action == 'edit-prefs') {
+        $('<a href="#toggle">&#9660;</a>')
+            .addClass('advanced-toggle')
+            .appendTo('#preferences-details fieldset.advanced legend');
+
+          $('#preferences-details fieldset.advanced legend').click(function(e){
+            var collapsed = $(this).hasClass('collapsed'),
+              toggle = $('.advanced-toggle', this).html(collapsed ? '&#9650;' : '&#9660;');
+            $(this)
+              .toggleClass('collapsed')
+              .closest('fieldset').children('.propform').toggle()
+          }).addClass('collapsed')
+      }
     }
     /***  addressbook task  ***/
     else if (rcmail.env.task == 'addressbook') {
diff --git a/tests/Framework/Browser.php b/tests/Framework/Browser.php
index c3860d8..832d4bf 100644
--- a/tests/Framework/Browser.php
+++ b/tests/Framework/Browser.php
@@ -17,4 +17,207 @@
 
         $this->assertInstanceOf('rcube_browser', $object, "Class constructor");
     }
+
+    /**
+     * @dataProvider browsers
+     */
+    function test_browser($useragent, $opera, $chrome, $ie, $ns, $ns4, $khtml, $safari, $mz)
+    {
+
+        $object = $this->getBrowser($useragent);
+
+        $this->assertEquals($opera, $object->opera, 'Check for Opera failed');
+        $this->assertEquals($chrome, $object->chrome, 'Check for Chrome failed');
+        $this->assertEquals($ie, $object->ie, 'Check for IE failed');
+        $this->assertEquals($ns, $object->ns, 'Check for NS failed');
+        $this->assertEquals($ns4, $object->ns4, 'Check for NS4 failed');
+        $this->assertEquals($khtml, $object->khtml, 'Check for khtml failed');
+        $this->assertEquals($safari, $object->safari, 'Check for Safari failed');
+        $this->assertEquals($mz, $object->mz, 'Check for MZ failed');
+    }
+
+    /**
+     * @dataProvider os
+     */
+    function test_os($useragent, $windows, $linux, $unix, $mac)
+    {
+        $object = $this->getBrowser($useragent);
+
+        $this->assertEquals($windows, $object->win, 'Check Result of Windows');
+        $this->assertEquals($linux, $object->linux, 'Check Result of Linux');
+        $this->assertEquals($mac, $object->mac, 'Check Result of Mac');
+        $this->assertEquals($unix, $object->unix, 'Check Result of Unix');
+
+    }
+
+    /**
+     * @dataProvider versions
+     */
+    function test_version($useragent, $version)
+    {
+        $object = $this->getBrowser($useragent);
+        $this->assertEquals($version, $object->ver);
+    }
+
+    /**
+     * @dataProvider dom
+     */
+    function test_dom($useragent, $dom)
+    {
+        $object = $this->getBrowser($useragent);
+        $this->assertEquals($dom, $object->dom);
+
+    }
+
+    /**
+     * @dataProvider pngalpha
+     */
+    function test_pngalpha($useragent, $pngalpha)
+    {
+        $object = $this->getBrowser($useragent);
+        $this->assertEquals($pngalpha, $object->pngalpha);
+    }
+
+    /**
+     * @dataProvider imgdata
+     */
+    function test_imgdata($useragent, $imgdata)
+    {
+        $object = $this->getBrowser($useragent);
+        $this->assertEquals($imgdata, $object->imgdata);
+    }
+
+    function versions()
+    {
+        return $this->extractDataSet(array('version'));
+    }
+
+    function pngalpha()
+    {
+        return $this->extractDataSet(array('canPNGALPHA'));
+    }
+
+    function imgdata()
+    {
+        return $this->extractDataSet(array('canIMGDATA'));
+    }
+
+    private function extractDataSet($keys)
+    {
+        $keys = array_merge(array('useragent'), $keys);
+
+        $browser = $this->useragents();
+
+        $extracted = array();
+
+        foreach ($browser as $label => $data) {
+            foreach($keys as $key) {
+                $extracted[$data['useragent']][] = $data[$key];
+            }
+
+        }
+
+        return $extracted;
+    }
+
+    function lang()
+    {
+        return $this->extractDataSet(array('lang'));
+    }
+
+    function dom()
+    {
+        return $this->extractDataSet(array('hasDOM'));
+    }
+
+    function browsers()
+    {
+        return $this->extractDataSet(array('isOpera','isChrome','isIE','isNS','isNS4','isKHTML','isSafari','isMZ'));
+    }
+
+    function useragents()
+    {
+        return array(
+             'WIN: Mozilla Firefox ' => array(
+                 'useragent'    => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1',
+                 'version'      => '1.8',                                                                                      //Version
+                 'isWin'        => true,                                                                                           //isWindows
+                 'isLinux'      => false,
+                 'isMac'        => false,                                                                                           //isMac
+                 'isUnix'       => false,                                                                                           //isUnix
+                 'isOpera'      => false,                                                                                           //isOpera
+                 'isChrome'     => false,                                                                                           //isChrome
+                 'isIE'         => false,                                                                                           //isIE
+                 'isNS'         => false,                                                                                           //isNS
+                 'isNS4'        => false,                                                                                           //isNS4
+                 'isKHTML'      => false,                                                                                           //isKHTML
+                 'isSafari'     => false,                                                                                           //isSafari
+                 'isMZ'         => true,                                                                                           //isMZ
+                 'lang'         => 'en-US',                                                                               //lang
+                 'hasDOM'       => true,                                                                                            //hasDOM
+                 'canPNGALPHA'  => true,                                                                                            //canPNGALPHA
+                 'canIMGDATA'   => true,                                                                                            //canIMGDATA
+             ),
+            'LINUX: Bon Echo ' => array(
+                 'useragent'    => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.1) Gecko/20070222 BonEcho/2.0.0.1',
+                 'version'      => '1.8',                                                                                      //Version
+                 'isWin'        => false,                                                                                           //isWindows
+                 'isLinux'      => true,
+                 'isMac'        => false,                                                                                           //isMac
+                 'isUnix'       => false,                                                                                           //isUnix
+                 'isOpera'      => false,                                                                                           //isOpera
+                 'isChrome'     => false,                                                                                           //isChrome
+                 'isIE'         => false,                                                                                           //isIE
+                 'isNS'         => false,                                                                                           //isNS
+                 'isNS4'        => false,                                                                                           //isNS4
+                 'isKHTML'      => false,                                                                                           //isKHTML
+                 'isSafari'     => false,                                                                                           //isSafari
+                 'isMZ'         => true,                                                                                           //isMZ
+                 'lang'         => 'en-US',                                                                               //lang
+                 'hasDOM'       => true,                                                                                            //hasDOM
+                 'canPNGALPHA'  => true,                                                                                            //canPNGALPHA
+                 'canIMGDATA'   => true,                                                                                            //canIMGDATA
+             ),
+
+            'Chrome Mac' => array(
+                 'useragent'    => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3',
+                 'version'      => '5',                                                                                      //Version
+                 'isWin'        => false,                                                                                           //isWindows
+                 'isLinux'      => false,
+                 'isMac'        => true,                                                                                           //isMac
+                 'isUnix'       => false,                                                                                           //isUnix
+                 'isOpera'      => false,                                                                                           //isOpera
+                 'isChrome'     => true,                                                                                           //isChrome
+                 'isIE'         => false,                                                                                           //isIE
+                 'isNS'         => false,                                                                                           //isNS
+                 'isNS4'        => false,                                                                                           //isNS4
+                 'isKHTML'      => true,                                                                                           //isKHTML
+                 'isSafari'     => false,                                                                                           //isSafari
+                 'isMZ'         => false,                                                                                           //isMZ
+                 'lang'         => 'en-US',                                                                               //lang
+                 'hasDOM'       => false,                                                                                            //hasDOM
+                 'canPNGALPHA'  => false,                                                                                            //canPNGALPHA
+                 'canIMGDATA'   => true,                                                                                            //canIMGDATA
+             ),
+        );
+    }
+
+    function os()
+    {
+        return $this->extractDataSet(array('isWin','isLinux','isUnix','isMac'));
+    }
+
+    /**
+     * @param string $useragent
+     * @return rcube_browser
+     */
+    private function getBrowser($useragent)
+    {
+        /** @var $object rcube_browser */
+        $_SERVER['HTTP_USER_AGENT'] = $useragent;
+
+        $object = new rcube_browser();
+
+        return $object;
+    }
 }

--
Gitblit v1.9.1