From a6b0ca60a431b8e56d7c23246de71978d6968a79 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 02 Jun 2013 08:33:11 -0400
Subject: [PATCH] Fix bug where serialized strings were truncated in PDO::quote() (#1489142)

---
 CHANGELOG                                    |    1 
 program/lib/Roundcube/rcube_cache_shared.php |   32 ++++++++++++++--
 program/lib/Roundcube/rcube_cache.php        |   33 ++++++++++++++--
 program/lib/Roundcube/rcube_db.php           |   23 +++++++++--
 program/lib/Roundcube/rcube_imap_cache.php   |   12 +++---
 5 files changed, 82 insertions(+), 19 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 0af5ff1..74d7d84 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix bug where serialized strings were truncated in PDO::quote() (#1489142)
 - Improved handling of Reply-To/Bcc addresses of identity in compose form (#1489016)
 - Fix displaying messages with invalid self-closing HTML tags (#1489137)
 - Fix PHP warning when responding to a message with many Return-Path headers (#1489136)
diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php
index 129f324..08c9fc8 100644
--- a/program/lib/Roundcube/rcube_cache.php
+++ b/program/lib/Roundcube/rcube_cache.php
@@ -145,7 +145,7 @@
      */
     function write($key, $data)
     {
-        return $this->write_record($key, $this->packed ? serialize($data) : $data);
+        return $this->write_record($key, $this->serialize($data));
     }
 
 
@@ -219,7 +219,7 @@
             if ($this->cache_changes[$key]) {
                 // Make sure we're not going to write unchanged data
                 // by comparing current md5 sum with the sum calculated on DB read
-                $data = $this->packed ? serialize($data) : $data;
+                $data = $this->serialize($data);
 
                 if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
                     $this->write_record($key, $data);
@@ -255,7 +255,7 @@
 
             if ($data) {
                 $md5sum = md5($data);
-                $data   = $this->packed ? unserialize($data) : $data;
+                $data   = $this->unserialize($data);
 
                 if ($nostore) {
                     return $data;
@@ -283,7 +283,7 @@
                 $key = substr($sql_arr['cache_key'], strlen($this->prefix)+1);
                 $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
                 if ($sql_arr['data']) {
-                    $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data'];
+                    $data = $this->unserialize($sql_arr['data']);
                 }
 
                 if ($nostore) {
@@ -364,7 +364,6 @@
      * @param string  $key         Cache key name or pattern
      * @param boolean $prefix_mode Enable it to clear all keys starting
      *                             with prefix specified in $key
-     *
      */
     private function remove_record($key=null, $prefix_mode=false)
     {
@@ -553,4 +552,28 @@
         // This way each cache will have its own index
         return sprintf('%d:%s%s', $this->userid, $this->prefix, 'INDEX');
     }
+
+    /**
+     * Serializes data for storing
+     */
+    private function serialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->encode($data, $this->packed);
+        }
+
+        return $this->packed ? serialize($data) : $data;
+    }
+
+    /**
+     * Unserializes serialized data
+     */
+    private function unserialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->decode($data, $this->packed);
+        }
+
+        return $this->packed ? @unserialize($data) : $data;
+    }
 }
diff --git a/program/lib/Roundcube/rcube_cache_shared.php b/program/lib/Roundcube/rcube_cache_shared.php
index 5983bd3..2c4af20 100644
--- a/program/lib/Roundcube/rcube_cache_shared.php
+++ b/program/lib/Roundcube/rcube_cache_shared.php
@@ -144,7 +144,7 @@
      */
     function write($key, $data)
     {
-        return $this->write_record($key, $this->packed ? serialize($data) : $data);
+        return $this->write_record($key, $this->serialize($data));
     }
 
 
@@ -216,7 +216,7 @@
             if ($this->cache_changes[$key]) {
                 // Make sure we're not going to write unchanged data
                 // by comparing current md5 sum with the sum calculated on DB read
-                $data = $this->packed ? serialize($data) : $data;
+                $data = $this->serialize($data);
 
                 if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
                     $this->write_record($key, $data);
@@ -252,7 +252,7 @@
 
             if ($data) {
                 $md5sum = md5($data);
-                $data   = $this->packed ? unserialize($data) : $data;
+                $data   = $this->unserialize($data);
 
                 if ($nostore) {
                     return $data;
@@ -278,7 +278,7 @@
             if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
                 $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
                 if ($sql_arr['data']) {
-                    $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data'];
+                    $data = $this->unserialize($sql_arr['data']);
                 }
 
                 if ($nostore) {
@@ -541,4 +541,28 @@
         // This way each cache will have its own index
         return $this->prefix . 'INDEX';
     }
+
+    /**
+     * Serializes data for storing
+     */
+    private function serialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->encode($data, $this->packed);
+        }
+
+        return $this->packed ? serialize($data) : $data;
+    }
+
+    /**
+     * Unserializes serialized data
+     */
+    private function unserialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->decode($data, $this->packed);
+        }
+
+        return $this->packed ? @unserialize($data) : $data;
+    }
 }
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 5da38c8..645b85a 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -795,12 +795,19 @@
     /**
      * Encodes non-UTF-8 characters in string/array/object (recursive)
      *
-     * @param mixed $input Data to fix
+     * @param mixed $input      Data to fix
+     * @param bool  $serialized Enable serialization
      *
      * @return mixed Properly UTF-8 encoded data
      */
-    public static function encode($input)
+    public static function encode($input, $serialized = false)
     {
+        // use Base64 encoding to workaround issues with invalid
+        // or null characters in serialized string (#1489142)
+        if ($serialized) {
+            return base64_encode(serialize($input));
+        }
+
         if (is_object($input)) {
             foreach (get_object_vars($input) as $idx => $value) {
                 $input->$idx = self::encode($value);
@@ -811,6 +818,7 @@
             foreach ($input as $idx => $value) {
                 $input[$idx] = self::encode($value);
             }
+
             return $input;
         }
 
@@ -820,12 +828,19 @@
     /**
      * Decodes encoded UTF-8 string/object/array (recursive)
      *
-     * @param mixed $input Input data
+     * @param mixed $input      Input data
+     * @param bool  $serialized Enable serialization
      *
      * @return mixed Decoded data
      */
-    public static function decode($input)
+    public static function decode($input, $serialized = false)
     {
+        if ($serialized) {
+            // use Base64 encoding to workaround issues with invalid
+            // or null characters in serialized string (#1489142)
+            return @unserialize(base64_decode($input));
+        }
+
         if (is_object($input)) {
             foreach (get_object_vars($input) as $idx => $value) {
                 $input->$idx = self::decode($value);
diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php
index 089a524..71545f1 100644
--- a/program/lib/Roundcube/rcube_imap_cache.php
+++ b/program/lib/Roundcube/rcube_imap_cache.php
@@ -419,7 +419,7 @@
         }
 
         unset($msg->flags);
-        $msg = serialize($this->db->encode($msg));
+        $msg = $this->db->encode($msg, true);
 
         // update cache record (even if it exists, the update
         // here will work as select, assume row exist if affected_rows=0)
@@ -641,7 +641,7 @@
 
         if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
             $data  = explode('@', $sql_arr['data']);
-            $index = @unserialize($data[0]);
+            $index = $this->db->decode($data[0], true);
             unset($data[0]);
 
             if (empty($index)) {
@@ -678,7 +678,7 @@
 
         if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
             $data   = explode('@', $sql_arr['data']);
-            $thread = @unserialize($data[0]);
+            $thread = $this->db->decode($data[0], true);
             unset($data[0]);
 
             if (empty($thread)) {
@@ -704,7 +704,7 @@
         $data, $mbox_data = array(), $exists = false, $modseq = null)
     {
         $data = array(
-            serialize($data),
+            $this->db->encode($data, true),
             $sort_field,
             (int) $this->skip_deleted,
             (int) $mbox_data['UIDVALIDITY'],
@@ -737,7 +737,7 @@
     private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false)
     {
         $data = array(
-            serialize($data),
+            $this->db->encode($data, true),
             (int) $this->skip_deleted,
             (int) $mbox_data['UIDVALIDITY'],
             (int) $mbox_data['UIDNEXT'],
@@ -1069,7 +1069,7 @@
      */
     private function build_message($sql_arr)
     {
-        $message = $this->db->decode(unserialize($sql_arr['data']));
+        $message = $this->db->decode($sql_arr['data'], true);
 
         if ($message) {
             $message->flags = array();

--
Gitblit v1.9.1