ZF-7762: Patch against today's svn (r17952) to fix (De)serialization when str* functions overloaded via mbstring.func_overload in php.ini

Description

Overloading str* functions via the mbstring.func_overload php.ini setting causes Zend_Amf_Exceptions due to using strlen and substr on binary data.

PHP Fatal error: Uncaught exception 'Zend_Amf_Exception' with message 'Buffer underrun at needle position: 301 while requesting length: 9' in /usr/share/pear/Zend-1.11.10/Amf/Util/BinaryStream.php:98\nStack trace:\n#0 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf3/Deserializer.php(194): Zend_Amf_Util_BinaryStream->readBytes(9)\n#1 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf3/Deserializer.php(97): Zend_Amf_Parse_Amf3_Deserializer->readString()\n#2 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf3/Deserializer.php(382): Zend_Amf_Parse_Amf3_Deserializer->readTypeMarker()\n#3 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf3/Deserializer.php(103): Zend_Amf_Parse_Amf3_Deserializer->readObject()\n#4 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf0/Deserializer.php(293): Zend_Amf_Parse_Amf3_Deserializer->readTypeMarker()\n#5 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf0/Deserializer.php(130): Zend_Amf_Parse_Amf0_Deserializer->readAmf3TypeMarker()\n#6 /usr/share/pear/Zend-1.11.10/Amf/Parse/Amf0/Deserializer.php(217): Zend_Amf_Parse_Amf0_Deserializer->readTypeMarke in /usr/share/pear/Zend-1.11.10/Amf/Request.php on line 176

The attached patch changes the necessary strlen and substr uses to use mb_strlen and mb_substr with '8bit' encoding to use bytes rather than multi-byte characters. We've been running this patch on our production site since version 1.9.3 and our flex projects works with mbstring.func_overload = 7 and mbstring.func_overload = 0.

Comments

Patch for when str* functions are overwritten using mbstring.func_overload php.ini setting.

This is the patch updated against ZF version 1.11.10. I do not know why I can't upload it as an attachment.


diff -ru ZendFramework-1.11.10-minimal/library/Zend/Amf/Parse/Amf0/Serializer.php ./Zend/Amf/Parse/Amf0/Serializer.php
--- ZendFramework-1.11.10-minimal/library/Zend/Amf/Parse/Amf0/Serializer.php    2011-03-01 17:25:24.000000000 +0000
+++ ./Zend/Amf/Parse/Amf0/Serializer.php    2011-08-29 20:35:28.000000000 +0000
@@ -127,7 +127,7 @@
                 case (is_bool($data)):
                     $markerType = Zend_Amf_Constants::AMF0_BOOLEAN;
                     break;
-                case (is_string($data) && (strlen($data) > 65536)):
+                case (is_string($data) && (($this->_mbStringFunctionsOverloaded ? mb_strlen($data, '8bit') : strlen($data)) > 65536)):
                     $markerType = Zend_Amf_Constants::AMF0_LONGSTRING;
                     break;
                 case (is_string($data)):
