Thomas Bruederli
2012-06-11 2950ce49eff42eb08cd4363975a3292692cbccd8
Merge branch 'master' of github.com:roundcube/roundcubemail
15 files modified
190 ■■■■ changed files
CHANGELOG 9 ●●●●● patch | view | raw | blame | history
SQL/mssql.initial.sql 2 ●●● patch | view | raw | blame | history
SQL/mssql.upgrade.sql 13 ●●●●● patch | view | raw | blame | history
program/include/rcmail.php 25 ●●●●● patch | view | raw | blame | history
program/include/rcube_output_html.php 6 ●●●● patch | view | raw | blame | history
program/include/rcube_vcard.php 5 ●●●●● patch | view | raw | blame | history
program/js/app.js 10 ●●●● patch | view | raw | blame | history
program/lib/Mail/mime.php 7 ●●●●● patch | view | raw | blame | history
program/lib/Mail/mimePart.php 49 ●●●● patch | view | raw | blame | history
program/lib/html2text.php 37 ●●●●● patch | view | raw | blame | history
program/lib/washtml.php 2 ●●● patch | view | raw | blame | history
program/steps/mail/func.inc 12 ●●●●● patch | view | raw | blame | history
program/steps/mail/get.inc 7 ●●●● patch | view | raw | blame | history
program/steps/mail/show.inc 5 ●●●●● patch | view | raw | blame | history
skins/larry/mail.css 1 ●●●● patch | view | raw | blame | history
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)
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 ,
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
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)          *********
     ***********************************************************************/
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);
        }
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':
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();
      }
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
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';
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]);
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 ? "'" : '"';
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);
}
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);
        }
      }
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) {
skins/larry/mail.css
@@ -377,6 +377,7 @@
#messagelist tr td.size {
    width: 60px;
    text-align: right;
}
#messagelist tr td.from,