Thrift-1427: PHP library uses non-multibyte safe functions with mbstring function overloading
Client: php
Patch: Bryan Alves

Fixes issue with php overloaded mbstring to be binary-safe for strlen and substr.



git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1207960 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/php/src/TStringUtils.php b/lib/php/src/TStringUtils.php
new file mode 100644
index 0000000..69672e8
--- /dev/null
+++ b/lib/php/src/TStringUtils.php
@@ -0,0 +1,81 @@
+<?php
+
+interface TStringFunc {
+    public function substr($str, $start, $length = null);
+    public function strlen($str);
+}
+
+class TStringFunc_Core
+implements TStringFunc {
+    public function substr($str, $start, $length = null) {
+        // specifying a null $length would return an empty string
+        if($length === null) {
+            return substr($str, $start);
+        }
+        return substr($str, $start, $length);
+    }
+
+    public function strlen($str) {
+        return strlen($str);
+    }
+}
+
+class TStringFunc_Mbstring
+implements TStringFunc {
+    public function substr($str, $start, $length = null) {
+        /**
+         * We need to set the charset parameter, which is the second
+         * optional parameter and the first optional parameter can't
+         * be null or false as a "magic" value because that would
+         * cause an empty string to be returned, so we need to
+         * actually calculate the proper length value.
+         */
+        if($length === null) {
+            $length = $this->strlen($str) - $start;
+        }
+
+        return mb_substr($str, $start, $length, '8bit');
+    }
+
+    public function strlen($str) {
+        return mb_strlen($str, '8bit');
+    }
+}
+
+class TStringFuncFactory {
+    private static $_instance;
+
+    /**
+     * Get the Singleton instance of TStringFunc implementation that is
+     * compatible with the current system's mbstring.func_overload settings.
+     *
+     * @return TStringFunc
+     */
+    public static function create() {
+        if(!self::$_instance) {
+            self::_setInstance();
+        }
+
+        return self::$_instance;
+    }
+
+    private static function _setInstance() {
+        /**
+         * Cannot use str* functions for byte counting because multibyte
+         * characters will be read a single bytes.
+         *
+         * See: http://us.php.net/manual/en/mbstring.overload.php
+         */
+        if(ini_get('mbstring.func_overload') & 2) {
+            self::$_instance = new TStringFunc_Mbstring();
+        }
+        /**
+         * mbstring is not installed or does not have function overloading
+         * of the str* functions enabled so use PHP core str* functions for
+         * byte counting.
+         */
+        else {
+            self::$_instance = new TStringFunc_Core();
+        }
+    }
+}
diff --git a/lib/php/src/Thrift.php b/lib/php/src/Thrift.php
index d2a9441..c845395 100644
--- a/lib/php/src/Thrift.php
+++ b/lib/php/src/Thrift.php
@@ -785,3 +785,5 @@
 }
 include_once $GLOBALS['THRIFT_ROOT'].'/protocol/TProtocol.php';
 include_once $GLOBALS['THRIFT_ROOT'].'/transport/TTransport.php';
