lib/kolab_sync_data_email.php lib/kolab_sync_message.php tests/message.php tests/src

Aleksander Machniak machniak at kolabsys.com
Wed Jan 22 14:48:28 CET 2014


 lib/kolab_sync_data_email.php |    2 
 lib/kolab_sync_message.php    |   99 ++++++++++++++++++++++++++++++++++--------
 tests/message.php             |   39 ++++++++++++++++
 tests/src/mail.recode1        |   16 ++++++
 tests/src/mail.recode1.out    |   13 +++++
 tests/src/mail.recode2        |   46 +++++++++++++++++++
 tests/src/mail.recode2.out    |   42 +++++++++++++++++
 tests/src/mail.recode3        |   11 ++++
 tests/src/mail.recode3.out    |   11 ++++
 9 files changed, 261 insertions(+), 18 deletions(-)

New commits:
commit 122c53ee6a51047e7a4d31037535278a15fa5795
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Jan 22 14:45:32 2014 +0100

    Fix issue where mime message source wasn't encoded when sending to the device (Bug #2715, #2757)
    
    This caused removing some characters in messages that use 8bit transfer-encoding
    and character set other than UTF-8.

diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index cea12da..cc3e19a 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -270,6 +270,8 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
         }
         else if ($airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_MIME) {
             $messageBody = $this->storage->get_raw_body($message->uid);
+            // make the source safe (Bug #2715, #2757)
+            $messageBody = kolab_sync_message::recode_message($messageBody);
             // strip out any non utf-8 characters
             $messageBody = rcube_charset::clean($messageBody);
             $real_length = $body_length = strlen($messageBody);
diff --git a/lib/kolab_sync_message.php b/lib/kolab_sync_message.php
index 8c5c44f..355b4f7 100644
--- a/lib/kolab_sync_message.php
+++ b/lib/kolab_sync_message.php
@@ -140,7 +140,7 @@ class kolab_sync_message
      */
     public function set_header($name, $value)
     {
-        $name = $this->normalize_header_name($name);
+        $name = self::normalize_header_name($name);
 
         if ($name != 'Content-Type') {
             $this->headers[$name] = $value;
@@ -277,6 +277,58 @@ class kolab_sync_message
     }
 
     /**
+     * Parses the message source and fixes 8bit data for ActiveSync.
+     * This way any not UTF8 characters will be encoded before
+     * sending to the device.
+     *
+     * @param string $message Message source
+     *
+     * @return string Fixed message source
+     */
+    public static function recode_message($message)
+    {
+        // @TODO: work with stream, to workaround memory issues with big messages
+        if (is_resource($message)) {
+            $message = stream_get_contents($message);
+        }
+
+        list($headers, $message) = preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY);
+
+        $hdrs = self::parse_headers($headers);
+
+        // multipart message
+        if (preg_match('/boundary="?([a-z0-9-_]+)"?/i', $hdrs['Content-Type'], $matches)) {
+            $boundary = '--' . $matches[1];
+            $message  = explode($boundary, $message);
+
+            for ($x=1, $parts = count($message) - 1; $x<$parts; $x++) {
+                $message[$x] = "\r\n" . self::recode_message($message[$x]);
+            }
+
+            return $headers . "\r\n\r\n" . implode($boundary, $message);
+        }
+
+        // single part
+        $enc = strtolower($hdrs['Content-Transfer-Encoding']);
+
+        // do nothing if already encoded
+        if ($enc != 'quoted-printable' && $enc != 'base64') {
+            // recode body if any non-printable-ascii characters found
+            if (preg_match('/[^\x20-\x7E\x0A\x0D\x09]/', $message)) {
+                $hdrs['Content-Transfer-Encoding'] = 'base64';
+                foreach ($hdrs as $header => $header_value) {
+                    $hdrs[$header] = $header . ': ' . $header_value;
+                }
+
+                $headers = trim(implode("\r\n", $hdrs));
+                $message = rtrim(chunk_split(base64_encode(rtrim($message)), 76, "\r\n")) . "\r\n";
+            }
+        }
+
+        return $headers . "\r\n\r\n" . $message;
+    }
+
+    /**
      * MIME message parser
      *
      * @param string|resource $message     MIME message source
@@ -293,6 +345,30 @@ class kolab_sync_message
 
         list($headers, $message) = preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY);
 
+        $headers = self::parse_headers($headers);
+
+        // parse Content-Type header
+        $ctype_parts = preg_split('/[; ]+/', $headers['Content-Type']);
+        $this->ctype = strtolower(array_shift($ctype_parts));
+        foreach ($ctype_parts as $part) {
+            if (preg_match('/^([a-z-_]+)\s*=\s*(.+)$/i', trim($part), $m)) {
+                $this->ctype_params[strtolower($m[1])] = trim($m[2], '"');
+            }
+        }
+
+        if (!empty($headers['Content-Transfer-Encoding'])) {
+            $headers['Content-Transfer-Encoding'] = strtolower($headers['Content-Transfer-Encoding']);
+        }
+
+        $this->headers = $headers;
+        $this->body    = $message;
+    }
+
+    /**
+     * Parse message source with headers
+     */
+    protected static function parse_headers($headers)
+    {
         // Parse headers
         $headers = str_replace("\r\n", "\n", $headers);
         $headers = explode("\n", trim($headers));
@@ -313,31 +389,18 @@ class kolab_sync_message
         $headers = array();
         foreach ($lines as $line) {
             list($field, $string) = explode(':', $line, 2);
-            $field = $this->normalize_header_name($field);
-            $headers[$field] = trim($string);
-        }
-
-        // parse Content-Type header
-        $ctype_parts = preg_split('/[; ]+/', $headers['Content-Type']);
-        $this->ctype = strtolower(array_shift($ctype_parts));
-        foreach ($ctype_parts as $part) {
-            if (preg_match('/^([a-z-_]+)\s*=\s*(.+)$/i', trim($part), $m)) {
-                $this->ctype_params[strtolower($m[1])] = trim($m[2], '"');
+            if ($field = self::normalize_header_name($field)) {
+                $headers[$field] = trim($string);
             }
         }
 
-        if (!empty($headers['Content-Transfer-Encoding'])) {
-            $headers['Content-Transfer-Encoding'] = strtolower($headers['Content-Transfer-Encoding']);
-        }
-
-        $this->headers = $headers;
-        $this->body    = $message;
+        return $headers;
     }
 
     /**
      * Normalize (fix) header names
      */
-    protected function normalize_header_name($name)
+    protected static function normalize_header_name($name)
     {
         $headers_map = array(
             'subject' => 'Subject',
diff --git a/tests/message.php b/tests/message.php
index 1cafa5f..19e1901 100644
--- a/tests/message.php
+++ b/tests/message.php
@@ -96,4 +96,43 @@ class message extends PHPUnit_Framework_TestCase
         $result  = str_replace("\r\n", "\n", $result);
         $this->assertEquals($append, $result);
     }
+
+    /**
+     * Test recoding the message
+     */
+    function test_recode_message_1()
+    {
+        $source = file_get_contents(TESTS_DIR . '/src/mail.recode1');
+        $result = file_get_contents(TESTS_DIR . '/src/mail.recode1.out');
+
+        $message = kolab_sync_message::recode_message($source);
+
+        $this->assertEquals($result, $message);
+    }
+
+    /**
+     * Test recoding the message
+     */
+    function test_recode_message_2()
+    {
+        $source = file_get_contents(TESTS_DIR . '/src/mail.recode2');
+        $result = file_get_contents(TESTS_DIR . '/src/mail.recode2.out');
+
+        $message = kolab_sync_message::recode_message($source);
+
+        $this->assertEquals($result, $message);
+    }
+
+    /**
+     * Test recoding the message
+     */
+    function test_recode_message_3()
+    {
+        $source = file_get_contents(TESTS_DIR . '/src/mail.recode3');
+        $result = file_get_contents(TESTS_DIR . '/src/mail.recode3.out');
+
+        $message = kolab_sync_message::recode_message($source);
+
+        $this->assertEquals($result, $message);
+    }
 }
diff --git a/tests/src/mail.recode1 b/tests/src/mail.recode1
new file mode 100644
index 0000000..72767c5
--- /dev/null
+++ b/tests/src/mail.recode1
@@ -0,0 +1,16 @@
+X-Sieve: CMU Sieve 2.4
+X-Virus-Scanned: amavisd-new at mx.domain.de
+Message-ID: <52CDD362.2020009 at domain.de>
+Date: Wed, 08 Jan 2014 23:38:26 +0100
+From: Daniel <daniel at domain.de>
+MIME-Version: 1.0
+To: <alec at domain.de>
+Subject: Bitte um =?ISO-8859-15?Q?R=FCckruf?=
+Content-Type: text/plain; charset=ISO-8859-15; format=flowed
+Content-Transfer-Encoding: 8bit
+
+Hallo Björn,
+bitte rufe mich doch mal ganz dringend zurück. 0178-5217029
+
+Liebe Grüße
+Daniel
diff --git a/tests/src/mail.recode1.out b/tests/src/mail.recode1.out
new file mode 100644
index 0000000..09cefc0
--- /dev/null
+++ b/tests/src/mail.recode1.out
@@ -0,0 +1,13 @@
+X-Sieve: CMU Sieve 2.4
+X-Virus-Scanned: amavisd-new at mx.domain.de
+Message-ID: <52CDD362.2020009 at domain.de>
+Date: Wed, 08 Jan 2014 23:38:26 +0100
+From: Daniel <daniel at domain.de>
+MIME-Version: 1.0
+To: <alec at domain.de>
+Subject: Bitte um =?ISO-8859-15?Q?R=FCckruf?=
+Content-Type: text/plain; charset=ISO-8859-15; format=flowed
+Content-Transfer-Encoding: base64
+
+SGFsbG8gQmr2cm4sDQpiaXR0ZSBydWZlIG1pY2ggZG9jaCBtYWwgZ2FueiBkcmluZ2VuZCB6dXL8
+Y2suIDAxNzgtNTIxNzAyOQ0KDQpMaWViZSBHcvzfZQ0KRGFuaWVs
diff --git a/tests/src/mail.recode2 b/tests/src/mail.recode2
new file mode 100644
index 0000000..2ac9a2d
--- /dev/null
+++ b/tests/src/mail.recode2
@@ -0,0 +1,46 @@
+Message-ID: <52B02B8F.9010500 at domain.de>
+Date: Tue, 17 Dec 2013 11:46:39 +0100
+From: Sender <sender at domain.de>
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.13) Gecko/20101207 Thunderbird/3.1.7
+To: Verborgene_Empfaenger:;
+Subject: Weihnachtsbrief
+Content-Type: multipart/mixed;
+ boundary="------------000801030509090203070207"
+
+This is a multi-part message in MIME format.
+--------------000801030509090203070207
+Content-Type: multipart/alternative;
+ boundary="------------060305070900000101080707"
+
+
+--------------060305070900000101080707
+Content-Type: text/plain; charset=ISO-8859-15; format=flowed
+Content-Transfer-Encoding: 8bit
+
+Herzliche Adventsgrüße
+
+--------------060305070900000101080707
+Content-Type: text/html; charset=ISO-8859-15
+Content-Transfer-Encoding: 8bit
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=ISO-8859-15">
+  </head>
+  <body bgcolor="#ffffff" text="#000000">
+    Herzliche Adventsgrüße<br>
+  </body>
+</html>
+
+--------------060305070900000101080707--
+
+--------------000801030509090203070207
+Content-Type: application/pdf;
+ name="Weihnachtsbrief13.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="Weihnachtsbrief13.pdf"
+
+JVBERi0 [...shortened...] DYKJSVFT0YK
+--------------000801030509090203070207--
\ No newline at end of file
diff --git a/tests/src/mail.recode2.out b/tests/src/mail.recode2.out
new file mode 100644
index 0000000..ff4c323
--- /dev/null
+++ b/tests/src/mail.recode2.out
@@ -0,0 +1,42 @@
+Message-ID: <52B02B8F.9010500 at domain.de>
+Date: Tue, 17 Dec 2013 11:46:39 +0100
+From: Sender <sender at domain.de>
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.13) Gecko/20101207 Thunderbird/3.1.7
+To: Verborgene_Empfaenger:;
+Subject: Weihnachtsbrief
+Content-Type: multipart/mixed;
+ boundary="------------000801030509090203070207"
+
+This is a multi-part message in MIME format.
+--------------000801030509090203070207
+
+Content-Type: multipart/alternative;
+ boundary="------------060305070900000101080707"
+
+
+--------------060305070900000101080707
+Content-Type: text/plain; charset=ISO-8859-15; format=flowed
+Content-Transfer-Encoding: base64
+
+SGVyemxpY2hlIEFkdmVudHNncvzfZQ==
+--------------060305070900000101080707
+Content-Type: text/html; charset=ISO-8859-15
+Content-Transfer-Encoding: base64
+
+PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEgVHJhbnNpdGlvbmFs
+Ly9FTiI+DQo8aHRtbD4NCiAgPGhlYWQ+DQogICAgPG1ldGEgaHR0cC1lcXVpdj0iY29udGVudC10
+eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9SVNPLTg4NTktMTUiPg0KICA8L2hlYWQ+
+DQogIDxib2R5IGJnY29sb3I9IiNmZmZmZmYiIHRleHQ9IiMwMDAwMDAiPg0KICAgIEhlcnpsaWNo
+ZSBBZHZlbnRzZ3L832U8YnI+DQogIDwvYm9keT4NCjwvaHRtbD4=
+--------------060305070900000101080707--
+
+--------------000801030509090203070207
+
+Content-Type: application/pdf;
+ name="Weihnachtsbrief13.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="Weihnachtsbrief13.pdf"
+
+JVBERi0 [...shortened...] DYKJSVFT0YK
+--------------000801030509090203070207--
\ No newline at end of file
diff --git a/tests/src/mail.recode3 b/tests/src/mail.recode3
new file mode 100644
index 0000000..5ca5d19
--- /dev/null
+++ b/tests/src/mail.recode3
@@ -0,0 +1,11 @@
+X-Sieve: CMU Sieve 2.4
+X-Virus-Scanned: amavisd-new at mx.domain.de
+Message-ID: <52CDD362.2020009 at domain.de>
+Date: Wed, 08 Jan 2014 23:38:26 +0100
+From: Daniel <daniel at domain.de>
+MIME-Version: 1.0
+To: <alec at domain.de>
+Subject: Hello
+Content-Type: text/plain; charset=ISO-8859-15; format=flowed
+
+Hello
diff --git a/tests/src/mail.recode3.out b/tests/src/mail.recode3.out
new file mode 100644
index 0000000..5ca5d19
--- /dev/null
+++ b/tests/src/mail.recode3.out
@@ -0,0 +1,11 @@
+X-Sieve: CMU Sieve 2.4
+X-Virus-Scanned: amavisd-new at mx.domain.de
+Message-ID: <52CDD362.2020009 at domain.de>
+Date: Wed, 08 Jan 2014 23:38:26 +0100
+From: Daniel <daniel at domain.de>
+MIME-Version: 1.0
+To: <alec at domain.de>
+Subject: Hello
+Content-Type: text/plain; charset=ISO-8859-15; format=flowed
+
+Hello




More information about the commits mailing list