From 2950ce49eff42eb08cd4363975a3292692cbccd8 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 11 Jun 2012 04:20:53 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 program/lib/Mail/mime.php             |    7 +
 CHANGELOG                             |    9 ++
 program/lib/html2text.php             |   37 +++++---
 program/steps/mail/show.inc           |    5 
 program/lib/Mail/mimePart.php         |   49 +++++++++---
 program/include/rcube_output_html.php |    6 
 program/include/rcmail.php            |   25 ++++++
 program/steps/mail/func.inc           |   12 +-
 program/include/rcube_vcard.php       |    5 
 program/steps/mail/get.inc            |    7 +
 SQL/mssql.initial.sql                 |    2 
 program/js/app.js                     |   10 +-
 skins/larry/mail.css                  |    1 
 SQL/mssql.upgrade.sql                 |   13 +++
 program/lib/washtml.php               |    2 
 15 files changed, 137 insertions(+), 53 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 8b0f067..3ac39c3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,15 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix storing X-ANNIVERSARY date in vCard format (#1488527)
+- Update to Mail_Mime-1.8.5 (#1488521)
+- Fix Shift + delete button does not permanently delete messages (#1488243)
+- Add Content-Length for attachments where possible (#1485478)
+- Fix attachment sizes in message print page and attachment preview page (#1488515)
+- Fix XSS vulnerability in message subject handling using Larry skin (#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
 - Add mail attachments using drag & drop on HTML5 enabled browsers
 - Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
 - Decode header value in rcube_mime::get() by default (#1488511)
diff --git a/SQL/mssql.initial.sql b/SQL/mssql.initial.sql
index 72c24f1..2ac6aa6 100644
--- a/SQL/mssql.initial.sql
+++ b/SQL/mssql.initial.sql
@@ -40,7 +40,7 @@
 	[changed] [datetime] NOT NULL ,
 	[del] [char] (1) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[name] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
-	[email] [text] COLLATE Latin1_General_CI_AI NOT NULL ,
+	[email] [varchar] (8000) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[firstname] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[surname] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[vcard] [text] COLLATE Latin1_General_CI_AI NULL ,
diff --git a/SQL/mssql.upgrade.sql b/SQL/mssql.upgrade.sql
index d4a5e41..d111ef3 100644
--- a/SQL/mssql.upgrade.sql
+++ b/SQL/mssql.upgrade.sql
@@ -246,6 +246,19 @@
 
 -- Updates from version 0.7
 
+ALTER TABLE [dbo].[contacts] DROP CONSTRAINT [DF_contacts_email]
+GO
 ALTER TABLE [dbo].[contacts] ALTER COLUMN [email] [text] COLLATE Latin1_General_CI_AI NOT NULL
 GO
+ALTER TABLE [dbo].[contacts] ADD CONSTRAINT [DF_contacts_email] DEFAULT ('') FOR [email]
+GO
 
+-- Updates from version 0.8-rc
+
+ALTER TABLE [dbo].[contacts] DROP CONSTRAINT [DF_contacts_email]
+GO
+ALTER TABLE [dbo].[contacts] ALTER COLUMN [email] [varchar] (8000) COLLATE Latin1_General_CI_AI NOT NULL
+GO
+ALTER TABLE [dbo].[contacts] ADD CONSTRAINT [DF_contacts_email] DEFAULT ('') FOR [email]
+GO
+
\ No newline at end of file
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index a10a2aa..8ec8cfe 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1997,6 +1997,31 @@
     }
 
 
+    /**
+     * Returns real size (calculated) of the message part
+     *
+     * @param rcube_message_part  Message part
+     *
+     * @return string Part size (and unit)
+     */
+    public function message_part_size($part)
+    {
+        if (isset($part->d_parameters['size'])) {
+            $size = $this->show_bytes((int)$part->d_parameters['size']);
+        }
+        else {
+          $size = $part->size;
+          if ($part->encoding == 'base64') {
+            $size = $size / 1.33;
+          }
+
+          $size = '~' . $this->show_bytes($size);
+        }
+
+        return $size;
+    }
+
+
     /************************************************************************
      *********          Deprecated methods (to be removed)          *********
      ***********************************************************************/
diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php
index 142755b..fce9615 100644
--- a/program/include/rcube_output_html.php
+++ b/program/include/rcube_output_html.php
@@ -933,7 +933,7 @@
             // make valid href to specific buttons
             if (in_array($attrib['command'], rcmail::$main_tasks)) {
                 $attrib['href']    = $this->app->url(array('task' => $attrib['command']));
-                $attrib['onclick'] = sprintf("%s.command('switch-task','%s');return false", rcmail::JS_OBJECT_NAME, $attrib['command']);
+                $attrib['onclick'] = sprintf("%s.command('switch-task','%s',null,event); return false", rcmail::JS_OBJECT_NAME, $attrib['command']);
             }
             else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
                 $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
@@ -956,7 +956,7 @@
         }
         else if ($command && !$attrib['onclick']) {
             $attrib['onclick'] = sprintf(
-                "return %s.command('%s','%s',this)",
+                "return %s.command('%s','%s',this,event)",
                 rcmail::JS_OBJECT_NAME,
                 $command,
                 $attrib['prop']
@@ -1485,7 +1485,7 @@
         if (empty($attrib['form'])) {
             $out = $this->form_tag(array(
                 'name' => "rcmqsearchform",
-                'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search');return false;",
+                'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search'); return false",
                 'style' => "display:inline"),
                 $out);
         }
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index b03ba9c..52545a0 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -312,8 +312,9 @@
         break;
 
       case 'birthday':
-        if ($val = rcube_strtotime($value))
-          $this->raw['BDAY'][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
+      case 'anniversary':
+        if (($val = rcube_strtotime($value)) && ($fn = self::$fieldmap[$field]))
+          $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
         break;
 
       case 'address':
diff --git a/program/js/app.js b/program/js/app.js
index cc4b8c2..ce75704 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -493,7 +493,7 @@
   /*********************************************************/
 
   // execute a specific command on the web client
-  this.command = function(command, props, obj)
+  this.command = function(command, props, obj, event)
   {
     var ret, uid, cid, url, flag;
 
@@ -713,7 +713,7 @@
       case 'delete':
         // mail task
         if (this.task == 'mail')
-          this.delete_messages();
+          this.delete_messages(event);
         // addressbook task
         else if (this.task == 'addressbook')
           this.delete_contacts();
@@ -1827,7 +1827,7 @@
         html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
       }
       else if (c == 'attachment') {
-        if (/application\/|multipart\/m/.test(flags.ctype))
+        if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
           html = '<span class="attachment">&nbsp;</span>';
         else if (/multipart\/report/.test(flags.ctype))
           html = '<span class="report">&nbsp;</span>';
@@ -2555,7 +2555,7 @@
   };
 
   // delete selected messages from the current mailbox
-  this.delete_messages = function()
+  this.delete_messages = function(event)
   {
     var uid, i, len, trash = this.env.trash_mailbox,
       list = this.message_list,
@@ -2587,7 +2587,7 @@
     // if there is a trash mailbox defined and we're not currently in it
     else {
       // if shift was pressed delete it immediately
-      if (list && list.modkey == SHIFT_KEY) {
+      if ((list && list.modkey == SHIFT_KEY) || (event && rcube_event.get_modifier(event) == SHIFT_KEY)) {
         if (confirm(this.get_label('deletemessagesconfirm')))
           this.permanently_remove_messages();
       }
diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php
index a7dfa6d..76c6ec2 100644
--- a/program/lib/Mail/mime.php
+++ b/program/lib/Mail/mime.php
@@ -48,7 +48,7 @@
  * @author    Aleksander Machniak <alec@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   1.8.4
+ * @version   1.8.5
  * @link      http://pear.php.net/package/Mail_mime
  *
  *            This class is based on HTML Mime Mail class from
@@ -89,7 +89,7 @@
  * @author    Sean Coates <sean@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   Release: 1.8.4
+ * @version   Release: 1.8.5
  * @link      http://pear.php.net/package/Mail_mime
  */
 class Mail_mime
@@ -387,7 +387,8 @@
      * @param string $description Content-Description header
      * @param string $h_charset   The character set of the headers e.g. filename
      *                            If not specified, $charset will be used
-     * @param array  $add_headers Additional part headers
+     * @param array  $add_headers Additional part headers. Array keys can be in form
+     *                            of <header_name>:<parameter_name>
      *
      * @return mixed              True on success or PEAR_Error object
      * @access public
diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php
index f3cd98e..4e4170d 100644
--- a/program/lib/Mail/mimePart.php
+++ b/program/lib/Mail/mimePart.php
@@ -48,7 +48,7 @@
  * @author    Aleksander Machniak <alec@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   1.8.4
+ * @version   1.8.5
  * @link      http://pear.php.net/package/Mail_mime
  */
 
@@ -70,7 +70,7 @@
  * @author    Aleksander Machniak <alec@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   Release: 1.8.4
+ * @version   Release: 1.8.5
  * @link      http://pear.php.net/package/Mail_mime
  */
 class Mail_mimePart
@@ -156,7 +156,8 @@
     *     headers_charset   - Charset of the headers e.g. filename, description.
     *                         If not set, 'charset' will be used
     *     eol               - End of line sequence. Default: "\r\n"
-    *     headers           - Hash array with additional part headers
+    *     headers           - Hash array with additional part headers. Array keys can be
+    *                         in form of <header_name>:<parameter_name>
     *     body_file         - Location of file with part's body (instead of $body)
     *
     * @access public
@@ -222,13 +223,17 @@
                 $params['headers_charset'] = $params['charset'];
             }
         }
+
+        // header values encoding parameters
+        $h_charset  = !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII';
+        $h_language = !empty($params['language']) ? $params['language'] : null;
+        $h_encoding = !empty($params['name_encoding']) ? $params['name_encoding'] : null;
+
+
         if (!empty($params['filename'])) {
             $headers['Content-Type'] .= ';' . $this->_eol;
             $headers['Content-Type'] .= $this->_buildHeaderParam(
-                'name', $params['filename'],
-                !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII',
-                !empty($params['language']) ? $params['language'] : null,
-                !empty($params['name_encoding']) ? $params['name_encoding'] : null
+                'name', $params['filename'], $h_charset, $h_language, $h_encoding
             );
         }
 
@@ -238,23 +243,41 @@
             if (!empty($params['filename'])) {
                 $headers['Content-Disposition'] .= ';' . $this->_eol;
                 $headers['Content-Disposition'] .= $this->_buildHeaderParam(
-                    'filename', $params['filename'],
-                    !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII',
-                    !empty($params['language']) ? $params['language'] : null,
+                    'filename', $params['filename'], $h_charset, $h_language,
                     !empty($params['filename_encoding']) ? $params['filename_encoding'] : null
                 );
+            }
+
+            // add attachment size
+            $size = $this->_body_file ? filesize($this->_body_file) : strlen($body);
+            if ($size) {
+                $headers['Content-Disposition'] .= ';' . $this->_eol . ' size=' . $size;
             }
         }
 
         if (!empty($params['description'])) {
             $headers['Content-Description'] = $this->encodeHeader(
-                'Content-Description', $params['description'],
-                !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII',
-                !empty($params['name_encoding']) ? $params['name_encoding'] : 'quoted-printable',
+                'Content-Description', $params['description'], $h_charset, $h_encoding,
                 $this->_eol
             );
         }
 
+        // Search and add existing headers' parameters
+        foreach ($headers as $key => $value) {
+            $items = explode(':', $key);
+            if (count($items) == 2) {
+                $header = $items[0];
+                $param  = $items[1];
+                if (isset($headers[$header])) {
+                    $headers[$header] .= ';' . $this->_eol;
+                }
+                $headers[$header] .= $this->_buildHeaderParam(
+                    $param, $value, $h_charset, $h_language, $h_encoding
+                );
+                unset($headers[$key]);
+            }
+        }
+
         // Default encoding
         if (!isset($this->_encoding)) {
             $this->_encoding = '7bit';
diff --git a/program/lib/html2text.php b/program/lib/html2text.php
index 84a7374..9de2e96 100644
--- a/program/lib/html2text.php
+++ b/program/lib/html2text.php
@@ -249,12 +249,11 @@
      *  @access public
      */
     var $callback_search = array(
-        '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i',
-                                                   // <a href="">
-        '/<(h)[123456][^>]*>(.*?)<\/h[123456]>/i', // H1 - H3
-        '/<(b)[^>]*>(.*?)<\/b>/i',                 // <b>
-        '/<(strong)[^>]*>(.*?)<\/strong>/i',       // <strong>
-        '/<(th)[^>]*>(.*?)<\/th>/i',               // <th> and </th>
+        '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
+        '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i',         // h1 - h6
+        '/<(b)( [^>]*)?>(.*?)<\/b>/i',                         // <b>
+        '/<(strong)( [^>]*)?>(.*?)<\/strong>/i',               // <strong>
+        '/<(th)( [^>]*)?>(.*?)<\/th>/i',                       // <th> and </th>
     );
 
    /**
@@ -368,7 +367,7 @@
     function set_html( $source, $from_file = false )
     {
         if ( $from_file && file_exists($source) ) {
-            $this->html = file_get_contents($source); 
+            $this->html = file_get_contents($source);
         }
         else
             $this->html = $source;
@@ -560,11 +559,11 @@
 	    }
 
         // Ignored link types
-	    if (preg_match('!^(javascript|mailto|#):!i', $link)) {
+	    if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
 		    return $display;
         }
 
-	    if (preg_match('!^(https?://)!i', $link)) {
+	    if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
             $url = $link;
         }
         else {
@@ -576,8 +575,8 @@
         }
 
         if (($index = array_search($url, $this->_link_list)) === false) {
-            $this->_link_list[] = $url;
             $index = count($this->_link_list);
+            $this->_link_list[] = $url;
         }
 
         return $display . ' [' . ($index+1) . ']';
@@ -593,12 +592,20 @@
     {
         // get the content of PRE element
         while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
+            $this->pre_content = $matches[1];
+
+            // Run our defined tags search-and-replace with callback
+            $this->pre_content = preg_replace_callback($this->callback_search,
+                array('html2text', '_preg_callback'), $this->pre_content);
+
             // convert the content
             $this->pre_content = sprintf('<div><br>%s<br></div>',
-                preg_replace($this->pre_search, $this->pre_replace, $matches[1]));
+                preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
+
             // replace the content (use callback because content can contain $0 variable)
-            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU', 
+            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
                 array('html2text', '_preg_pre_callback'), $text, 1);
+
             // free memory
             $this->pre_content = '';
         }
@@ -671,11 +678,11 @@
         switch (strtolower($matches[1])) {
         case 'b':
         case 'strong':
-            return $this->_toupper($matches[2]);
+            return $this->_toupper($matches[3]);
         case 'th':
-            return $this->_toupper("\t\t". $matches[2] ."\n");
+            return $this->_toupper("\t\t". $matches[3] ."\n");
         case 'h':
-            return $this->_toupper("\n\n". $matches[2] ."\n\n");
+            return $this->_toupper("\n\n". $matches[3] ."\n\n");
         case 'a':
             // Remove spaces in URL (#1487805)
             $url = str_replace(' ', '', $matches[3]);
diff --git a/program/lib/washtml.php b/program/lib/washtml.php
index 4221abd..6ea59f0 100644
--- a/program/lib/washtml.php
+++ b/program/lib/washtml.php
@@ -202,7 +202,7 @@
       $key = strtolower($key);
       $value = $node->getAttribute($key);
       if (isset($this->_html_attribs[$key]) ||
-         ($key == 'href' && preg_match('!^(http:|https:|ftp:|mailto:|//|#).+!i', $value)))
+         ($key == 'href' && 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 ? "'" : '"';
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 1e5dbda..5a18ded 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -947,7 +947,7 @@
 
   // single header value is requested
   if (!empty($attrib['valueof']))
-    return Q($plugin['output'][$attrib['valueof']]['value'], ($hkey == 'subject' ? 'strict' : 'show'));
+    return Q($plugin['output'][$attrib['valueof']]['value'], ($attrib['valueof'] == 'subject' ? 'strict' : 'show'));
 
   // compose html table
   $table = new html_table(array('cols' => 2));
@@ -1477,13 +1477,13 @@
 
 function rcmail_message_part_controls($attrib)
 {
-  global $MESSAGE;
+  global $MESSAGE, $RCMAIL;
 
   $part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC));
   if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
     return '';
 
-  $part = $MESSAGE->mime_parts[$part];
+  $part  = $MESSAGE->mime_parts[$part];
   $table = new html_table(array('cols' => 3));
 
   $filename = $part->filename;
@@ -1497,10 +1497,8 @@
     $table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
   }
 
-  if (!empty($part->size)) {
-    $table->add('title', Q(rcube_label('filesize')));
-    $table->add('header', Q(show_bytes($part->size)));
-  }
+  $table->add('title', Q(rcube_label('filesize')));
+  $table->add('header', Q($RCMAIL->message_part_size($part)));
 
   return $table->show($attrib);
 }
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
index 658538a..bcd57de 100644
--- a/program/steps/mail/get.inc
+++ b/program/steps/mail/get.inc
@@ -199,13 +199,18 @@
           $sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, false, $stdout);
         }
       }
+      // send part as-it-is
       else {
-        // turn off output buffering and print part content
         if ($part->body) {
+          header("Content-Length: " . sizeof($part->body));
           echo $part->body;
           $sent = true;
         }
         else if ($part->size) {
+          if ($size = (int)$part->d_parameters['size']) {
+            header("Content-Length: $size");
+          }
+
           $sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, true);
         }
       }
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 158ba31..5fa72d7 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -144,7 +144,7 @@
 
 function rcmail_message_attachments($attrib)
 {
-  global $PRINT_MODE, $MESSAGE;
+  global $PRINT_MODE, $MESSAGE, $RCMAIL;
 
   $out = $ol = '';
 
@@ -156,7 +156,8 @@
       }
 
       if ($PRINT_MODE) {
-        $ol .= html::tag('li', null, sprintf("%s (%s)", Q($filename), Q(show_bytes($attach_prop->size))));
+        $size = $RCMAIL->message_part_size($attach_prop);
+        $ol .= html::tag('li', null, Q(sprintf("%s (%s)", $filename, $size)));
       }
       else {
         if (mb_strlen($filename) > 50) {
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 0889b3b..ab87cd8 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -377,6 +377,7 @@
 
 #messagelist tr td.size {
 	width: 60px;
+	text-align: right;
 }
 
 #messagelist tr td.from,

--
Gitblit v1.9.1