From 7439d3ee14ea8b9e61f656ab092b8d83c72e0dc9 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 21 Apr 2013 11:08:12 -0400
Subject: [PATCH] Fix incorrect handling of leading spaces in text wrapping

---
 CHANGELOG                            |    1 
 tests/Framework/Mime.php             |   46 +++++++++++
 program/lib/Roundcube/rcube_mime.php |  146 +++++++++++++++++++++++-------------
 3 files changed, 140 insertions(+), 53 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index dcfa0d7..7743032 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix incorrect handling of leading spaces in text wrapping
 - Fix unintentional messages list jumps on click in Internet Explorer (#1489056)
 - Fix list of required configuration options (#1489055)
 - Fix DB error when creating a new contact and a group is selected (#1489051)
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 7cd5207..1e4fac8 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -564,82 +564,122 @@
 
 
     /**
-     * Improved wordwrap function.
+     * Improved wordwrap function with multibyte support.
+     * The code is based on Zend_Text_MultiByte::wordWrap().
      *
-     * @param string $string  Text to wrap
-     * @param int    $width   Line width
-     * @param string $break   Line separator
-     * @param bool   $cut     Enable to cut word
-     * @param string $charset Charset of $string
+     * @param string $string      Text to wrap
+     * @param int    $width       Line width
+     * @param string $break       Line separator
+     * @param bool   $cut         Enable to cut word
+     * @param string $charset     Charset of $string
+     * @param bool   $wrap_quoted When enabled quoted lines will not be wrapped
      *
      * @return string Text
      */
-    public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null)
+    public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true)
     {
-        if ($charset && function_exists('mb_internal_encoding')) {
-            mb_internal_encoding($charset);
+        if (!$charset) {
+            $charset = RCUBE_CHARSET;
         }
 
-        $para   = preg_split('/\r?\n/', $string);
-        $string = '';
+        // detect available functions
+        $strlen_func  = function_exists('iconv_strlen') ? 'iconv_strlen' : 'mb_strlen';
+        $strpos_func  = function_exists('iconv_strpos') ? 'iconv_strpos' : 'mb_strpos';
+        $strrpos_func = function_exists('iconv_strrpos') ? 'iconv_strrpos' : 'mb_strrpos';
+        $substr_func  = function_exists('iconv_substr') ? 'iconv_substr' : 'mb_substr';
 
-        while (count($para)) {
-            $line = array_shift($para);
-            if ($line[0] == '>') {
-                $string .= $line . (count($para) ? $break : '');
-                continue;
-            }
+        // Convert \r\n to \n, this is our line-separator
+        $string       = str_replace("\r\n", "\n", $string);
+        $separator    = "\n"; // must be 1 character length
+        $result       = array();
 
-            $list = explode(' ', $line);
-            $len = 0;
-            while (count($list)) {
-                $line   = array_shift($list);
-                $l      = mb_strlen($line);
-                $space  = $len ? 1 : 0;
-                $newlen = $len + $l + $space;
+        while (($stringLength = $strlen_func($string, $charset)) > 0) {
+            $breakPos = $strpos_func($string, $separator, 0, $charset);
 
-                if ($newlen <= $width) {
-                    $string .= ($space ? ' ' : '').$line;
-                    $len += ($space + $l);
+            // quoted line (do not wrap)
+            if ($wrap_quoted && $string[0] == '>') {
+                if ($breakPos === $stringLength - 1 || $breakPos === false) {
+                    $subString = $string;
+                    $cutLength = null;
                 }
                 else {
-                    if ($l > $width) {
-                        if ($cut) {
-                            $start = 0;
-                            while ($l) {
-                                $str = mb_substr($line, $start, $width);
-                                $strlen = mb_strlen($str);
-                                $string .= ($len ? $break : '').$str;
-                                $start += $strlen;
-                                $l -= $strlen;
-                                $len = $strlen;
+                    $subString = $substr_func($string, 0, $breakPos, $charset);
+                    $cutLength = $breakPos + 1;
+                }
+            }
+            // next line found and current line is shorter than the limit
+            else if ($breakPos !== false && $breakPos < $width) {
+                if ($breakPos === $stringLength - 1) {
+                    $subString = $string;
+                    $cutLength = null;
+                }
+                else {
+                    $subString = $substr_func($string, 0, $breakPos, $charset);
+                    $cutLength = $breakPos + 1;
+                }
+            }
+            else {
+                $subString = $substr_func($string, 0, $width, $charset);
+
+                // last line
+                if ($subString === $string) {
+                    $cutLength = null;
+                }
+                else {
+                    $nextChar = $substr_func($string, $width, 1, $charset);
+
+                    if ($nextChar === ' ' || $nextChar === $separator) {
+                        $afterNextChar = $substr_func($string, $width + 1, 1, $charset);
+
+                        if ($afterNextChar === false) {
+                            $subString .= $nextChar;
+                        }
+
+                        $cutLength = $strlen_func($subString, $charset) + 1;
+                    }
+                    else {
+                        if ($strrpos_func[0] == 'm') {
+                            $spacePos = $strrpos_func($subString, ' ', 0, $charset);
+                        }
+                        else {
+                            $spacePos = $strrpos_func($subString, ' ', $charset);
+                        }
+
+                        if ($spacePos !== false) {
+                            $subString = $substr_func($subString, 0, $spacePos, $charset);
+                            $cutLength = $spacePos + 1;
+                        }
+                        else if ($cut === false) {
+                            $spacePos = $strpos_func($string, ' ', 0, $charset);
+
+                            if ($spacePos !== false) {
+                                $subString = $substr_func($string, 0, $spacePos, $charset);
+                                $cutLength = $spacePos + 1;
+                            }
+                            else {
+                                $subString = $string;
+                                $cutLength = null;
                             }
                         }
                         else {
-                            $string .= ($len ? $break : '').$line;
-                            if (count($list)) {
-                                $string .= $break;
-                            }
-                            $len = 0;
+                            $subString = $substr_func($subString, 0, $width, $charset);
+                            $cutLength = $width;
                         }
-                    }
-                    else {
-                        $string .= $break.$line;
-                        $len = $l;
                     }
                 }
             }
 
-            if (count($para)) {
-                $string .= $break;
+            $result[] = $subString;
+
+            if ($cutLength !== null) {
+                $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset);
+            }
+            else {
+                break;
             }
         }
 
-        if ($charset && function_exists('mb_internal_encoding')) {
-            mb_internal_encoding(RCUBE_CHARSET);
-        }
-
-        return $string;
+        return implode($break, $result);
     }
 
 
diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php
index 1f9a8c5..f15379e 100644
--- a/tests/Framework/Mime.php
+++ b/tests/Framework/Mime.php
@@ -142,4 +142,50 @@
 
         $this->assertEquals($unfolded, rcube_mime::unfold_flowed($flowed), "Test correct unfolding of quoted lines");
     }
+
+    /**
+     * Test wordwrap()
+     */
+    function test_wordwrap()
+    {
+        $samples = array(
+            array(
+                array("aaaa aaaa\n           aaaa"),
+                "aaaa aaaa\n           aaaa",
+            ),
+            array(
+                array("123456789 123456789 123456789 123", 29),
+                "123456789 123456789 123456789\n123",
+            ),
+            array(
+                array("123456789   3456789 123456789", 29),
+                "123456789   3456789 123456789",
+            ),
+            array(
+                array("123456789 123456789 123456789   123", 29),
+                "123456789 123456789 123456789\n  123",
+            ),
+            array(
+                array("abc", 1, "\n", true),
+                "a\nb\nc",
+            ),
+            array(
+                array("ąść", 1, "\n", true, 'UTF-8'),
+                "ą\nś\nć",
+            ),
+            array(
+                array(">abc\n>def", 2, "\n", true),
+                ">abc\n>def",
+            ),
+            array(
+                array("abc def", 3, "-"),
+                "abc-def",
+            ),
+        );
+
+        foreach ($samples as $sample) {
+            $this->assertEquals($sample[1], call_user_func_array(array('rcube_mime', 'wordwrap'), $sample[0]), "Test text wrapping");
+        }
+    }
+
 }

--
Gitblit v1.9.1