+include_once $GLOBALS['THRIFT_ROOT'].'/TStringUtils.php';
+
diff --git a/lib/php/src/protocol/TBinaryProtocol.php b/lib/php/src/protocol/TBinaryProtocol.php
index b5bc750..40b9f9b 100644
--- a/lib/php/src/protocol/TBinaryProtocol.php
+++ b/lib/php/src/protocol/TBinaryProtocol.php
@@ -180,7 +180,7 @@
   }
 
   public function writeString($value) {
-    $len = strlen($value);
+    $len = TStringFuncFactory::create()->strlen($value);
     $result = $this->writeI32($len);
     if ($len) {
       $this->trans_->write($value, $len);
diff --git a/lib/php/src/protocol/TCompactProtocol.php b/lib/php/src/protocol/TCompactProtocol.php
index 6ecbd09..9f0d407 100644
--- a/lib/php/src/protocol/TCompactProtocol.php
+++ b/lib/php/src/protocol/TCompactProtocol.php
@@ -121,7 +121,7 @@
 
   public function writeVarint($data) {
     $out = $this->getVarint($data);
-    $result = strlen($out);
+    $result = TStringFuncFactory::create()->strlen($out);
     $this->trans_->write($out, $result);
     return $result;
   }
@@ -308,7 +308,7 @@
   }
 
   public function writeString($value) {
-    $len = strlen($value);
+    $len = TStringFuncFactory::create()->strlen($value);
     $result = $this->writeVarint($len);
     if ($len) {
       $this->trans_->write($value, $len);
@@ -653,7 +653,7 @@
         }
       }
 
-      $ret = strlen($out);
+      $ret = TStringFuncFactory::create()->strlen($out);
       $this->trans_->write($out, $ret);
 
       return $ret;
diff --git a/lib/php/src/transport/TBufferedTransport.php b/lib/php/src/transport/TBufferedTransport.php
index e841564..9b27d02 100644
--- a/lib/php/src/transport/TBufferedTransport.php
+++ b/lib/php/src/transport/TBufferedTransport.php
@@ -87,7 +87,7 @@
   }
 
   public function putBack($data) {
-    if (strlen($this->rBuf_) === 0) {
+    if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) {
       $this->rBuf_ = $data;
     } else {
       $this->rBuf_ = ($data . $this->rBuf_);
@@ -104,7 +104,7 @@
    * the buffered readAll.
    */
   public function readAll($len) {
-    $have = strlen($this->rBuf_);
+    $have = TStringFuncFactory::create()->strlen($this->rBuf_);
     if ($have == 0) {
       $data = $this->transport_->readAll($len);
     } else if ($have < $len) {
@@ -115,31 +115,31 @@
       $data = $this->rBuf_;
       $this->rBuf_ = '';
     } else if ($have > $len) {
-      $data = substr($this->rBuf_, 0, $len);
-      $this->rBuf_ = substr($this->rBuf_, $len);
+      $data = TStringFuncFactory::create()->substr($this->rBuf_, 0, $len);
+      $this->rBuf_ = TStringFuncFactory::create()->substr($this->rBuf_, $len);
     }
     return $data;
   }
 
   public function read($len) {
-    if (strlen($this->rBuf_) === 0) {
+    if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) {
       $this->rBuf_ = $this->transport_->read($this->rBufSize_);
     }
 
-    if (strlen($this->rBuf_) <= $len) {
+    if (TStringFuncFactory::create()->strlen($this->rBuf_) <= $len) {
       $ret = $this->rBuf_;
       $this->rBuf_ = '';
       return $ret;
     }
 
-    $ret = substr($this->rBuf_, 0, $len);
-    $this->rBuf_ = substr($this->rBuf_, $len);
+    $ret = TStringFuncFactory::create()->substr($this->rBuf_, 0, $len);
+    $this->rBuf_ = TStringFuncFactory::create()->substr($this->rBuf_, $len);
     return $ret;
   }
 
   public function write($buf) {
     $this->wBuf_ .= $buf;
-    if (strlen($this->wBuf_) >= $this->wBufSize_) {
+    if (TStringFuncFactory::create()->strlen($this->wBuf_) >= $this->wBufSize_) {
       $out = $this->wBuf_;
 
       // Note that we clear the internal wBuf_ prior to the underlying write
@@ -151,7 +151,7 @@
   }
 
   public function flush() {
-    if (strlen($this->wBuf_) > 0) {
+    if (TStringFuncFactory::create()->strlen($this->wBuf_) > 0) {
       $this->transport_->write($this->wBuf_);
       $this->wBuf_ = '';
     }
diff --git a/lib/php/src/transport/TFramedTransport.php b/lib/php/src/transport/TFramedTransport.php
index b1c0bb5..bc759de 100644
--- a/lib/php/src/transport/TFramedTransport.php
+++ b/lib/php/src/transport/TFramedTransport.php
@@ -98,20 +98,20 @@
       return $this->transport_->read($len);
     }
 
-    if (strlen($this->rBuf_) === 0) {
+    if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) {
       $this->readFrame();
     }
 
     // Just return full buff
-    if ($len >= strlen($this->rBuf_)) {
+    if ($len >= TStringFuncFactory::create()->strlen($this->rBuf_)) {
       $out = $this->rBuf_;
       $this->rBuf_ = null;
       return $out;
     }
 
-    // Return substr
-    $out = substr($this->rBuf_, 0, $len);
-    $this->rBuf_ = substr($this->rBuf_, $len);
+    // Return TStringFuncFactory::create()->substr
+    $out = TStringFuncFactory::create()->substr($this->rBuf_, 0, $len);
+    $this->rBuf_ = TStringFuncFactory::create()->substr($this->rBuf_, $len);
     return $out;
   }
 
@@ -121,7 +121,7 @@
    * @param string $data data to return
    */
   public function putBack($data) {
-    if (strlen($this->rBuf_) === 0) {
+    if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) {
       $this->rBuf_ = $data;
     } else {
       $this->rBuf_ = ($data . $this->rBuf_);
@@ -150,8 +150,8 @@
       return $this->transport_->write($buf, $len);
     }
 
-    if ($len !== null && $len < strlen($buf)) {
-      $buf = substr($buf, 0, $len);
+    if ($len !== null && $len < TStringFuncFactory::create()->strlen($buf)) {
+      $buf = TStringFuncFactory::create()->substr($buf, 0, $len);
     }
     $this->wBuf_ .= $buf;
   }
@@ -161,11 +161,11 @@
    * followed by the actual data.
    */
   public function flush() {
-    if (!$this->write_ || strlen($this->wBuf_) == 0) {
+    if (!$this->write_ || TStringFuncFactory::create()->strlen($this->wBuf_) == 0) {
       return $this->transport_->flush();
     }
 
-    $out = pack('N', strlen($this->wBuf_));
+    $out = pack('N', TStringFuncFactory::create()->strlen($this->wBuf_));
     $out .= $this->wBuf_;
 
     // Note that we clear the internal wBuf_ prior to the underlying write
diff --git a/lib/php/src/transport/THttpClient.php b/lib/php/src/transport/THttpClient.php
index 880011a..118f0d3 100644
--- a/lib/php/src/transport/THttpClient.php
+++ b/lib/php/src/transport/THttpClient.php
@@ -85,7 +85,7 @@
    * @param string $uri
    */
   public function __construct($host, $port=80, $uri='', $scheme = 'http') {
-    if ((strlen($uri) > 0) && ($uri{0} != '/')) {
+    if ((TStringFuncFactory::create()->strlen($uri) > 0) && ($uri{0} != '/')) {
       $uri = '/'.$uri;
     }
     $this->scheme_ = $scheme;
@@ -175,7 +175,7 @@
                      'Accept: application/x-thrift',
                      'User-Agent: PHP/THttpClient',
                      'Content-Type: application/x-thrift',
-                     'Content-Length: '.strlen($this->buf_));
+                     'Content-Length: '.TStringFuncFactory::create()->strlen($this->buf_));
 
     $options = array('method' => 'POST',
                      'header' => implode("\r\n", $headers),
diff --git a/lib/php/src/transport/TMemoryBuffer.php b/lib/php/src/transport/TMemoryBuffer.php
index a0b1a54..13aa9e5 100644
--- a/lib/php/src/transport/TMemoryBuffer.php
+++ b/lib/php/src/transport/TMemoryBuffer.php
@@ -54,20 +54,20 @@
   }
 
   public function read($len) {
-    if (strlen($this->buf_) === 0) {
+    if (TStringFuncFactory::create()->strlen($this->buf_) === 0) {
       throw new TTransportException('TMemoryBuffer: Could not read ' .
                                     $len . ' bytes from buffer.',
                                     TTransportException::UNKNOWN);
     }
 
-    if (strlen($this->buf_) <= $len) {
+    if (TStringFuncFactory::create()->strlen($this->buf_) <= $len) {
       $ret = $this->buf_;
       $this->buf_ = '';
       return $ret;
     }
 
-    $ret = substr($this->buf_, 0, $len);
-    $this->buf_ = substr($this->buf_, $len);
+    $ret = TStringFuncFactory::create()->substr($this->buf_, 0, $len);
+    $this->buf_ = TStringFuncFactory::create()->substr($this->buf_, $len);
 
     return $ret;
   }
@@ -77,6 +77,6 @@
   }
 
   public function available() {
-    return strlen($this->buf_);
+    return TStringFuncFactory::create()->strlen($this->buf_);
   }
 }
diff --git a/lib/php/src/transport/TPhpStream.php b/lib/php/src/transport/TPhpStream.php
index 94b11a6..e16b472 100644
--- a/lib/php/src/transport/TPhpStream.php
+++ b/lib/php/src/transport/TPhpStream.php
@@ -86,12 +86,12 @@
   }
 
   public function write($buf) {
-    while (strlen($buf) > 0) {
+    while (TStringFuncFactory::create()->strlen($buf) > 0) {
       $got = @fwrite($this->outStream_, $buf);
       if ($got === 0 || $got === FALSE) {
-        throw new TException('TPhpStream: Could not write '.strlen($buf).' bytes');
+        throw new TException('TPhpStream: Could not write '.TStringFuncFactory::create()->strlen($buf).' bytes');
       }
-      $buf = substr($buf, $got);
+      $buf = TStringFuncFactory::create()->substr($buf, $got);
     }
   }
 
diff --git a/lib/php/src/transport/TSocket.php b/lib/php/src/transport/TSocket.php
index daf9020..0203384 100644
--- a/lib/php/src/transport/TSocket.php
+++ b/lib/php/src/transport/TSocket.php
@@ -283,23 +283,23 @@
     $write = array($this->handle_);
 
     // keep writing until all the data has been written
-    while (strlen($buf) > 0) {
+    while (TStringFuncFactory::create()->strlen($buf) > 0) {
       // wait for stream to become available for writing
       $writable = @stream_select($null, $write, $null, $this->sendTimeoutSec_, $this->sendTimeoutUsec_);
       if ($writable > 0) {
         // write buffer to stream
         $written = @stream_socket_sendto($this->handle_, $buf);
         if ($written === -1 || $written === false) {
-          throw new TTransportException('TSocket: Could not write '.strlen($buf).' bytes '.
+          throw new TTransportException('TSocket: Could not write '.TStringFuncFactory::create()->strlen($buf).' bytes '.
                                    $this->host_.':'.$this->port_);
         }
         // determine how much of the buffer is left to write
-        $buf = substr($buf, $written);
+        $buf = TStringFuncFactory::create()->substr($buf, $written);
       } else if ($writable === 0) {
-          throw new TTransportException('TSocket: timed out writing '.strlen($buf).' bytes from '.
+          throw new TTransportException('TSocket: timed out writing '.TStringFuncFactory::create()->strlen($buf).' bytes from '.
                                $this->host_.':'.$this->port_);
         } else {
-            throw new TTransportException('TSocket: Could not write '.strlen($buf).' bytes '.
+            throw new TTransportException('TSocket: Could not write '.TStringFuncFactory::create()->strlen($buf).' bytes '.
                                  $this->host_.':'.$this->port_);
         }
       }
diff --git a/lib/php/src/transport/TTransport.php b/lib/php/src/transport/TTransport.php
index e0e336d..f8c8d86 100644
--- a/lib/php/src/transport/TTransport.php
+++ b/lib/php/src/transport/TTransport.php
@@ -83,7 +83,7 @@
 
     $data = '';
     $got = 0;
-    while (($got = strlen($data)) < $len) {
+    while (($got = TStringFuncFactory::create()->strlen($data)) < $len) {
       $data .= $this->read($len - $got);
     }
     return $data;