Merge branch 'master' of github.com:roundcube/roundcubemail

pull/13/head
Thomas Bruederli 13 years ago
commit 2950ce49ef

@ -1,6 +1,15 @@
CHANGELOG Roundcube Webmail 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 mail attachments using drag & drop on HTML5 enabled browsers
- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585) - Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
- Decode header value in rcube_mime::get() by default (#1488511) - Decode header value in rcube_mime::get() by default (#1488511)

@ -40,7 +40,7 @@ CREATE TABLE [dbo].[contacts] (
[changed] [datetime] NOT NULL , [changed] [datetime] NOT NULL ,
[del] [char] (1) COLLATE Latin1_General_CI_AI NOT NULL , [del] [char] (1) COLLATE Latin1_General_CI_AI NOT NULL ,
[name] [varchar] (128) 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 , [firstname] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[surname] [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 , [vcard] [text] COLLATE Latin1_General_CI_AI NULL ,

@ -246,6 +246,19 @@ GO
-- Updates from version 0.7 -- 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 ALTER TABLE [dbo].[contacts] ALTER COLUMN [email] [text] COLLATE Latin1_General_CI_AI NOT NULL
GO 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

@ -1997,6 +1997,31 @@ class rcmail extends rcube
} }
/**
* 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) ********* ********* Deprecated methods (to be removed) *********
***********************************************************************/ ***********************************************************************/

@ -933,7 +933,7 @@ class rcube_output_html extends rcube_output
// make valid href to specific buttons // make valid href to specific buttons
if (in_array($attrib['command'], rcmail::$main_tasks)) { if (in_array($attrib['command'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('task' => $attrib['command'])); $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)) { else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task'])); $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
@ -956,7 +956,7 @@ class rcube_output_html extends rcube_output
} }
else if ($command && !$attrib['onclick']) { else if ($command && !$attrib['onclick']) {
$attrib['onclick'] = sprintf( $attrib['onclick'] = sprintf(
"return %s.command('%s','%s',this)", "return %s.command('%s','%s',this,event)",
rcmail::JS_OBJECT_NAME, rcmail::JS_OBJECT_NAME,
$command, $command,
$attrib['prop'] $attrib['prop']
@ -1485,7 +1485,7 @@ class rcube_output_html extends rcube_output
if (empty($attrib['form'])) { if (empty($attrib['form'])) {
$out = $this->form_tag(array( $out = $this->form_tag(array(
'name' => "rcmqsearchform", 'name' => "rcmqsearchform",
'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search');return false;", 'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search'); return false",
'style' => "display:inline"), 'style' => "display:inline"),
$out); $out);
} }

@ -312,8 +312,9 @@ class rcube_vcard
break; break;
case 'birthday': case 'birthday':
if ($val = rcube_strtotime($value)) case 'anniversary':
$this->raw['BDAY'][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); if (($val = rcube_strtotime($value)) && ($fn = self::$fieldmap[$field]))
$this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
break; break;
case 'address': case 'address':

@ -493,7 +493,7 @@ function rcube_webmail()
/*********************************************************/ /*********************************************************/
// execute a specific command on the web client // 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; var ret, uid, cid, url, flag;
@ -713,7 +713,7 @@ function rcube_webmail()
case 'delete': case 'delete':
// mail task // mail task
if (this.task == 'mail') if (this.task == 'mail')
this.delete_messages(); this.delete_messages(event);
// addressbook task // addressbook task
else if (this.task == 'addressbook') else if (this.task == 'addressbook')
this.delete_contacts(); this.delete_contacts();
@ -1827,7 +1827,7 @@ function rcube_webmail()
html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>'; html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
} }
else if (c == 'attachment') { 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>'; html = '<span class="attachment">&nbsp;</span>';
else if (/multipart\/report/.test(flags.ctype)) else if (/multipart\/report/.test(flags.ctype))
html = '<span class="report">&nbsp;</span>'; html = '<span class="report">&nbsp;</span>';
@ -2555,7 +2555,7 @@ function rcube_webmail()
}; };
// delete selected messages from the current mailbox // 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, var uid, i, len, trash = this.env.trash_mailbox,
list = this.message_list, list = this.message_list,
@ -2587,7 +2587,7 @@ function rcube_webmail()
// if there is a trash mailbox defined and we're not currently in it // if there is a trash mailbox defined and we're not currently in it
else { else {
// if shift was pressed delete it immediately // 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'))) if (confirm(this.get_label('deletemessagesconfirm')))
this.permanently_remove_messages(); this.permanently_remove_messages();
} }

@ -48,7 +48,7 @@
* @author Aleksander Machniak <alec@php.net> * @author Aleksander Machniak <alec@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net> * @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License * @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 * @link http://pear.php.net/package/Mail_mime
* *
* This class is based on HTML Mime Mail class from * This class is based on HTML Mime Mail class from
@ -89,7 +89,7 @@ require_once 'Mail/mimePart.php';
* @author Sean Coates <sean@php.net> * @author Sean Coates <sean@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net> * @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License * @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 * @link http://pear.php.net/package/Mail_mime
*/ */
class Mail_mime class Mail_mime
@ -387,7 +387,8 @@ class Mail_mime
* @param string $description Content-Description header * @param string $description Content-Description header
* @param string $h_charset The character set of the headers e.g. filename * @param string $h_charset The character set of the headers e.g. filename
* If not specified, $charset will be used * 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 * @return mixed True on success or PEAR_Error object
* @access public * @access public

@ -48,7 +48,7 @@
* @author Aleksander Machniak <alec@php.net> * @author Aleksander Machniak <alec@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net> * @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License * @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 * @link http://pear.php.net/package/Mail_mime
*/ */
@ -70,7 +70,7 @@
* @author Aleksander Machniak <alec@php.net> * @author Aleksander Machniak <alec@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net> * @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License * @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 * @link http://pear.php.net/package/Mail_mime
*/ */
class Mail_mimePart class Mail_mimePart
@ -156,7 +156,8 @@ class Mail_mimePart
* headers_charset - Charset of the headers e.g. filename, description. * headers_charset - Charset of the headers e.g. filename, description.
* If not set, 'charset' will be used * If not set, 'charset' will be used
* eol - End of line sequence. Default: "\r\n" * 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) * body_file - Location of file with part's body (instead of $body)
* *
* @access public * @access public
@ -222,13 +223,17 @@ class Mail_mimePart
$params['headers_charset'] = $params['charset']; $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'])) { if (!empty($params['filename'])) {
$headers['Content-Type'] .= ';' . $this->_eol; $headers['Content-Type'] .= ';' . $this->_eol;
$headers['Content-Type'] .= $this->_buildHeaderParam( $headers['Content-Type'] .= $this->_buildHeaderParam(
'name', $params['filename'], 'name', $params['filename'], $h_charset, $h_language, $h_encoding
!empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII',
!empty($params['language']) ? $params['language'] : null,
!empty($params['name_encoding']) ? $params['name_encoding'] : null
); );
} }
@ -238,23 +243,41 @@ class Mail_mimePart
if (!empty($params['filename'])) { if (!empty($params['filename'])) {
$headers['Content-Disposition'] .= ';' . $this->_eol; $headers['Content-Disposition'] .= ';' . $this->_eol;
$headers['Content-Disposition'] .= $this->_buildHeaderParam( $headers['Content-Disposition'] .= $this->_buildHeaderParam(
'filename', $params['filename'], 'filename', $params['filename'], $h_charset, $h_language,
!empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII',
!empty($params['language']) ? $params['language'] : null,
!empty($params['filename_encoding']) ? $params['filename_encoding'] : null !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'])) { if (!empty($params['description'])) {
$headers['Content-Description'] = $this->encodeHeader( $headers['Content-Description'] = $this->encodeHeader(
'Content-Description', $params['description'], 'Content-Description', $params['description'], $h_charset, $h_encoding,
!empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII',
!empty($params['name_encoding']) ? $params['name_encoding'] : 'quoted-printable',
$this->_eol $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 // Default encoding
if (!isset($this->_encoding)) { if (!isset($this->_encoding)) {
$this->_encoding = '7bit'; $this->_encoding = '7bit';

@ -249,12 +249,11 @@ class html2text
* @access public * @access public
*/ */
var $callback_search = array( var $callback_search = array(
'/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
// <a href=""> '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i', // h1 - h6
'/<(h)[123456][^>]*>(.*?)<\/h[123456]>/i', // H1 - H3 '/<(b)( [^>]*)?>(.*?)<\/b>/i', // <b>
'/<(b)[^>]*>(.*?)<\/b>/i', // <b> '/<(strong)( [^>]*)?>(.*?)<\/strong>/i', // <strong>
'/<(strong)[^>]*>(.*?)<\/strong>/i', // <strong> '/<(th)( [^>]*)?>(.*?)<\/th>/i', // <th> and </th>
'/<(th)[^>]*>(.*?)<\/th>/i', // <th> and </th>
); );
/** /**
@ -560,11 +559,11 @@ class html2text
} }
// Ignored link types // Ignored link types
if (preg_match('!^(javascript|mailto|#):!i', $link)) { if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
return $display; return $display;
} }
if (preg_match('!^(https?://)!i', $link)) { if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
$url = $link; $url = $link;
} }
else { else {
@ -576,8 +575,8 @@ class html2text
} }
if (($index = array_search($url, $this->_link_list)) === false) { if (($index = array_search($url, $this->_link_list)) === false) {
$this->_link_list[] = $url;
$index = count($this->_link_list); $index = count($this->_link_list);
$this->_link_list[] = $url;
} }
return $display . ' [' . ($index+1) . ']'; return $display . ' [' . ($index+1) . ']';
@ -593,12 +592,20 @@ class html2text
{ {
// get the content of PRE element // get the content of PRE element
while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) { 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 // convert the content
$this->pre_content = sprintf('<div><br>%s<br></div>', $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) // 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); array('html2text', '_preg_pre_callback'), $text, 1);
// free memory // free memory
$this->pre_content = ''; $this->pre_content = '';
} }
@ -671,11 +678,11 @@ class html2text
switch (strtolower($matches[1])) { switch (strtolower($matches[1])) {
case 'b': case 'b':
case 'strong': case 'strong':
return $this->_toupper($matches[2]); return $this->_toupper($matches[3]);
case 'th': case 'th':
return $this->_toupper("\t\t". $matches[2] ."\n"); return $this->_toupper("\t\t". $matches[3] ."\n");
case 'h': case 'h':
return $this->_toupper("\n\n". $matches[2] ."\n\n"); return $this->_toupper("\n\n". $matches[3] ."\n\n");
case 'a': case 'a':
// Remove spaces in URL (#1487805) // Remove spaces in URL (#1487805)
$url = str_replace(' ', '', $matches[3]); $url = str_replace(' ', '', $matches[3]);

@ -202,7 +202,7 @@ class washtml
$key = strtolower($key); $key = strtolower($key);
$value = $node->getAttribute($key); $value = $node->getAttribute($key);
if (isset($this->_html_attribs[$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) . '"'; $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
else if ($key == 'style' && ($style = $this->wash_style($value))) { else if ($key == 'style' && ($style = $this->wash_style($value))) {
$quot = strpos($style, '"') !== false ? "'" : '"'; $quot = strpos($style, '"') !== false ? "'" : '"';

@ -947,7 +947,7 @@ function rcmail_message_headers($attrib, $headers=NULL)
// single header value is requested // single header value is requested
if (!empty($attrib['valueof'])) 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 // compose html table
$table = new html_table(array('cols' => 2)); $table = new html_table(array('cols' => 2));
@ -1477,7 +1477,7 @@ function rcmail_draftinfo_decode($str)
function rcmail_message_part_controls($attrib) function rcmail_message_part_controls($attrib)
{ {
global $MESSAGE; global $MESSAGE, $RCMAIL;
$part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC)); $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]) if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
@ -1497,10 +1497,8 @@ function rcmail_message_part_controls($attrib)
$table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download')))); $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('title', Q(rcube_label('filesize')));
$table->add('header', Q(show_bytes($part->size))); $table->add('header', Q($RCMAIL->message_part_size($part)));
}
return $table->show($attrib); return $table->show($attrib);
} }

@ -199,13 +199,18 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) {
$sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, false, $stdout); $sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, false, $stdout);
} }
} }
// send part as-it-is
else { else {
// turn off output buffering and print part content
if ($part->body) { if ($part->body) {
header("Content-Length: " . sizeof($part->body));
echo $part->body; echo $part->body;
$sent = true; $sent = true;
} }
else if ($part->size) { 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); $sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, true);
} }
} }

@ -144,7 +144,7 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) {
function rcmail_message_attachments($attrib) function rcmail_message_attachments($attrib)
{ {
global $PRINT_MODE, $MESSAGE; global $PRINT_MODE, $MESSAGE, $RCMAIL;
$out = $ol = ''; $out = $ol = '';
@ -156,7 +156,8 @@ function rcmail_message_attachments($attrib)
} }
if ($PRINT_MODE) { 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 { else {
if (mb_strlen($filename) > 50) { if (mb_strlen($filename) > 50) {

@ -377,6 +377,7 @@ a.iconbutton.threadmode.selected {
#messagelist tr td.size { #messagelist tr td.size {
width: 60px; width: 60px;
text-align: right;
} }
#messagelist tr td.from, #messagelist tr td.from,

Loading…
Cancel
Save