From 99b8c1e2ac619665c0514b15d8efb5defabb8090 Mon Sep 17 00:00:00 2001 From: alecpl Date: Tue, 14 Sep 2010 08:40:51 +0000 Subject: - Fix format=flowed handling (#1486989) + small improvements in plain messages parsing --- CHANGELOG | 1 + program/include/rcube_message.php | 86 ++++++++++++++++++++++------- program/steps/mail/func.inc | 113 +++++++++++++++++++++++--------------- program/steps/mail/sendmail.inc | 6 +- 4 files changed, 139 insertions(+), 67 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e0739b6cc..f6a99a2d8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ CHANGELOG RoundCube Webmail - Re-added 'Close' button in upload form (#1486930, #1486823) - Fix handling of charsets with LATIN-* label - Fix messages background image handling in some cases (#1486990) +- Fix format=flowed handling (#1486989) RELEASE 0.4 ----------- diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index ba59bae07..a15117e19 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -214,23 +214,22 @@ class rcube_message if ($mimetype == 'text/plain') { $out = $this->imap->get_message_part($this->uid, $mime_id, $part); - + // re-format format=flowed content if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') $out = self::unfold_flowed($out); break; } else if ($mimetype == 'text/html') { - $html_part = $this->imap->get_message_part($this->uid, $mime_id, $part); + $out = $this->imap->get_message_part($this->uid, $mime_id, $part); // remove special chars encoding $trans = array_flip(get_html_translation_table(HTML_ENTITIES)); - $html_part = strtr($html_part, $trans); + $out = strtr($out, $trans); // create instance of html2text class - $txt = new html2text($html_part); + $txt = new html2text($out); $out = $txt->get_text(); - break; } } @@ -604,10 +603,51 @@ class rcube_message */ public static function unfold_flowed($text) { - return preg_replace( - array('/-- (\r?\n)/', '/^ /m', '/(.) \r?\n/', '/--%SIGEND%(\r?\n)/'), - array('--%SIGEND%\\1', '', '\\1 ', '-- \\1'), - $text); + $text = preg_split('/\r?\n/', $text); + $last = -1; + $q_level = 0; + + foreach ($text as $idx => $line) { + if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) { + $q = strlen(str_replace(' ', '', $regs[0])); + $line = substr($line, strlen($regs[0])); + + if ($q == $q_level && isset($text[$last]) + && $text[$last][strlen($text[$last])-1] == ' ' + ) { + $text[$last] .= $line; + unset($text[$idx]); + } + else { + $last = $idx; + } + } + else { + $q = 0; + if ($line == '-- ') { + $last = $idx; + } + else { + // remove space-stuffing + $line = preg_replace('/^\s/', '', $line); + + if (isset($text[$last]) + && $text[$last] != '-- ' + && $text[$last][strlen($text[$last])-1] == ' ' + ) { + $text[$last] .= $line; + unset($text[$idx]); + } + else { + $text[$idx] = $line; + $last = $idx; + } + } + } + $q_level = $q; + } + + return implode("\r\n", $text); } @@ -616,17 +656,25 @@ class rcube_message */ public static function format_flowed($text, $length = 72) { - $out = ''; - - foreach (preg_split('/\r?\n/', trim($text)) as $line) { - // don't wrap quoted lines (to avoid wrapping problems) - if ($line[0] != '>') - $line = rc_wordwrap(rtrim($line, "\r\n"), $length - 1, " \r\n"); - - $out .= $line . "\r\n"; + $text = preg_split('/\r?\n/', $text); + + foreach ($text as $idx => $line) { + if ($line != '-- ') { + if ($line[0] == '>' && preg_match('/^(>+)/', $line, $regs)) { + $prefix = $regs[0]; + $level = strlen($prefix); + $line = rtrim(substr($line, $level)); + $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix "); + } + else { + $line = ' ' . rc_wordwrap(rtrim($line), $length - 2, " \r\n "); + } + + $text[$idx] = $line; + } } - - return $out; + + return implode("\r\n", $text); } } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 68b790cd9..75d7b9098 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -735,7 +735,7 @@ function rcmail_print_body($part, $p = array()) // 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 +747,102 @@ function rcmail_print_body($part, $p = array()) /** * 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) { // 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('
', $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('
', $quote_level - $q))) . $a_lines[$n]; + else if ($flowed) { + // previous line is flowed + if (isset($a_lines[$last]) + && $a_lines[$last][strlen($a_lines[$last])-1] == ' ') { + // merge lines (and remove space-stuffing) + $a_lines[$last] .= $a_lines[$n]; + unset($a_lines[$n]); + } + else + $last = $n; + } + } + 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[$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('', $quote_level))) . $a_lines[$last]; + } + else if ($quote_level > 0) + $a_lines[$n] = $replacer->get_replacement($replacer->add( + str_repeat('', $quote_level))) . $a_lines[$n]; } - else if ($quote_level > 0) - $q_lines[$n]['endquote'] = $quote_level; $quote_level = $q; } // quote plain text - $body = Q(join("\n", $a_lines), 'replace', false); + $body = Q(join("\n", $a_lines), '', false); // 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)) - .''.substr($body, $sp).''; + if (($sp = strrpos($body, "-- \n")) !== false) { + if (($sp == 0 || $body[$sp-1] == "\n")) { + // do not touch blocks with more that 10 lines + if (substr_count($body, "\n", $sp) < 10) + $body = substr($body, 0, max(0, $sp)) + .''.substr($body, $sp).''; } + } - // colorize quoted lines - $a_lines = preg_split('/\n/', $body); - foreach ($q_lines as $i => $q) - if ($q['quote']) - $a_lines[$i] = str_repeat('
', $q['quote']) . $a_lines[$i]; - else if ($q['endquote']) - $a_lines[$i] = str_repeat('
', $q['endquote']) . $a_lines[$i]; - - // insert the links for urls and mailtos - $body = $replacements->resolve(join("\n", $a_lines)); - - return $body; -} - + // insert url/mailto links and citation tags + $body = $replacer->resolve($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++)."}##"; + return $body; } @@ -983,10 +1010,6 @@ function rcmail_message_body($attrib) $plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array( 'part' => $part, 'prefix' => '')); - // re-format format=flowed content - if ($part->ctype_secondary == "plain" && $part->ctype_parameters['format'] == "flowed") - $part->body = rcube_message::unfold_flowed($part->body); - $body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html'])); if ($part->ctype_secondary == 'html') { diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 23ff571b2..3902b9ad3 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -461,9 +461,9 @@ else { $message_body = $plugin['body']; - // compose format=flowed content if enabled and not a reply message - if (empty($_SESSION['compose']['reply_msgid']) && ($flowed = $RCMAIL->config->get('send_format_flowed', true))) - $message_body = rcube_message::format_flowed($message_body, $LINE_LENGTH); + // compose format=flowed content if enabled + if ($flowed = $RCMAIL->config->get('send_format_flowed', true)) + $message_body = rcube_message::format_flowed($message_body, min($LINE_LENGTH+2, 79)); else $message_body = rc_wordwrap($message_body, $LINE_LENGTH, "\r\n"); -- cgit v1.2.3