diff -ru ZendFramework-1.11.10-minimal/library/Zend/Amf/Parse/Amf3/Serializer.php ./Zend/Amf/Parse/Amf3/Serializer.php
--- ZendFramework-1.11.10-minimal/library/Zend/Amf/Parse/Amf3/Serializer.php    2011-03-01 17:25:24.000000000 +0000
+++ ./Zend/Amf/Parse/Amf3/Serializer.php    2011-08-29 20:39:51.000000000 +0000
@@ -215,7 +215,7 @@
      * @return Zend_Amf_Parse_Amf3_Serializer
      */
     protected function writeBinaryString(&$string){
-        $ref = strlen($string) << 1 | 0x01;
+        $ref = ($this->_mbStringFunctionsOverloaded ? mb_strlen($string, '8bit') : strlen($string)) << 1 | 0x01;
         $this->writeInteger($ref);
         $this->_stream->writeBytes($string);
 
@@ -230,7 +230,7 @@
      */
     public function writeString(&$string)
     {
-        $len = strlen($string);
+        $len = $this->_mbStringFunctionsOverloaded ? mb_strlen($string, '8bit') : strlen($string);
         if(!$len){
             $this->writeInteger(0x01);
             return $this;
diff -ru ZendFramework-1.11.10-minimal/library/Zend/Amf/Parse/Serializer.php ./Zend/Amf/Parse/Serializer.php
--- ZendFramework-1.11.10-minimal/library/Zend/Amf/Parse/Serializer.php 2011-03-01 17:25:24.000000000 +0000
+++ ./Zend/Amf/Parse/Serializer.php 2011-08-29 20:44:20.000000000 +0000
@@ -38,6 +38,13 @@
     protected $_stream;
 
     /**
+     * str* functions overloaded using mbstring.func_overload
+     *
+     * @var bool
+     */
+    protected $mbStringFunctionsOverloaded;
+
+    /**
      * Constructor
      *
      * @param  Zend_Amf_Parse_OutputStream $stream
@@ -46,6 +53,7 @@
     public function __construct(Zend_Amf_Parse_OutputStream $stream)
     {
         $this->_stream = $stream;
+        $this->_mbStringFunctionsOverloaded = function_exists('mb_strlen') && (ini_get('mbstring.func_overload') !== '') && ((int)ini_get('mbstring.func_overload') & 2);
     }
 
     /**
diff -ru ZendFramework-1.11.10-minimal/library/Zend/Amf/Util/BinaryStream.php ./Zend/Amf/Util/BinaryStream.php
--- ZendFramework-1.11.10-minimal/library/Zend/Amf/Util/BinaryStream.php    2011-03-01 17:25:24.000000000 +0000
+++ ./Zend/Amf/Util/BinaryStream.php    2011-08-29 20:51:36.000000000 +0000
@@ -51,6 +51,11 @@
     protected $_needle;
 
     /**
+     * @var bool str* functions overloaded using mbstring.func_overload?
+     */
+    protected $_mbStringFunctionsOverloaded;
+
+    /**
      * Constructor
      *
      * Create a reference to a byte stream that is going to be parsed or created
@@ -69,7 +74,8 @@
 
         $this->_stream       = $stream;
         $this->_needle       = 0;
-        $this->_streamLength = strlen($stream);
+        $this->_mbStringFunctionsOverloaded = function_exists('mb_strlen') && (ini_get('mbstring.func_overload') !== '') && ((int)ini_get('mbstring.func_overload') & 2);
+        $this->_streamLength = $this->_mbStringFunctionsOverloaded ? mb_strlen($stream, '8bit') : strlen($stream);
         $this->_bigEndian    = (pack('l', 1) === "\x00\x00\x00\x01");
     }
 
@@ -97,7 +103,7 @@
             require_once 'Zend/Amf/Exception.php';
             throw new Zend_Amf_Exception('Buffer underrun at needle position: ' . $this->_needle . ' while requesting length: ' . $length);
         }
-        $bytes = substr($this->_stream, $this->_needle, $length);
+        $bytes = $this->_mbStringFunctionsOverloaded ? mb_substr($this->_stream, $this->_needle, $length, '8bit') : substr($this->_stream, $this->_needle, $length);
         $this->_needle+= $length;
         return $bytes;
     }
@@ -184,7 +190,7 @@
      */
     public function writeUtf($stream)
     {
-        $this->writeInt(strlen($stream));
+        $this->writeInt($this->_mbStringFunctionsOverloaded ? mb_strlen($stream, '8bit') : strlen($stream));
         $this->_stream.= $stream;
         return $this;
     }
@@ -209,7 +215,7 @@
      */
     public function writeLongUtf($stream)
     {
-        $this->writeLong(strlen($stream));
+        $this->writeLong($this->_mbStringFunctionsOverloaded ? mb_strlen($stream, '8bit') : strlen($stream));
         $this->_stream.= $stream;
     }
 
@@ -255,7 +261,7 @@
      */
     public function readDouble()
     {
-        $bytes = substr($this->_stream, $this->_needle, 8);
+        $bytes = $this->_mbStringFunctionsOverloaded ? mb_substr($this->_stream, $this->_needle, 8, '8bit') : substr($this->_stream, $this->_needle, 8);
         $this->_needle+= 8;
 
         if (!$this->_bigEndian) {

Fixed on trunk (25178) and release-1.12 (25179)