From b46edc0f906e00f8cff42541f49f0d58181c836c Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Wed, 01 Dec 2010 04:52:23 -0500 Subject: [PATCH] - Fix "Cannot use scalar value..." warning (#1487066) --- program/steps/mail/func.inc | 584 +++++++++++++++++++++++++++++++++++++-------------------- 1 files changed, 377 insertions(+), 207 deletions(-) diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 55ca8eb..4803956 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -4,8 +4,8 @@ +-----------------------------------------------------------------------+ | program/steps/mail/func.inc | | | - | This file is part of the RoundCube Webmail client | - | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -24,10 +24,12 @@ $DRAFTS_MBOX = $RCMAIL->config->get('drafts_mbox'); $SEARCH_MODS_DEFAULT = array('*' => array('subject'=>1, 'from'=>1), $SENT_MBOX => array('subject'=>1, 'to'=>1), $DRAFTS_MBOX => array('subject'=>1, 'to'=>1)); -$EMAIL_ADDRESS_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9][a-z0-9\-\.]*\\.[a-z]{2,5})'; +// Simplified for IDN in Unicode +//$EMAIL_ADDRESS_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9][a-z0-9\-\.]*\\.[a-z]{2,5})'; +$EMAIL_ADDRESS_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.[a-z]{2,5})'; -// actions that do not require imap connection -$NOIMAP_ACTIONS = array('addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment'); +// actions that do not require imap connection here +$NOIMAP_ACTIONS = array('addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment', 'get'); // always instantiate imap object (but not yet connect to server) $RCMAIL->imap_init(); @@ -44,7 +46,7 @@ } // set imap properties and session vars -if ($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC)) +if (strlen(trim($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC, true)))) $IMAP->set_mailbox(($_SESSION['mbox'] = $mbox)); else if ($IMAP) $_SESSION['mbox'] = $IMAP->get_mailbox_name(); @@ -70,33 +72,32 @@ $IMAP->set_threading($a_threading[$_SESSION['mbox']]); // set message set for search result -if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) - { - $IMAP->set_search_set($_SESSION['search'][$_REQUEST['_search']]); +if (!empty($_REQUEST['_search']) && isset($_SESSION['search']) + && $_SESSION['search_request'] == $_REQUEST['_search'] +) { + $IMAP->set_search_set($_SESSION['search']); $OUTPUT->set_env('search_request', $_REQUEST['_search']); $OUTPUT->set_env('search_text', $_SESSION['last_text_search']); - } +} // set main env variables, labels and page title -if (empty($RCMAIL->action) || $RCMAIL->action == 'list') - { +if (empty($RCMAIL->action) || $RCMAIL->action == 'list') { $mbox_name = $IMAP->get_mailbox_name(); - if (empty($RCMAIL->action)) - { + if (empty($RCMAIL->action)) { // initialize searching result if search_filter is used - if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') - { + if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') { $search_request = md5($mbox_name.$_SESSION['search_filter']); $IMAP->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET, $_SESSION['sort_col']); - $_SESSION['search'][$search_request] = $IMAP->get_search_set(); + $_SESSION['search'] = $IMAP->get_search_set(); + $_SESSION['search_request'] = $search_request; $OUTPUT->set_env('search_request', $search_request); } $search_mods = $RCMAIL->config->get('search_mods', $SEARCH_MODS_DEFAULT); $OUTPUT->set_env('search_mods', $search_mods); - } + } // set current mailbox and some other vars in client environment $OUTPUT->set_env('mailbox', $mbox_name); @@ -130,10 +131,11 @@ if (!$OUTPUT->ajax_call) $OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash', - 'movingmessage', 'copyingmessage', 'copy', 'move', 'quota'); + 'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage', + 'copy', 'move', 'quota'); $OUTPUT->set_pagetitle(rcmail_localize_foldername($mbox_name)); - } +} /** @@ -171,7 +173,9 @@ && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) $a_show_cols[$f] = 'to'; - // make sure 'threads' column is present + // make sure 'threads' and 'subject' columns are present + if (!in_array('subject', $a_show_cols)) + array_unshift($a_show_cols, 'subject'); if (!in_array('threads', $a_show_cols)) array_unshift($a_show_cols, 'threads'); @@ -182,28 +186,6 @@ $OUTPUT->set_env('autoexpand_threads', intval($CONFIG['autoexpand_threads'])); $OUTPUT->set_env('sort_col', $_SESSION['sort_col']); $OUTPUT->set_env('sort_order', $_SESSION['sort_order']); - - if ($attrib['messageicon']) - $OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']); - if ($attrib['deletedicon']) - $OUTPUT->set_env('deletedicon', $skin_path . $attrib['deletedicon']); - if ($attrib['unreadicon']) - $OUTPUT->set_env('unreadicon', $skin_path . $attrib['unreadicon']); - if ($attrib['repliedicon']) - $OUTPUT->set_env('repliedicon', $skin_path . $attrib['repliedicon']); - if ($attrib['forwardedicon']) - $OUTPUT->set_env('forwardedicon', $skin_path . $attrib['forwardedicon']); - if ($attrib['forwardedrepliedicon']) - $OUTPUT->set_env('forwardedrepliedicon', $skin_path . $attrib['forwardedrepliedicon']); - if ($attrib['attachmenticon']) - $OUTPUT->set_env('attachmenticon', $skin_path . $attrib['attachmenticon']); - if ($attrib['flaggedicon']) - $OUTPUT->set_env('flaggedicon', $skin_path . $attrib['flaggedicon']); - if ($attrib['unflaggedicon']) - $OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']); - if ($attrib['unreadchildrenicon']) - $OUTPUT->set_env('unreadchildrenicon', $skin_path . $attrib['unreadchildrenicon']); - $OUTPUT->set_env('messages', array()); $OUTPUT->set_env('coltypes', $a_show_cols); @@ -223,28 +205,41 @@ /** * return javascript commands to add rows to the message list - * or to replace the whole list (IE only) */ -function rcmail_js_message_list($a_headers, $insert_top=FALSE, $head_replace=FALSE) +function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null) { global $CONFIG, $IMAP, $RCMAIL, $OUTPUT; - if (!empty($_SESSION['list_attrib']['columns'])) - $a_show_cols = $_SESSION['list_attrib']['columns']; - else - $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); + if (empty($a_show_cols)) { + if (!empty($_SESSION['list_attrib']['columns'])) + $a_show_cols = $_SESSION['list_attrib']['columns']; + else + $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); + } + else { + if (!is_array($a_show_cols)) + $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($a_show_cols)); + $head_replace = true; + } $mbox = $IMAP->get_mailbox_name(); $delim = $IMAP->get_hierarchy_delimiter(); + + // make sure 'threads' and 'subject' columns are present + if (!in_array('subject', $a_show_cols)) + array_unshift($a_show_cols, 'subject'); + if (!in_array('threads', $a_show_cols)) + array_unshift($a_show_cols, 'threads'); + + $_SESSION['list_attrib']['columns'] = $a_show_cols; // show 'to' instead of 'from' in sent/draft messages if ((strpos($mbox.$delim, $CONFIG['sent_mbox'].$delim)===0 || strpos($mbox.$delim, $CONFIG['drafts_mbox'].$delim)===0) && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) $a_show_cols[$f] = 'to'; - // make sure 'threads' column is present - if (!in_array('threads', $a_show_cols)) - array_unshift($a_show_cols, 'threads'); + // Make sure there are no duplicated columns (#1486999) + $a_show_cols = array_unique($a_show_cols); // Plugins may set header's list_cols/list_flags and other rcube_mail_header variables // and list columns @@ -261,17 +256,14 @@ if (empty($a_headers)) return; - // remove 'threads', 'attachment', 'flag' columns, we don't need them here - if (($key = array_search('attachment', $a_show_cols)) !== FALSE) - unset($a_show_cols[$key]); - if (($key = array_search('flag', $a_show_cols)) !== FALSE) - unset($a_show_cols[$key]); - if (($key = array_search('threads', $a_show_cols)) !== FALSE) - unset($a_show_cols[$key]); - // loop through message headers + // remove 'threads', 'attachment', 'flag', 'status' columns, we don't need them here + foreach (array('threads', 'attachment', 'flag', 'status') as $col) { + if (($key = array_search($col, $a_show_cols)) !== FALSE) + unset($a_show_cols[$key]); + } - foreach ($a_headers as $n => $header) - { + // loop through message headers + foreach ($a_headers as $n => $header) { if (empty($header)) continue; @@ -281,16 +273,14 @@ $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']); // format each col; similar as in rcmail_message_list() - foreach ($a_show_cols as $col) - { + foreach ($a_show_cols as $col) { if (in_array($col, array('from', 'to', 'cc', 'replyto'))) $cont = Q(rcmail_address_string($header->$col, 3), 'show'); - else if ($col=='subject') - { + else if ($col=='subject') { $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160); if (!$cont) $cont = rcube_label('nosubject'); $cont = Q($cont); - } + } else if ($col=='size') $cont = show_bytes($header->$col); else if ($col=='date') @@ -299,7 +289,7 @@ $cont = Q($header->$col); $a_msg_cols[$col] = $cont; - } + } if ($header->depth) $a_msg_flags['depth'] = $header->depth; @@ -321,8 +311,10 @@ $a_msg_flags['forwarded'] = 1; if ($header->flagged) $a_msg_flags['flagged'] = 1; - if (preg_match("/(application\/|multipart\/m)/i", $header->ctype)) - $a_msg_flags['attachment'] = 1; + if ($header->others['list-post']) + $a_msg_flags['ml'] = 1; + + $a_msg_flags['ctype'] = Q($header->ctype); $a_msg_flags['mbox'] = $mbox; // merge with plugin result @@ -336,11 +328,11 @@ $a_msg_cols, $a_msg_flags, $insert_top); - } + } - if ($IMAP->threading) { - $OUTPUT->command('init_threads', (array) $roots); - } + if ($IMAP->threading) { + $OUTPUT->command('init_threads', (array) $roots); + } } @@ -361,11 +353,17 @@ // define sortable columns $a_sort_cols = array('subject', 'date', 'from', 'to', 'size', 'cc'); - if (!empty($attrib['optionsmenuicon'])) - $list_menu = html::a( - array('href' => '#', 'onclick' => 'return '.JS_OBJECT_NAME.".command('menu-open', 'messagelistmenu')"), - html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'id' => 'listmenulink', 'title' => rcube_label('listoptions'))) - ); + if (!empty($attrib['optionsmenuicon'])) { + $onclick = 'return ' . JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')"; + if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true') + $list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu', + 'id' => 'listmenulink', 'title' => rcube_label('listoptions'))); + else + $list_menu = html::a(array('href' => '#', 'onclick' => $onclick), + html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], + 'id' => 'listmenulink', 'title' => rcube_label('listoptions'))) + ); + } else $list_menu = ''; @@ -375,10 +373,11 @@ // get column name switch ($col) { case 'flag': - $col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], ''); + $col_name = '<span class="flagged"> </span>'; break; case 'attachment': - $col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], ''); + case 'status': + $col_name = '<span class="' . $col .'"> </span>'; break; case 'threads': $col_name = $list_menu; @@ -392,7 +391,7 @@ $col_name = html::a(array('href'=>"./#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name); $sort_class = $col == $sort_col ? " sorted$sort_order" : ''; - $class_name = $col == 'attachment' ? 'icon' : $col.$sort_class; + $class_name = $col.$sort_class; // put it all together $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name); @@ -424,14 +423,16 @@ function rcmail_messagecount_display($attrib) { - global $IMAP, $OUTPUT; + global $RCMAIL; if (!$attrib['id']) $attrib['id'] = 'rcmcountdisplay'; - $OUTPUT->add_gui_object('countdisplay', $attrib['id']); + $RCMAIL->output->add_gui_object('countdisplay', $attrib['id']); - return html::span($attrib, rcmail_get_messagecount_text()); + $content = $RCMAIL->action != 'show' ? rcmail_get_messagecount_text() : rcube_label('loading'); + + return html::span($attrib, $content); } @@ -496,14 +497,7 @@ function rcmail_get_messagecount_text($count=NULL, $page=NULL) { - global $RCMAIL, $IMAP, $MESSAGE; - - if (isset($MESSAGE->index)) - { - return rcube_label(array('name' => 'messagenrof', - 'vars' => array('nr' => $MESSAGE->index+1, - 'count' => $count!==NULL ? $count : $IMAP->messagecount(NULL, 'ALL')))); // Only messages, no threads here - } + global $RCMAIL, $IMAP; if ($page===NULL) $page = $IMAP->list_page; @@ -551,7 +545,7 @@ { global $RCMAIL; - $old_unseen = $_SESSION['unseen_count'][$mbox_name]; + $old_unseen = rcmail_get_unseen_count($mbox_name); if ($count === null) $unseen = $RCMAIL->imap->messagecount($mbox_name, 'UNSEEN', $force); @@ -561,10 +555,30 @@ if ($unseen != $old_unseen || ($mbox_name == 'INBOX')) $RCMAIL->output->command('set_unread_count', $mbox_name, $unseen, ($mbox_name == 'INBOX')); - // @TODO: this data is doubled (session and cache tables) if caching is enabled - $_SESSION['unseen_count'][$mbox_name] = $unseen; + rcmail_set_unseen_count($mbox_name, $unseen); return $unseen; +} + + +function rcmail_set_unseen_count($mbox_name, $count) +{ + // @TODO: this data is doubled (session and cache tables) if caching is enabled + + // Make sure we have an array here (#1487066) + if (!is_array($_SESSION['unseen_count'])) + $_SESSION['unseen_count'] = array(); + + $_SESSION['unseen_count'][$mbox_name] = $count; +} + + +function rcmail_get_unseen_count($mbox_name) +{ + if (is_array($_SESSION['unseen_count']) && array_key_exists($mbox_name, $_SESSION['unseen_count'])) + return $_SESSION['unseen_count'][$mbox_name]; + else + return null; } @@ -646,7 +660,7 @@ // charset was converted to UTF-8 in rcube_imap::get_message_part(), // -> change charset specification in HTML accordingly - $charset_pattern = '(<meta\s+[^>]* content=)[\'"]?(\w+\/\w+;\s*charset=)([a-z0-9-_]+[\'"]?)'; + $charset_pattern = '(<meta\s+[^>]*content=)[\'"]?(\w+\/\w+;\s*charset=)([a-z0-9-_]+[\'"]?)'; if (preg_match("/$charset_pattern/Ui", $html)) { $html = preg_replace("/$charset_pattern/i", '\\1"\\2'.RCMAIL_CHARSET.'"', $html); } @@ -656,7 +670,6 @@ $html = '<head></head>'. $html; $html = substr_replace($html, '<meta http-equiv="Content-Type" content="text/html; charset='.RCMAIL_CHARSET.'" />', intval(stripos($html, '<head>')+6), 0); } - // turn relative into absolute urls $html = rcmail_resolve_base($html); @@ -678,11 +691,21 @@ $wash_opts['html_attribs'] = array('rel','type'); } + // overwrite washer options with options from plugins + if (isset($p['html_elements'])) + $wash_opts['html_elements'] = $p['html_elements']; + if (isset($p['html_attribs'])) + $wash_opts['html_attribs'] = $p['html_attribs']; + + // initialize HTML washer $washer = new washtml($wash_opts); - $washer->add_callback('form', 'rcmail_washtml_callback'); + + if (!$p['skip_washer_form_callback']) + $washer->add_callback('form', 'rcmail_washtml_callback'); // allow CSS styles, will be sanitized by rcmail_washtml_callback() - $washer->add_callback('style', 'rcmail_washtml_callback'); + if (!$p['skip_washer_style_callback']) + $washer->add_callback('style', 'rcmail_washtml_callback'); $html = $washer->wash($html); $REMOTE_OBJECTS = $washer->extlinks; @@ -735,7 +758,7 @@ // plaintext postprocessing if ($part->ctype_secondary == 'plain') - $body = rcmail_plain_body($body); + $body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed'); // allow post-processing of the message body $data = $RCMAIL->plugins->exec_hook('message_part_after', array('type' => $part->ctype_secondary, 'body' => $body) + $data); @@ -747,75 +770,111 @@ /** * Handle links and citation marks in plain text message * - * @param string Plain text string + * @param string Plain text string + * @param boolean Text uses format=flowed + * * @return string Formatted HTML string */ -function rcmail_plain_body($body) +function rcmail_plain_body($body, $flowed=false) { + global $RCMAIL; + // make links and email-addresses clickable - $replacements = new rcube_string_replacer; + $replacer = new rcube_string_replacer; // search for patterns like links and e-mail addresses - $body = preg_replace_callback($replacements->link_pattern, array($replacements, 'link_callback'), $body); - $body = preg_replace_callback($replacements->mailto_pattern, array($replacements, 'mailto_callback'), $body); + $body = preg_replace_callback($replacer->link_pattern, array($replacer, 'link_callback'), $body); + $body = preg_replace_callback($replacer->mailto_pattern, array($replacer, 'mailto_callback'), $body); // split body into single lines $a_lines = preg_split('/\r?\n/', $body); - $q_lines = array(); $quote_level = 0; + $last = -1; // find/mark quoted lines... for ($n=0, $cnt=count($a_lines); $n < $cnt; $n++) { - $q = 0; - if ($a_lines[$n][0] == '>' && preg_match('/^(>+\s*)+/', $a_lines[$n], $regs)) { $q = strlen(preg_replace('/\s/', '', $regs[0])); - $a_lines[$n] = substr($a_lines[$n], strlen($regs[0])); + $a_lines[$n] = substr($a_lines[$n], strlen($regs[0])); if ($q > $quote_level) - $q_lines[$n]['quote'] = $q - $quote_level; + $a_lines[$n] = $replacer->get_replacement($replacer->add( + str_repeat('<blockquote>', $q - $quote_level))) . $a_lines[$n]; else if ($q < $quote_level) - $q_lines[$n]['endquote'] = $quote_level - $q; + $a_lines[$n] = $replacer->get_replacement($replacer->add( + str_repeat('</blockquote>', $quote_level - $q))) . $a_lines[$n]; + else if ($flowed) { + // previous line is flowed + if (isset($a_lines[$last]) && $a_lines[$n] + && $a_lines[$last][strlen($a_lines[$last])-1] == ' ') { + // merge lines + $a_lines[$last] .= $a_lines[$n]; + unset($a_lines[$n]); + } + else + $last = $n; + } } - else if ($quote_level > 0) - $q_lines[$n]['endquote'] = $quote_level; + else { + $q = 0; + if ($flowed) { + // sig separator - line is fixed + if ($a_lines[$n] == '-- ') { + $last = $n; + } + else { + // remove space-stuffing + if ($a_lines[$n][0] == ' ') + $a_lines[$n] = substr($a_lines[$n], 1); + + // previous line is flowed? + if (isset($a_lines[$last]) && $a_lines[$n] + && $a_lines[$last] != '-- ' + && $a_lines[$last][strlen($a_lines[$last])-1] == ' ' + ) { + $a_lines[$last] .= $a_lines[$n]; + unset($a_lines[$n]); + } + else { + $last = $n; + } + } + if ($quote_level > 0) + $a_lines[$last] = $replacer->get_replacement($replacer->add( + str_repeat('</blockquote>', $quote_level))) . $a_lines[$last]; + } + else if ($quote_level > 0) + $a_lines[$n] = $replacer->get_replacement($replacer->add( + str_repeat('</blockquote>', $quote_level))) . $a_lines[$n]; + } $quote_level = $q; } - // quote plain text - $body = Q(join("\n", $a_lines), 'replace', false); + $body = join("\n", $a_lines); - // colorize signature - if (($sp = strrpos($body, '-- ')) !== false) - if (($sp == 0 || $body[$sp-1] == "\n") && $body[$sp+3] == "\n") { - $body = substr($body, 0, max(0, $sp)) - .'<span class="sig">'.substr($body, $sp).'</span>'; + // quote plain text (don't use Q() here, to display entities "as is") + $table = get_html_translation_table(HTML_SPECIALCHARS); + unset($table['?']); + $body = strtr($body, $table); + + // colorize signature (up to <sig_max_lines> lines) + $len = strlen($body); + $sig_max_lines = $RCMAIL->config->get('sig_max_lines', 15); + while (($sp = strrpos($body, "-- \n", $sp ? -$len+$sp-1 : 0)) !== false) { + if ($sp == 0 || $body[$sp-1] == "\n") { + // do not touch blocks with more that X lines + if (substr_count($body, "\n", $sp) < $sig_max_lines) + $body = substr($body, 0, max(0, $sp)) + .'<span class="sig">'.substr($body, $sp).'</span>'; + break; } + } - // colorize quoted lines - $a_lines = preg_split('/\n/', $body); - foreach ($q_lines as $i => $q) - if ($q['quote']) - $a_lines[$i] = str_repeat('<blockquote>', $q['quote']) . $a_lines[$i]; - else if ($q['endquote']) - $a_lines[$i] = str_repeat('</blockquote>', $q['endquote']) . $a_lines[$i]; + // insert url/mailto links and citation tags + $body = $replacer->resolve($body); - // insert the links for urls and mailtos - $body = $replacements->resolve(join("\n", $a_lines)); - return $body; -} - - -/** - * add a string to the replacement array and return a replacement string - */ -function rcmail_str_replacement($str, &$rep) -{ - static $count = 0; - $rep[$count] = stripslashes($str); - return "##string_replacement{".($count++)."}##"; } @@ -885,33 +944,49 @@ $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers; // show these headers - $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', 'date'); + $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', + 'mail-reply-to', 'mail-followup-to', 'date'); $output_headers = array(); foreach ($standard_headers as $hkey) { - if (!$headers[$hkey]) + if ($headers[$hkey]) + $value = $headers[$hkey]; + else if ($headers['others'][$hkey]) + $value = $headers['others'][$hkey]; + else continue; if ($hkey == 'date') { if ($PRINT_MODE) - $header_value = format_date($headers[$hkey], $RCMAIL->config->get('date_long', 'x')); + $header_value = format_date($value, $RCMAIL->config->get('date_long', 'x')); else - $header_value = format_date($headers[$hkey]); + $header_value = format_date($value); } else if ($hkey == 'replyto') { if ($headers['replyto'] != $headers['from']) - $header_value = rcmail_address_string($headers['replyto'], null, true, $attrib['addicon']); + $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); else continue; } + else if ($hkey == 'mail-reply-to') { + if ($headers['mail-replyto'] != $headers['reply-to'] + && $headers['reply-to'] != $headers['from'] + ) + $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); + else + continue; + } + else if ($hkey == 'mail-followup-to') { + $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); + } else if (in_array($hkey, array('from', 'to', 'cc', 'bcc'))) - $header_value = rcmail_address_string($headers[$hkey], null, true, $attrib['addicon']); - else if ($hkey == 'subject' && empty($headers[$hkey])) + $header_value = rcmail_address_string($value, null, true, $attrib['addicon']); + else if ($hkey == 'subject' && empty($value)) $header_value = rcube_label('nosubject'); else - $header_value = trim($IMAP->decode_header($headers[$hkey])); + $header_value = trim($IMAP->decode_header($value)); - $output_headers[$hkey] = array('title' => rcube_label($hkey), 'value' => $header_value, 'raw' => $headers[$hkey]); + $output_headers[$hkey] = array('title' => rcube_label($hkey), 'value' => $header_value, 'raw' => $value); } $plugin = $RCMAIL->plugins->exec_hook('message_headers_output', array('output' => $output_headers, 'headers' => $MESSAGE->headers)); @@ -921,7 +996,7 @@ foreach ($plugin['output'] as $hkey => $row) { $table->add(array('class' => 'header-title'), Q($row['title'])); - $table->add(array('class' => $hkey, 'width' => "90%"), Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show'))); + $table->add(array('class' => 'header '.$hkey), Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show'))); } // all headers division @@ -944,7 +1019,7 @@ */ function rcmail_message_body($attrib) { - global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $REMOTE_OBJECTS; + global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $RCMAIL, $REMOTE_OBJECTS; if (!is_array($MESSAGE->parts) && empty($MESSAGE->body)) return ''; @@ -980,28 +1055,37 @@ rcmail_message_error($MESSAGE->uid); } - // re-format format=flowed content - if ($part->ctype_secondary == "plain" && $part->ctype_parameters['format'] == "flowed") - $part->body = rcube_message::unfold_flowed($part->body); + $plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array( + 'part' => $part, 'prefix' => '')); $body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html'])); if ($part->ctype_secondary == 'html') { - $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $color); + $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs); $div_attr = array('class' => 'message-htmlpart'); - if ($color) - $div_attr['style'] = 'background-color: '.$color; + $style = array(); - $out .= html::div($div_attr, $body); + if (!empty($attrs)) { + foreach ($attrs as $a_idx => $a_val) + $style[] = $a_idx . ': ' . $a_val; + if (!empty($style)) + $div_attr['style'] = implode('; ', $style); + } + + $out .= html::div($div_attr, $plugin['prefix'] . $body); } else - $out .= html::div('message-part', $body); + $out .= html::div('message-part', $plugin['prefix'] . $body); } } } - else - $out .= html::div('message-part', html::tag('pre', array(), + else { + $plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array( + 'part' => $MESSAGE, 'prefix' => '')); + + $out .= html::div('message-part', $plugin['prefix'] . html::tag('pre', array(), rcmail_plain_body(Q($MESSAGE->body, 'strict', false)))); + } $ctype_primary = strtolower($MESSAGE->structure->ctype_primary); $ctype_secondary = strtolower($MESSAGE->structure->ctype_secondary); @@ -1009,11 +1093,16 @@ // list images after mail body if ($CONFIG['inline_images'] && $ctype_primary == 'multipart' - && !empty($MESSAGE->attachments) - && !strstr($message_body, '<html')) + && !empty($MESSAGE->attachments)) { foreach ($MESSAGE->attachments as $attach_prop) { - if (strpos($attach_prop->mimetype, 'image/') === 0) { + // Content-Type: image/*... + if (preg_match('/^image\//i', $attach_prop->mimetype) || + // ...or known file extension: many clients are using application/octet-stream + ($attach_prop->filename && + preg_match('/^application\/octet-stream$/i', $attach_prop->mimetype) && + preg_match('/\.(jpg|jpeg|png|gif|bmp)$/i', $attach_prop->filename)) + ) { $out .= html::tag('hr') . html::p(array('align' => "center"), html::img(array( 'src' => $MESSAGE->get_part_url($attach_prop->mime_id), @@ -1052,31 +1141,32 @@ /** * modify a HTML message that it can be displayed inside a HTML page */ -function rcmail_html4inline($body, $container_id, $body_id='', &$body_color='') - { +function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null) +{ $last_style_pos = 0; $body_lc = strtolower($body); + $cont_id = $container_id.($body_id ? ' div.'.$body_id : ''); // find STYLE tags while (($pos = strpos($body_lc, '<style', $last_style_pos)) && ($pos2 = strpos($body_lc, '</style>', $pos))) - { + { $pos = strpos($body_lc, '>', $pos)+1; // replace all css definitions with #container [def] - $styles = rcmail_mod_css_styles(substr($body, $pos, $pos2-$pos), - $container_id.($body_id ? ' div.'.$body_id : '')); + $styles = rcmail_mod_css_styles( + substr($body, $pos, $pos2-$pos), $cont_id); $body = substr($body, 0, $pos) . $styles . substr($body, $pos2); $body_lc = strtolower($body); $last_style_pos = $pos2; - } + } // modify HTML links to open a new window if clicked $GLOBALS['rcmail_html_container_id'] = $container_id; $body = preg_replace_callback('/<(a|link)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body); unset($GLOBALS['rcmail_html_container_id']); - $out = preg_replace(array( + $body = preg_replace(array( // add comments arround html and other tags '/(<!DOCTYPE[^>]*>)/i', '/(<\?xml[^>]*>)/i', @@ -1105,20 +1195,43 @@ ), $body); - // Get rcmBody's bgcolor attribute, we'll set the color to the body container - // as background-color, bgcolor doesn't work with a div - if (preg_match('/<div class="' . preg_quote($body_id, '/') . '" ([^>]+)>/', $out, $m)) { - if (preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $m[0], $mb)) { - $body_color = $mb[1]; + $attributes = array(); + + // Handle body attributes that doesn't play nicely with div elements + if (preg_match('/<div class="' . preg_quote($body_id, '/') . '" ([^>]+)/', $body, $m)) { + $attrs = $m[0]; + // Get bgcolor, we'll set it as background-color of the message container + if (preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $attrs, $mb)) { + $attributes['background-color'] = $mb[1]; + $attrs = preg_replace('/bgcolor=["\']*([a-z0-9#]+)["\']*/', '', $attrs); + } + // Get background, we'll set it as background-image of the message container + if (preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) { + $attributes['background-image'] = 'url('.$mb[1].')'; + $attrs = preg_replace('/background=["\']*([^"\'>\s]+)["\']*/', '', $attrs); + } + if (!empty($attributes)) + $body = preg_replace('/<div class="' . preg_quote($body_id, '/') . '" [^>]+/', rtrim($attrs), $body, 1); + + // handle body styles related to background image + if ($attributes['background-image']) { + // get body style + if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) { + // get background related style + if (preg_match_all('/(background-position|background-repeat)\s*:\s*([^;]+);/i', $m[1], $ma, PREG_SET_ORDER)) { + foreach ($ma as $style) + $attributes[$style[1]] = $style[2]; + } + } } } // make sure there's 'rcmBody' div, we need it for proper css modification // its name is hardcoded in rcmail_message_body() also else - $out = '<div class="' . $body_id . '">' . $out . '</div>'; + $body = '<div class="' . $body_id . '">' . $body . '</div>'; - return $out; - } + return $body; +} /** @@ -1157,7 +1270,7 @@ */ function rcmail_address_string($input, $max=null, $linked=false, $addicon=null) { - global $IMAP, $RCMAIL, $PRINT_MODE, $CONFIG, $OUTPUT, $EMAIL_ADDRESS_PATTERN; + global $IMAP, $RCMAIL, $PRINT_MODE, $CONFIG; static $got_writable_abook = null; $a_parts = $IMAP->decode_address_list($input); @@ -1175,27 +1288,40 @@ foreach ($a_parts as $part) { $j++; + + $name = $part['name']; + $mailto = $part['mailto']; + $string = $part['string']; + + // IDNA ASCII to Unicode + if ($name == $mailto) + $name = idn_to_utf8($name); + if ($string == $mailto) + $string = idn_to_utf8($string); + $mailto = idn_to_utf8($mailto); + if ($PRINT_MODE) { - $out .= sprintf('%s <%s>', Q($part['name']), $part['mailto']); + $out .= sprintf('%s <%s>', Q($name), $mailto); } else if (check_email($part['mailto'], false)) { if ($linked) { $out .= html::a(array( - 'href' => 'mailto:'.$part['mailto'], - 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($part['mailto'])), - 'title' => $part['mailto'], + 'href' => 'mailto:'.$mailto, + 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)), + 'title' => $mailto, 'class' => "rcmContactAddress", ), - Q($part['name'])); + Q($name ? $name : $mailto)); } else { - $out .= html::span(array('title' => $part['mailto'], 'class' => "rcmContactAddress"), Q($part['name'])); + $out .= html::span(array('title' => $mailto, 'class' => "rcmContactAddress"), + Q($name ? $name : $mailto)); } if ($addicon && $got_writable_abook) { $out .= ' ' . html::a(array( 'href' => "#add", - 'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, urlencode($part['string'])), + 'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, urlencode($string)), 'title' => rcube_label('addtoaddressbook'), ), html::img(array( @@ -1205,10 +1331,10 @@ } } else { - if ($part['name']) - $out .= Q($part['name']); - if ($part['mailto']) - $out .= (strlen($out) ? ' ' : '') . sprintf('<%s>', Q($part['mailto'])); + if ($name) + $out .= Q($name); + if ($mailto) + $out .= (strlen($out) ? ' ' : '') . sprintf('<%s>', Q($mailto)); } if ($c>$j) @@ -1291,7 +1417,7 @@ function rcmail_message_part_controls() - { +{ global $MESSAGE; $part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC)); @@ -1313,12 +1439,12 @@ } return $table->show($attrib); - } +} function rcmail_message_part_frame($attrib) - { +{ global $MESSAGE; $part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))]; @@ -1327,21 +1453,21 @@ $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']); return html::iframe($attrib); - } +} /** * clear message composing settings */ function rcmail_compose_cleanup() - { +{ if (!isset($_SESSION['compose'])) return; $rcmail = rcmail::get_instance(); $rcmail->plugins->exec_hook('attachments_cleanup', array()); $rcmail->session->remove('compose'); - } +} /** @@ -1352,10 +1478,11 @@ * @param array $mailto Array of recipient address strings * @param array $smtp_error SMTP error array (reference) * @param string $body_file Location of file with saved message body (reference) + * @param array $smtp_opts SMTP options (e.g. DSN request) * * @return boolean Send status. */ -function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file) +function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file, $smtp_opts=null) { global $CONFIG, $RCMAIL; @@ -1399,7 +1526,7 @@ if (!is_object($RCMAIL->smtp)) $RCMAIL->smtp_init(true); - $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body); + $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts); $smtp_response = $RCMAIL->smtp->get_response(); $smtp_error = $RCMAIL->smtp->get_error(); @@ -1437,6 +1564,7 @@ $delim = $RCMAIL->config->header_delimiter(); $to = $headers_enc['To']; $subject = $headers_enc['Subject']; + $header_str = rtrim($header_str); if ($delim != "\r\n") { $header_str = str_replace("\r\n", $delim, $header_str); @@ -1485,12 +1613,20 @@ return $sent; } - -function rcmail_send_mdn($uid, &$smtp_error) +/** + * Send the MDN response + * + * @param mixed $message Original message object (rcube_message) or UID + * @param array $smtp_error SMTP error array (reference) + * + * @return boolean Send status + */ +function rcmail_send_mdn($message, &$smtp_error) { global $RCMAIL, $IMAP; - $message = new rcube_message($uid); + if (!is_a($message, rcube_message)) + $message = new rcube_message($message); if ($message->headers->mdn_to && !$message->headers->mdn_sent && ($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*'))) @@ -1515,7 +1651,7 @@ 'From' => $sender, 'To' => $message->headers->mdn_to, 'Subject' => rcube_label('receiptread') . ': ' . $message->subject, - 'Message-ID' => sprintf('<%s@%s>', md5(uniqid('rcmail'.mt_rand(),true)), $RCMAIL->config->mail_domain($_SESSION['imap_host'])), + 'Message-ID' => rcmail_gen_message_id(), 'X-Sender' => $identity['email'], 'References' => trim($message->headers->references . ' ' . $message->headers->messageID), ); @@ -1529,7 +1665,7 @@ "\t" . rcube_label("sent") . ': ' . format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" . "\r\n" . rcube_label("receiptnote") . "\r\n"; - $ua = $RCMAIL->config->get('useragent', "RoundCube Webmail (Version ".RCMAIL_VERSION.")"); + $ua = $RCMAIL->config->get('useragent', "Roundcube Webmail (Version ".RCMAIL_VERSION.")"); $report = "Reporting-UA: $ua\r\n"; if ($message->headers->to) @@ -1556,6 +1692,29 @@ return false; } +// Returns unique Message-ID +function rcmail_gen_message_id() +{ + global $RCMAIL; + + $local_part = md5(uniqid('rcmail'.mt_rand(),true)); + $domain_part = $RCMAIL->user->get_username('domain'); + + // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) + if (!preg_match('/\.[a-z]+$/i', $domain_part)) { + if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST'])) + && preg_match('/\.[a-z]+$/i', $host)) { + $domain_part = $host; + } + else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME'])) + && preg_match('/\.[a-z]+$/i', $host)) { + $domain_part = $host; + } + } + + return sprintf('<%s@%s>', $local_part, $domain_part); +} + // Returns RFC2822 formatted current date in user's timezone function rcmail_user_date() { @@ -1573,11 +1732,22 @@ $date = time() + $tz * 60 * 60; $date = gmdate('r', $date); - $date = preg_replace('/[+-][0-9]{4}$/', sprintf('%+05d', $tz * 100), $date); + $tz = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60); + $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date); return $date; } +// Fixes some content-type names +function rcmail_fix_mimetype($name) +{ + // Some versions of Outlook create garbage Content-Type: + // application/pdf.A520491B_3BF7_494D_8855_7FAC2C6C0608 + if (preg_match('/^application\/pdf.+/', $name)) + $name = 'application/pdf'; + + return $name; +} function rcmail_search_filter($attrib) { -- Gitblit v1.9.1