web/Zend/Serializer/Adapter/PythonPickle.php
changeset 0 4eba9c11703f
equal deleted inserted replaced
-1:000000000000 0:4eba9c11703f
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Serializer
       
    17  * @subpackage Adapter
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: PythonPickle.php 21187 2010-02-24 01:22:01Z stas $
       
    21  */
       
    22 
       
    23 /** @see Zend_Serializer_Adapter_AdapterAbstract */
       
    24 require_once 'Zend/Serializer/Adapter/AdapterAbstract.php';
       
    25 
       
    26 /**
       
    27  * @link       http://www.python.org
       
    28  * @see        Phython3.1/Lib/pickle.py
       
    29  * @see        Phython3.1/Modules/_pickle.c
       
    30  * @link       http://pickle-js.googlecode.com
       
    31  * @category   Zend
       
    32  * @package    Zend_Serializer
       
    33  * @subpackage Adapter
       
    34  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    35  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    36  */
       
    37 class Zend_Serializer_Adapter_PythonPickle extends Zend_Serializer_Adapter_AdapterAbstract
       
    38 {
       
    39     /* Pickle opcodes. See pickletools.py for extensive docs.  The listing
       
    40        here is in kind-of alphabetical order of 1-character pickle code.
       
    41        pickletools groups them by purpose. */
       
    42     const OP_MARK            = '(';     // push special markobject on stack
       
    43     const OP_STOP            = '.';     // every pickle ends with STOP
       
    44     const OP_POP             = '0';     // discard topmost stack item
       
    45     const OP_POP_MARK        = '1';     // discard stack top through topmost markobject
       
    46     const OP_DUP             = '2';     // duplicate top stack item
       
    47     const OP_FLOAT           = 'F';     // push float object; decimal string argument
       
    48     const OP_INT             = 'I';     // push integer or bool; decimal string argument
       
    49     const OP_BININT          = 'J';     // push four-byte signed int
       
    50     const OP_BININT1         = 'K';     // push 1-byte unsigned int
       
    51     const OP_LONG            = 'L';     // push long; decimal string argument
       
    52     const OP_BININT2         = 'M';     // push 2-byte unsigned int
       
    53     const OP_NONE            = 'N';     // push None
       
    54     const OP_PERSID          = 'P';     // push persistent object; id is taken from string arg
       
    55     const OP_BINPERSID       = 'Q';     //  "       "         "  ;  "  "   "     "  stack
       
    56     const OP_REDUCE          = 'R';     // apply callable to argtuple, both on stack
       
    57     const OP_STRING          = 'S';     // push string; NL-terminated string argument
       
    58     const OP_BINSTRING       = 'T';     // push string; counted binary string argument
       
    59     const OP_SHORT_BINSTRING = 'U';     //  "     "   ;    "      "       "      " < 256 bytes
       
    60     const OP_UNICODE         = 'V';     // push Unicode string; raw-unicode-escaped'd argument
       
    61     const OP_BINUNICODE      = 'X';     //   "     "       "  ; counted UTF-8 string argument
       
    62     const OP_APPEND          = 'a';     // append stack top to list below it
       
    63     const OP_BUILD           = 'b';     // call __setstate__ or __dict__.update()
       
    64     const OP_GLOBAL          = 'c';     // push self.find_class(modname, name); 2 string args
       
    65     const OP_DICT            = 'd';     // build a dict from stack items
       
    66     const OP_EMPTY_DICT      = '}';     // push empty dict
       
    67     const OP_APPENDS         = 'e';     // extend list on stack by topmost stack slice
       
    68     const OP_GET             = 'g';     // push item from memo on stack; index is string arg
       
    69     const OP_BINGET          = 'h';     //   "    "    "    "   "   "  ;   "    " 1-byte arg
       
    70     const OP_INST            = 'i';     // build & push class instance
       
    71     const OP_LONG_BINGET     = 'j';     // push item from memo on stack; index is 4-byte arg
       
    72     const OP_LIST            = 'l';     // build list from topmost stack items
       
    73     const OP_EMPTY_LIST      = ']';     // push empty list
       
    74     const OP_OBJ             = 'o';     // build & push class instance
       
    75     const OP_PUT             = 'p';     // store stack top in memo; index is string arg
       
    76     const OP_BINPUT          = 'q';     //   "     "    "   "   " ;   "    " 1-byte arg
       
    77     const OP_LONG_BINPUT     = 'r';     //   "     "    "   "   " ;   "    " 4-byte arg
       
    78     const OP_SETITEM         = 's';     // add key+value pair to dict
       
    79     const OP_TUPLE           = 't';     // build tuple from topmost stack items
       
    80     const OP_EMPTY_TUPLE     = ')';     // push empty tuple
       
    81     const OP_SETITEMS        = 'u';     // modify dict by adding topmost key+value pairs
       
    82     const OP_BINFLOAT        = 'G';     // push float; arg is 8-byte float encoding
       
    83 
       
    84     /* Protocol 2 */
       
    85     const OP_PROTO           = "\x80";  // identify pickle protocol
       
    86     const OP_NEWOBJ          = "\x81";  // build object by applying cls.__new__ to argtuple
       
    87     const OP_EXT1            = "\x82";  // push object from extension registry; 1-byte index
       
    88     const OP_EXT2            = "\x83";  // ditto, but 2-byte index
       
    89     const OP_EXT4            = "\x84";  // ditto, but 4-byte index
       
    90     const OP_TUPLE1          = "\x85";  // build 1-tuple from stack top
       
    91     const OP_TUPLE2          = "\x86";  // build 2-tuple from two topmost stack items
       
    92     const OP_TUPLE3          = "\x87";  // build 3-tuple from three topmost stack items
       
    93     const OP_NEWTRUE         = "\x88";  // push True
       
    94     const OP_NEWFALSE        = "\x89";  // push False
       
    95     const OP_LONG1           = "\x8a";  // push long from < 256 bytes
       
    96     const OP_LONG4           = "\x8b";  // push really big long
       
    97 
       
    98     /* Protocol 3 (Python 3.x) */
       
    99     const OP_BINBYTES        = 'B';     // push bytes; counted binary string argument
       
   100     const OP_SHORT_BINBYTES  = 'C';     //  "     "   ;    "      "       "      " < 256 bytes
       
   101 
       
   102     /**
       
   103      * @var bool Whether or not this is a PHP 6 binary
       
   104      */
       
   105     protected static $_isPhp6 = null;
       
   106 
       
   107     /**
       
   108      * @var bool Whether or not the system is little-endian
       
   109      */
       
   110     protected static $_isLittleEndian = null;
       
   111 
       
   112     /**
       
   113      * @var array Strings representing quotes
       
   114      */
       
   115     protected static $_quoteString = array(
       
   116         '\\' => '\\\\',
       
   117         "\x00" => '\\x00', "\x01" => '\\x01', "\x02" => '\\x02', "\x03" => '\\x03',
       
   118         "\x04" => '\\x04', "\x05" => '\\x05', "\x06" => '\\x06', "\x07" => '\\x07',
       
   119         "\x08" => '\\x08', "\x09" => '\\t',   "\x0a" => '\\n',   "\x0b" => '\\x0b',
       
   120         "\x0c" => '\\x0c', "\x0d" => '\\r',   "\x0e" => '\\x0e', "\x0f" => '\\x0f',
       
   121         "\x10" => '\\x10', "\x11" => '\\x11', "\x12" => '\\x12', "\x13" => '\\x13',
       
   122         "\x14" => '\\x14', "\x15" => '\\x15', "\x16" => '\\x16', "\x17" => '\\x17',
       
   123         "\x18" => '\\x18', "\x19" => '\\x19', "\x1a" => '\\x1a', "\x1b" => '\\x1b',
       
   124         "\x1c" => '\\x1c', "\x1d" => '\\x1d', "\x1e" => '\\x1e', "\x1f" => '\\x1f',
       
   125         "\xff" => '\\xff'
       
   126     );
       
   127 
       
   128     /**
       
   129      * @var array Default options
       
   130      */
       
   131     protected $_options = array(
       
   132         'protocol'           => 0,
       
   133     );
       
   134 
       
   135     // process vars
       
   136     protected $_protocol           = 0;
       
   137     protected $_binary             = false;
       
   138     protected $_memo               = array();
       
   139     protected $_pickle             = '';
       
   140     protected $_pickleLen          = 0;
       
   141     protected $_pos                = 0;
       
   142     protected $_stack              = array();
       
   143     protected $_marker             = null;
       
   144 
       
   145     /**
       
   146      * Constructor
       
   147      *
       
   148      * @link Zend_Serializer_Adapter_AdapterAbstract::__construct()
       
   149      */
       
   150     public function __construct($opts=array())
       
   151     {
       
   152         parent::__construct($opts);
       
   153 
       
   154         // init
       
   155         if (self::$_isLittleEndian === null) {
       
   156             self::$_isLittleEndian = (pack('l', 1) === "\x01\x00\x00\x00");
       
   157         }
       
   158         if (self::$_isPhp6 === null) {
       
   159             self::$_isPhp6 = !version_compare(PHP_VERSION, '6.0.0', '<');
       
   160         }
       
   161 
       
   162         $this->_marker = new stdClass();
       
   163     }
       
   164 
       
   165     /**
       
   166      * Set an option
       
   167      *
       
   168      * @link   Zend_Serializer_Adapter_AdapterAbstract::setOption()
       
   169      * @param  string $name
       
   170      * @param  mixed $value
       
   171      * @return Zend_Serializer_Adapter_PythonPickle
       
   172      */
       
   173     public function setOption($name, $value)
       
   174     {
       
   175         switch ($name) {
       
   176             case 'protocol':
       
   177                 $value = $this->_checkProtocolNumber($value);
       
   178                 break;
       
   179         }
       
   180 
       
   181         return parent::setOption($name, $value);
       
   182     }
       
   183 
       
   184     /**
       
   185      * Check and normalize pickle protocol number
       
   186      *
       
   187      * @param  int $number
       
   188      * @return int
       
   189      * @throws Zend_Serializer_Exception
       
   190      */
       
   191     protected function _checkProtocolNumber($number)
       
   192     {
       
   193         $int = (int) $number;
       
   194         if ($int < 0 || $int > 3) {
       
   195             require_once 'Zend/Serializer/Exception.php';
       
   196             throw new Zend_Serializer_Exception('Invalid or unknown protocol version "'.$number.'"');
       
   197         }
       
   198         return $int;
       
   199     }
       
   200 
       
   201     /* serialize */
       
   202 
       
   203     /**
       
   204      * Serialize PHP to PythonPickle format
       
   205      *
       
   206      * @param  mixed $value
       
   207      * @param  array $opts
       
   208      * @return string
       
   209      */
       
   210     public function serialize($value, array $opts = array())
       
   211     {
       
   212         $opts = $opts + $this->_options;
       
   213 
       
   214         $this->_protocol = $this->_checkProtocolNumber($opts['protocol']);
       
   215         $this->_binary   = $this->_protocol != 0;
       
   216 
       
   217         // clear process vars before serializing
       
   218         $this->_memo   = array();
       
   219         $this->_pickle = '';
       
   220 
       
   221         // write
       
   222         if ($this->_protocol >= 2) {
       
   223             $this->_writeProto($this->_protocol);
       
   224         }
       
   225         $this->_write($value);
       
   226         $this->_writeStop();
       
   227 
       
   228         // clear process vars after serializing
       
   229         $this->_memo = array();
       
   230         $pickle = $this->_pickle;
       
   231         $this->_pickle = '';
       
   232 
       
   233         return $pickle;
       
   234     }
       
   235 
       
   236     /**
       
   237      * Write a value
       
   238      *
       
   239      * @param  mixed $value
       
   240      * @return void
       
   241      * @throws Zend_Serializer_Exception on invalid or unrecognized value type
       
   242      */
       
   243     protected function _write($value)
       
   244     {
       
   245         if ($value === null) {
       
   246             $this->_writeNull();
       
   247         } elseif ($value === true) {
       
   248             $this->_writeTrue();
       
   249         } elseif ($value === false) {
       
   250             $this->_writeFalse();
       
   251         } elseif (is_int($value)) {
       
   252             $this->_writeInt($value);
       
   253         } elseif (is_float($value)) {
       
   254             $this->_writeFloat($value);
       
   255         } elseif (is_string($value)) {
       
   256             // TODO: write unicode / binary
       
   257             $this->_writeString($value);
       
   258         } elseif (is_array($value)) {
       
   259             if ($this->_isArrayAssoc($value)) {
       
   260                 $this->_writeArrayDict($value);
       
   261             } else {
       
   262                 $this->_writeArrayList($value);
       
   263             }
       
   264         } elseif (is_object($value)) {
       
   265             $this->_writeObject($value);
       
   266         } else {
       
   267             require_once 'Zend/Serializer/Exception.php';
       
   268             throw new Zend_Serializer_Exception(
       
   269                 'PHP-Type "'.gettype($value).'" isn\'t serializable with '.get_class($this)
       
   270             );
       
   271         }
       
   272     }
       
   273 
       
   274     /**
       
   275      * Write pickle protocol
       
   276      *
       
   277      * @param  int $protocol
       
   278      * @return void
       
   279      */
       
   280     protected function _writeProto($protocol)
       
   281     {
       
   282         $this->_pickle .= self::OP_PROTO . $protocol;
       
   283     }
       
   284 
       
   285     /**
       
   286      * Write a get
       
   287      *
       
   288      * @param  int $id Id of memo
       
   289      * @return void
       
   290      */
       
   291     protected function _writeGet($id)
       
   292     {
       
   293         if ($this->_binary) {
       
   294             if ($id <= 0xff) {
       
   295                 // BINGET + chr(i)
       
   296                 $this->_pickle .= self::OP_BINGET . chr($id);
       
   297             } else {
       
   298                 // LONG_BINGET + pack("<i", i)
       
   299                 $idBin = pack('l', $id);
       
   300                 if (self::$_isLittleEndian === false) {
       
   301                     $idBin = strrev($bin);
       
   302                 }
       
   303                 $this->_pickle .= self::OP_LONG_BINGET . $idBin;
       
   304             }
       
   305         } else {
       
   306             $this->_pickle .= self::OP_GET . $id . "\r\n";
       
   307         }
       
   308     }
       
   309 
       
   310     /**
       
   311      * Write a put
       
   312      *
       
   313      * @param  int $id Id of memo
       
   314      * @return void
       
   315      */
       
   316     protected function _writePut($id)
       
   317     {
       
   318         if ($this->_binary) {
       
   319             if ($id <= 0xff) {
       
   320                 // BINPUT + chr(i)
       
   321                 $this->_pickle .= self::OP_BINPUT . chr($id);
       
   322             } else {
       
   323                 // LONG_BINPUT + pack("<i", i)
       
   324                 $idBin = pack('l', $id);
       
   325                 if (self::$_isLittleEndian === false) {
       
   326                     $idBin = strrev($bin);
       
   327                 }
       
   328                 $this->_pickle .= self::OP_LONG_BINPUT . $idBin;
       
   329             }
       
   330         } else {
       
   331             $this->_pickle .= self::OP_PUT . $id . "\r\n";
       
   332         }
       
   333     }
       
   334 
       
   335     /**
       
   336      * Write a null as None
       
   337      *
       
   338      * @return void
       
   339      */
       
   340     protected function _writeNull()
       
   341     {
       
   342         $this->_pickle .= self::OP_NONE;
       
   343     }
       
   344 
       
   345     /**
       
   346      * Write a boolean true
       
   347      *
       
   348      * @return void
       
   349      */
       
   350     protected function _writeTrue()
       
   351     {
       
   352         if ($this->_protocol >= 2) {
       
   353             $this->_pickle .= self::OP_NEWTRUE;
       
   354         } else {
       
   355             $this->_pickle .= self::OP_INT . "01\r\n";
       
   356         }
       
   357     }
       
   358 
       
   359     /**
       
   360      * Write a boolean false
       
   361      *
       
   362      * @return void
       
   363      */
       
   364     protected function _writeFalse()
       
   365     {
       
   366         if ($this->_protocol >= 2) {
       
   367             $this->_pickle .= self::OP_NEWFALSE;
       
   368         } else {
       
   369             $this->_pickle .= self::OP_INT . "00\r\n";
       
   370         }
       
   371     }
       
   372 
       
   373     /**
       
   374      * Write an integer value
       
   375      *
       
   376      * @param  int $value
       
   377      * @return void
       
   378      */
       
   379     protected function _writeInt($value)
       
   380     {
       
   381         if ($this->_binary) {
       
   382             if ($value >= 0) {
       
   383                 if ($value <= 0xff) {
       
   384                     // self.write(BININT1 + chr(obj))
       
   385                     $this->_pickle .= self::OP_BININT1 . chr($value);
       
   386                 } elseif ($value <= 0xffff) {
       
   387                     // self.write("%c%c%c" % (BININT2, obj&0xff, obj>>8))
       
   388                     $this->_pickle .= self::OP_BININT2 . pack('v', $value);
       
   389                 }
       
   390                 return;
       
   391             }
       
   392 
       
   393             // Next check for 4-byte signed ints:
       
   394             $highBits = $value >> 31;  // note that Python shift sign-extends
       
   395             if ($highBits == 0 || $highBits == -1) {
       
   396                 // All high bits are copies of bit 2**31, so the value
       
   397                 // fits in a 4-byte signed int.
       
   398                 // self.write(BININT + pack("<i", obj))
       
   399                 $bin = pack('l', $value);
       
   400                 if (self::$_isLittleEndian === false) {
       
   401                     $bin = strrev($bin);
       
   402                 }
       
   403                 $this->_pickle .= self::OP_BININT . $bin;
       
   404                 return;
       
   405             }
       
   406         }
       
   407 
       
   408         $this->_pickle .= self::OP_INT . $value . "\r\n";
       
   409     }
       
   410 
       
   411     /**
       
   412      * Write a float value
       
   413      *
       
   414      * @param  float $value
       
   415      * @return void
       
   416      */
       
   417     protected function _writeFloat($value)
       
   418     {
       
   419         if ($this->_binary) {
       
   420             // self.write(BINFLOAT + pack('>d', obj))
       
   421             $bin = pack('d', $value);
       
   422             if (self::$_isLittleEndian === true) {
       
   423                 $bin = strrev($bin);
       
   424             }
       
   425             $this->_pickle .= self::OP_BINFLOAT . $bin;
       
   426         } else {
       
   427             $this->_pickle .= self::OP_FLOAT . $value . "\r\n";
       
   428         }
       
   429     }
       
   430 
       
   431     /**
       
   432      * Write a string value
       
   433      *
       
   434      * @param  string $value
       
   435      * @return void
       
   436      */
       
   437     protected function _writeString($value)
       
   438     {
       
   439         if ( ($id=$this->_searchMomo($value)) !== false ) {
       
   440             $this->_writeGet($id);
       
   441             return;
       
   442         }
       
   443 
       
   444         if ($this->_binary) {
       
   445             $n = strlen($value);
       
   446             if ($n <= 0xff) {
       
   447                 // self.write(SHORT_BINSTRING + chr(n) + obj)
       
   448                 $this->_pickle .= self::OP_SHORT_BINSTRING . chr($n) . $value;
       
   449             } else {
       
   450                 // self.write(BINSTRING + pack("<i", n) + obj)
       
   451                 $binLen = pack('l', $n);
       
   452                 if (self::$_isLittleEndian === false) {
       
   453                     $binLen = strrev($binLen);
       
   454                 }
       
   455                 $this->_pickle .= self::OP_BINSTRING . $binLen . $value;
       
   456             }
       
   457         } else {
       
   458             $this->_pickle .= self::OP_STRING . $this->_quoteString($value) . "\r\n";
       
   459         }
       
   460 
       
   461         $this->_momorize($value);
       
   462     }
       
   463 
       
   464     /**
       
   465      * Write an associative array value as dictionary
       
   466      *
       
   467      * @param  array $value
       
   468      * @return void
       
   469      */
       
   470     protected function _writeArrayDict(array $value)
       
   471     {
       
   472         if (($id=$this->_searchMomo($value)) !== false) {
       
   473             $this->_writeGet($id);;
       
   474             return;
       
   475         }
       
   476 
       
   477         $this->_pickle .= self::OP_MARK . self::OP_DICT;
       
   478         $this->_momorize($value);
       
   479 
       
   480         foreach ($value as $k => $v) {
       
   481             $this->_pickle .= $this->_write($k)
       
   482                             . $this->_write($v)
       
   483                             . self::OP_SETITEM;
       
   484         }
       
   485     }
       
   486 
       
   487     /**
       
   488      * Write a simple array value as list
       
   489      *
       
   490      * @param  array $value
       
   491      * @return void
       
   492      */
       
   493     protected function _writeArrayList(array $value)
       
   494     {
       
   495         if (($id = $this->_searchMomo($value)) !== false) {
       
   496             $this->_writeGet($id);
       
   497             return;
       
   498         }
       
   499 
       
   500         $this->_pickle .= self::OP_MARK . self::OP_LIST;
       
   501         $this->_momorize($value);
       
   502 
       
   503         foreach ($value as $k => $v) {
       
   504             $this->_pickle .= $this->_write($v) . self::OP_APPEND;
       
   505         }
       
   506     }
       
   507 
       
   508     /**
       
   509      * Write an object as an dictionary
       
   510      *
       
   511      * @param  object $value
       
   512      * @return void
       
   513      */
       
   514     protected function _writeObject($value)
       
   515     {
       
   516         // can't serialize php objects to python objects yet
       
   517         $this->_writeArrayDict(get_object_vars($value));
       
   518     }
       
   519 
       
   520     /**
       
   521      * Write stop
       
   522      *
       
   523      * @return void
       
   524      */
       
   525     protected function _writeStop()
       
   526     {
       
   527         $this->_pickle .= self::OP_STOP;
       
   528     }
       
   529 
       
   530     /* serialize helper */
       
   531 
       
   532     /**
       
   533      * Add a value to the memo and write the id
       
   534      *
       
   535      * @param mixed $value
       
   536      * @return void
       
   537      */
       
   538     protected function _momorize($value)
       
   539     {
       
   540         $id = count($this->_memo);
       
   541         $this->_memo[$id] = $value;
       
   542         $this->_writePut($id);
       
   543     }
       
   544 
       
   545     /**
       
   546      * Search a value in the meno and return  the id
       
   547      *
       
   548      * @param  mixed $value
       
   549      * @return int|false The id or false
       
   550      */
       
   551     protected function _searchMomo($value)
       
   552     {
       
   553         return array_search($value, $this->_memo, true);
       
   554     }
       
   555 
       
   556     /**
       
   557      * Is an array associative?
       
   558      *
       
   559      * @param  array $value
       
   560      * @return boolean
       
   561      */
       
   562     protected function _isArrayAssoc(array $value)
       
   563     {
       
   564         return array_diff_key($value, array_keys(array_keys($value)));
       
   565     }
       
   566 
       
   567     /**
       
   568      * Quote/Escape a string
       
   569      *
       
   570      * @param  string $str
       
   571      * @return string quoted string
       
   572      */
       
   573     protected function _quoteString($str)
       
   574     {
       
   575         $quoteArr = self::$_quoteString;
       
   576 
       
   577         if (($cntSingleQuote = substr_count($str, "'"))
       
   578             && ($cntDoubleQuote = substr_count($str, '"'))
       
   579             && ($cntSingleQuote < $cntDoubleQuote)
       
   580         ) {
       
   581             $quoteArr['"'] = '\\"';
       
   582             $enclosure     = '"';
       
   583         } else {
       
   584             $quoteArr["'"] = "\\'";
       
   585             $enclosure     = "'";
       
   586         }
       
   587 
       
   588         return $enclosure . strtr($str, $quoteArr) . $enclosure;
       
   589     }
       
   590 
       
   591     /* unserialize */
       
   592 
       
   593     /**
       
   594      * Unserialize from Python Pickle format to PHP
       
   595      *
       
   596      * @param  string $pickle
       
   597      * @param  array $opts
       
   598      * @return mixed
       
   599      * @throws Zend_Serializer_Exception on invalid Pickle string
       
   600      */
       
   601     public function unserialize($pickle, array $opts = array())
       
   602     {
       
   603         // init process vars
       
   604         $this->_pos       = 0;
       
   605         $this->_pickle    = $pickle;
       
   606         $this->_pickleLen = strlen($this->_pickle);
       
   607         $this->_memo      = array();
       
   608         $this->_stack     = array();
       
   609 
       
   610         // read pickle string
       
   611         while (($op=$this->_read(1)) !== self::OP_STOP) {
       
   612             $this->_load($op);
       
   613         }
       
   614 
       
   615         if (!count($this->_stack)) {
       
   616             require_once 'Zend/Serializer/Exception.php';
       
   617             throw new Zend_Serializer_Exception('No data found');
       
   618         }
       
   619 
       
   620         $ret = array_pop($this->_stack);
       
   621 
       
   622         // clear process vars
       
   623         $this->_pos       = 0;
       
   624         $this->_pickle    = '';
       
   625         $this->_pickleLen = 0;
       
   626         $this->_memo      = array();
       
   627         $this->_stack     = array();
       
   628 
       
   629         return $ret;
       
   630     }
       
   631 
       
   632     /**
       
   633      * Load a pickle opcode
       
   634      *
       
   635      * @param  string $op
       
   636      * @return void
       
   637      * @throws Zend_Serializer_Exception on invalid opcode
       
   638      */
       
   639     protected function _load($op)
       
   640     {
       
   641         switch ($op) {
       
   642             case self::OP_PUT:
       
   643                 $this->_loadPut();
       
   644                 break;
       
   645             case self::OP_BINPUT:
       
   646                 $this->_loadBinPut();
       
   647                 break;
       
   648             case self::OP_LONG_BINPUT:
       
   649                 $this->_loadLongBinPut();
       
   650                 break;
       
   651             case self::OP_GET:
       
   652                 $this->_loadGet();
       
   653                 break;
       
   654             case self::OP_BINGET:
       
   655                 $this->_loadBinGet();
       
   656                 break;
       
   657             case self::OP_LONG_BINGET:
       
   658                 $this->_loadLongBinGet();
       
   659                 break;
       
   660             case self::OP_NONE:
       
   661                 $this->_loadNone();
       
   662                 break;
       
   663             case self::OP_NEWTRUE:
       
   664                 $this->_loadNewTrue();
       
   665                 break;
       
   666             case self::OP_NEWFALSE:
       
   667                 $this->_loadNewFalse();
       
   668                 break;
       
   669             case self::OP_INT:
       
   670                 $this->_loadInt();
       
   671                 break;
       
   672             case self::OP_BININT:
       
   673                 $this->_loadBinInt();
       
   674                 break;
       
   675             case self::OP_BININT1:
       
   676                 $this->_loadBinInt1();
       
   677                 break;
       
   678             case self::OP_BININT2:
       
   679                 $this->_loadBinInt2();
       
   680                 break;
       
   681             case self::OP_LONG:
       
   682                 $this->_loadLong();
       
   683                 break;
       
   684             case self::OP_LONG1:
       
   685                 $this->_loadLong1();
       
   686                 break;
       
   687             case self::OP_LONG4:
       
   688                 $this->_loadLong4();
       
   689                 break;
       
   690             case self::OP_FLOAT:
       
   691                 $this->_loadFloat();
       
   692                 break;
       
   693             case self::OP_BINFLOAT:
       
   694                 $this->_loadBinFloat();
       
   695                 break;
       
   696             case self::OP_STRING:
       
   697                 $this->_loadString();
       
   698                 break;
       
   699             case self::OP_BINSTRING:
       
   700                 $this->_loadBinString();
       
   701                 break;
       
   702             case self::OP_SHORT_BINSTRING:
       
   703                 $this->_loadShortBinString();
       
   704                 break;
       
   705             case self::OP_BINBYTES:
       
   706                 $this->_loadBinBytes();
       
   707                 break;
       
   708             case self::OP_SHORT_BINBYTES:
       
   709                 $this->_loadShortBinBytes();
       
   710                 break;
       
   711             case self::OP_UNICODE:
       
   712                 $this->_loadUnicode();
       
   713                 break;
       
   714             case self::OP_BINUNICODE:
       
   715                 $this->_loadBinUnicode();
       
   716                 break;
       
   717             case self::OP_MARK:
       
   718                 $this->_loadMark();
       
   719                 break;
       
   720             case self::OP_LIST:
       
   721                 $this->_loadList();
       
   722                 break;
       
   723             case self::OP_EMPTY_LIST:
       
   724                 $this->_loadEmptyList();
       
   725                 break;
       
   726             case self::OP_APPEND:
       
   727                 $this->_loadAppend();
       
   728                 break;
       
   729             case self::OP_APPENDS:
       
   730                 $this->_loadAppends();
       
   731                 break;
       
   732             case self::OP_DICT:
       
   733                 $this->_loadDict();
       
   734                 break;
       
   735             case self::OP_EMPTY_DICT:
       
   736                 $this->_loadEmptyDict();
       
   737                 break;
       
   738             case self::OP_SETITEM:
       
   739                 $this->_loadSetItem();
       
   740                 break;
       
   741             case self::OP_SETITEMS:
       
   742                 $this->_loadSetItems();
       
   743                 break;
       
   744             case self::OP_TUPLE:
       
   745                 $this->_loadTuple();
       
   746                 break;
       
   747             case self::OP_TUPLE1:
       
   748                 $this->_loadTuple1();
       
   749                 break;
       
   750             case self::OP_TUPLE2:
       
   751                 $this->_loadTuple2();
       
   752                 break;
       
   753             case self::OP_TUPLE3:
       
   754                 $this->_loadTuple3();
       
   755                 break;
       
   756             case self::OP_PROTO:
       
   757                 $this->_loadProto();
       
   758                 break;
       
   759             default:
       
   760                 require_once 'Zend/Serializer/Exception.php';
       
   761                 throw new Zend_Serializer_Exception('Invalid or unknown opcode "'.$op.'"');
       
   762         }
       
   763     }
       
   764 
       
   765     /**
       
   766      * Load a PUT opcode
       
   767      *
       
   768      * @return void
       
   769      * @throws Zend_Serializer_Exception on missing stack
       
   770      */
       
   771     protected function _loadPut()
       
   772     {
       
   773         $id = (int)$this->_readline();
       
   774 
       
   775         $lastStack = count($this->_stack)-1;
       
   776         if (!isset($this->_stack[$lastStack])) {
       
   777             require_once 'Zend/Serializer/Exception.php';
       
   778             throw new Zend_Serializer_Exception('No stack exist');
       
   779         }
       
   780         $this->_memo[$id] = & $this->_stack[$lastStack];
       
   781     }
       
   782 
       
   783     /**
       
   784      * Load a binary PUT
       
   785      *
       
   786      * @return void
       
   787      * @throws Zend_Serializer_Exception on missing stack
       
   788      */
       
   789     protected function _loadBinPut()
       
   790     {
       
   791         $id = ord($this->_read(1));
       
   792 
       
   793         $lastStack = count($this->_stack)-1;
       
   794         if (!isset($this->_stack[$lastStack])) {
       
   795             require_once 'Zend/Serializer/Exception.php';
       
   796             throw new Zend_Serializer_Exception('No stack exist');
       
   797         }
       
   798         $this->_memo[$id] = & $this->_stack[$lastStack];
       
   799     }
       
   800 
       
   801     /**
       
   802      * Load a long binary PUT
       
   803      *
       
   804      * @return void
       
   805      * @throws Zend_Serializer_Exception on missing stack
       
   806      */
       
   807     protected function _loadLongBinPut()
       
   808     {
       
   809         $bin = $this->_read(4);
       
   810         if (self::$_isLittleEndian === false) {
       
   811             $bin = strrev($bin);
       
   812         }
       
   813         list(, $id) = unpack('l', $bin);
       
   814 
       
   815         $lastStack = count($this->_stack)-1;
       
   816         if (!isset($this->_stack[$lastStack])) {
       
   817             require_once 'Zend/Serializer/Exception.php';
       
   818             throw new Zend_Serializer_Exception('No stack exist');
       
   819         }
       
   820         $this->_memo[$id] = & $this->_stack[$lastStack];
       
   821     }
       
   822 
       
   823     /**
       
   824      * Load a GET operation
       
   825      *
       
   826      * @return void
       
   827      * @throws Zend_Serializer_Exception on missing GET identifier
       
   828      */
       
   829     protected function _loadGet()
       
   830     {
       
   831         $id = (int)$this->_readline();
       
   832 
       
   833         if (!array_key_exists($id, $this->_memo)) {
       
   834             require_once 'Zend/Serializer/Exception.php';
       
   835             throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
       
   836         }
       
   837         $this->_stack[] = & $this->_memo[$id];
       
   838     }
       
   839 
       
   840     /**
       
   841      * Load a binary GET operation
       
   842      *
       
   843      * @return void
       
   844      * @throws Zend_Serializer_Exception on missing GET identifier
       
   845      */
       
   846     protected function _loadBinGet()
       
   847     {
       
   848         $id = ord($this->_read(1));
       
   849 
       
   850         if (!array_key_exists($id, $this->_memo)) {
       
   851             require_once 'Zend/Serializer/Exception.php';
       
   852             throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
       
   853         }
       
   854         $this->_stack[] = & $this->_memo[$id];
       
   855     }
       
   856 
       
   857     /**
       
   858      * Load a long binary GET operation
       
   859      *
       
   860      * @return void
       
   861      * @throws Zend_Serializer_Exception on missing GET identifier
       
   862      */
       
   863     protected function _loadLongBinGet()
       
   864     {
       
   865         $bin = $this->_read(4);
       
   866         if (self::$_isLittleEndian === false) {
       
   867             $bin = strrev($bin);
       
   868         }
       
   869         list(, $id) = unpack('l', $bin);
       
   870 
       
   871         if (!array_key_exists($id, $this->_memo)) {
       
   872             require_once 'Zend/Serializer/Exception.php';
       
   873             throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
       
   874         }
       
   875         $this->_stack[] = & $this->_memo[$id];
       
   876     }
       
   877 
       
   878     /**
       
   879      * Load a NONE operator
       
   880      *
       
   881      * @return void
       
   882      */
       
   883     protected function _loadNone()
       
   884     {
       
   885         $this->_stack[] = null;
       
   886     }
       
   887 
       
   888     /**
       
   889      * Load a boolean TRUE operator
       
   890      *
       
   891      * @return void
       
   892      */
       
   893     protected function _loadNewTrue()
       
   894     {
       
   895         $this->_stack[] = true;
       
   896     }
       
   897 
       
   898     /**
       
   899      * Load a boolean FALSE operator
       
   900      *
       
   901      * @return void
       
   902      */
       
   903     protected function _loadNewFalse()
       
   904     {
       
   905         $this->_stack[] = false;
       
   906     }
       
   907 
       
   908     /**
       
   909      * Load an integer operator
       
   910      *
       
   911      * @return void
       
   912      */
       
   913     protected function _loadInt()
       
   914     {
       
   915         $line = $this->_readline();
       
   916         if ($line === '01') {
       
   917             $this->_stack[] = true;
       
   918         } elseif ($line === '00') {
       
   919             $this->_stack[] = false;
       
   920         } else {
       
   921             $this->_stack[] = (int)$line;
       
   922         }
       
   923     }
       
   924 
       
   925     /**
       
   926      * Load a binary integer operator
       
   927      *
       
   928      * @return void
       
   929      */
       
   930     protected function _loadBinInt()
       
   931     {
       
   932         $bin = $this->_read(4);
       
   933         if (self::$_isLittleEndian === false) {
       
   934             $bin = strrev($bin);
       
   935         }
       
   936         list(, $int)    = unpack('l', $bin);
       
   937         $this->_stack[] = $int;
       
   938     }
       
   939 
       
   940     /**
       
   941      * Load the first byte of a binary integer
       
   942      *
       
   943      * @return void
       
   944      */
       
   945     protected function _loadBinInt1()
       
   946     {
       
   947         $this->_stack[] = ord($this->_read(1));
       
   948     }
       
   949 
       
   950     /**
       
   951      * Load the second byte of a binary integer
       
   952      *
       
   953      * @return void
       
   954      */
       
   955     protected function _loadBinInt2()
       
   956     {
       
   957         $bin = $this->_read(2);
       
   958         list(, $int)    = unpack('v', $bin);
       
   959         $this->_stack[] = $int;
       
   960     }
       
   961 
       
   962     /**
       
   963      * Load a long (float) operator
       
   964      *
       
   965      * @return void
       
   966      */
       
   967     protected function _loadLong()
       
   968     {
       
   969         $data = rtrim($this->_readline(), 'L');
       
   970         if ($data === '') {
       
   971             $this->_stack[] = 0;
       
   972         } else {
       
   973             $this->_stack[] = $data;
       
   974         }
       
   975     }
       
   976 
       
   977     /**
       
   978      * Load a one byte long integer
       
   979      *
       
   980      * @return void
       
   981      */
       
   982     protected function _loadLong1()
       
   983     {
       
   984         $n    = ord($this->_read(1));
       
   985         $data = $this->_read($n);
       
   986         $this->_stack[] = $this->_decodeBinLong($data);
       
   987     }
       
   988 
       
   989     /**
       
   990      * Load a 4 byte long integer
       
   991      *
       
   992      * @return void
       
   993      */
       
   994     protected function _loadLong4()
       
   995     {
       
   996         $nBin = $this->_read(4);
       
   997         if (self::$_isLittleEndian === false) {
       
   998             $nBin = strrev($$nBin);
       
   999         }
       
  1000         list(, $n) = unpack('l', $nBin);
       
  1001         $data = $this->_read($n);
       
  1002 
       
  1003         $this->_stack[] = $this->_decodeBinLong($data);
       
  1004     }
       
  1005 
       
  1006     /**
       
  1007      * Load a float value
       
  1008      *
       
  1009      * @return void
       
  1010      */
       
  1011     protected function _loadFloat()
       
  1012     {
       
  1013         $float = (float)$this->_readline();
       
  1014         $this->_stack[] = $float;
       
  1015     }
       
  1016 
       
  1017     /**
       
  1018      * Load a binary float value
       
  1019      *
       
  1020      * @return void
       
  1021      */
       
  1022     protected function _loadBinFloat()
       
  1023     {
       
  1024         $bin = $this->_read(8);
       
  1025         if (self::$_isLittleEndian === true) {
       
  1026             $bin = strrev($bin);
       
  1027         }
       
  1028         list(, $float)  = unpack('d', $bin);
       
  1029         $this->_stack[] = $float;
       
  1030     }
       
  1031 
       
  1032     /**
       
  1033      * Load a string
       
  1034      *
       
  1035      * @return void
       
  1036      */
       
  1037     protected function _loadString()
       
  1038     {
       
  1039         $this->_stack[] = $this->_unquoteString((string)$this->_readline());
       
  1040     }
       
  1041 
       
  1042     /**
       
  1043      * Load a binary string
       
  1044      *
       
  1045      * @return void
       
  1046      */
       
  1047     protected function _loadBinString()
       
  1048     {
       
  1049         $bin = $this->_read(4);
       
  1050         if (!self::$_isLittleEndian) {
       
  1051             $bin = strrev($bin);
       
  1052         }
       
  1053         list(, $len)    = unpack('l', $bin);
       
  1054         $this->_stack[] = (string)$this->_read($len);
       
  1055     }
       
  1056 
       
  1057     /**
       
  1058      * Load a short binary string
       
  1059      *
       
  1060      * @return void
       
  1061      */
       
  1062     protected function _loadShortBinString()
       
  1063     {
       
  1064         $len            = ord($this->_read(1));
       
  1065         $this->_stack[] = (string)$this->_read($len);
       
  1066     }
       
  1067 
       
  1068     /**
       
  1069      * Load arbitrary binary bytes
       
  1070      *
       
  1071      * @return void
       
  1072      */
       
  1073     protected function _loadBinBytes()
       
  1074     {
       
  1075         // read byte length
       
  1076         $nBin = $this->_read(4);
       
  1077         if (self::$_isLittleEndian === false) {
       
  1078             $nBin = strrev($$nBin);
       
  1079         }
       
  1080         list(, $n)      = unpack('l', $nBin);
       
  1081         $this->_stack[] = $this->_read($n);
       
  1082     }
       
  1083 
       
  1084     /**
       
  1085      * Load a single binary byte
       
  1086      *
       
  1087      * @return void
       
  1088      */
       
  1089     protected function _loadShortBinBytes()
       
  1090     {
       
  1091         $n              = ord($this->_read(1));
       
  1092         $this->_stack[] = $this->_read($n);
       
  1093     }
       
  1094 
       
  1095     /**
       
  1096      * Load a unicode string
       
  1097      *
       
  1098      * @return void
       
  1099      */
       
  1100     protected function _loadUnicode()
       
  1101     {
       
  1102         $data    = $this->_readline();
       
  1103         $pattern = '/\\\\u([a-fA-F0-9]{4})/u'; // \uXXXX
       
  1104         $data    = preg_replace_callback($pattern, array($this, '_convertMatchingUnicodeSequence2Utf8'), $data);
       
  1105 
       
  1106         if (self::$_isPhp6) {
       
  1107             $data = unicode_decode($data, 'UTF-8');
       
  1108         }
       
  1109 
       
  1110         $this->_stack[] = $data;
       
  1111     }
       
  1112 
       
  1113     /**
       
  1114      * Convert a unicode sequence to UTF-8
       
  1115      *
       
  1116      * @param  array $match
       
  1117      * @return string
       
  1118      */
       
  1119     protected function _convertMatchingUnicodeSequence2Utf8(array $match)
       
  1120     {
       
  1121         return $this->_hex2Utf8($match[1]);
       
  1122     }
       
  1123 
       
  1124     /**
       
  1125      * Convert a hex string to a UTF-8 string
       
  1126      *
       
  1127      * @param  string $sequence
       
  1128      * @return string
       
  1129      * @throws Zend_Serializer_Exception on unmatched unicode sequence
       
  1130      */
       
  1131     protected function _hex2Utf8($hex)
       
  1132     {
       
  1133         $uniCode = hexdec($hex);
       
  1134 
       
  1135         if ($uniCode < 0x80) { // 1Byte
       
  1136             $utf8Char = chr($uniCode);
       
  1137 
       
  1138         } elseif ($uniCode < 0x800) { // 2Byte
       
  1139             $utf8Char = chr(0xC0 | $uniCode >> 6)
       
  1140                       . chr(0x80 | $uniCode & 0x3F);
       
  1141 
       
  1142         } elseif ($uniCode < 0x10000) { // 3Byte
       
  1143             $utf8Char = chr(0xE0 | $uniCode >> 12)
       
  1144                       . chr(0x80 | $uniCode >> 6 & 0x3F)
       
  1145                       . chr(0x80 | $uniCode & 0x3F);
       
  1146 
       
  1147         } elseif ($uniCode < 0x110000) { // 4Byte
       
  1148             $utf8Char  = chr(0xF0 | $uniCode >> 18)
       
  1149                        . chr(0x80 | $uniCode >> 12 & 0x3F)
       
  1150                        . chr(0x80 | $uniCode >> 6 & 0x3F)
       
  1151                        . chr(0x80 | $uniCode & 0x3F);
       
  1152         } else {
       
  1153             require_once 'Zend/Serializer/Exception.php';
       
  1154             throw new Zend_Serializer_Exception('Unsupported unicode character found "' . dechex($uniCode) . '"');
       
  1155         }
       
  1156 
       
  1157         return $utf8Char;
       
  1158     }
       
  1159 
       
  1160     /**
       
  1161      * Load binary unicode sequence
       
  1162      *
       
  1163      * @return void
       
  1164      */
       
  1165     protected function _loadBinUnicode()
       
  1166     {
       
  1167         // read byte length
       
  1168         $n = $this->_read(4);
       
  1169         if (self::$_isLittleEndian === false) {
       
  1170             $n = strrev($n);
       
  1171         }
       
  1172         list(, $n) = unpack('l', $n);
       
  1173         $data      = $this->_read($n);
       
  1174 
       
  1175         if (self::$_isPhp6) {
       
  1176             $data = unicode_decode($data, 'UTF-8');
       
  1177         }
       
  1178 
       
  1179         $this->_stack[] = $data;
       
  1180     }
       
  1181 
       
  1182     /**
       
  1183      * Load a marker sequence
       
  1184      *
       
  1185      * @return void
       
  1186      */
       
  1187     protected function _loadMark()
       
  1188     {
       
  1189         $this->_stack[] = $this->_marker;
       
  1190     }
       
  1191 
       
  1192     /**
       
  1193      * Load an array (list)
       
  1194      *
       
  1195      * @return void
       
  1196      */
       
  1197     protected function _loadList()
       
  1198     {
       
  1199         $k = $this->_lastMarker();
       
  1200         $this->_stack[$k] = array();
       
  1201 
       
  1202         // remove all elements after marker
       
  1203         $max = count($this->_stack);
       
  1204         for ($i = $k+1, $max; $i < $max; $i++) {
       
  1205             unset($this->_stack[$i]);
       
  1206         }
       
  1207     }
       
  1208 
       
  1209     /**
       
  1210      * Load an append (to list) sequence
       
  1211      *
       
  1212      * @return void
       
  1213      */
       
  1214     protected function _loadAppend()
       
  1215     {
       
  1216         $value  =  array_pop($this->_stack);
       
  1217         $list   =& $this->_stack[count($this->_stack)-1];
       
  1218         $list[] =  $value;
       
  1219     }
       
  1220 
       
  1221     /**
       
  1222      * Load an empty list sequence
       
  1223      *
       
  1224      * @return void
       
  1225      */
       
  1226     protected function _loadEmptyList()
       
  1227     {
       
  1228         $this->_stack[] = array();
       
  1229     }
       
  1230 
       
  1231     /**
       
  1232      * Load multiple append (to list) sequences at once
       
  1233      *
       
  1234      * @return void
       
  1235      */
       
  1236     protected function _loadAppends()
       
  1237     {
       
  1238         $k    =  $this->_lastMarker();
       
  1239         $list =& $this->_stack[$k - 1];
       
  1240         $max  =  count($this->_stack);
       
  1241         for ($i = $k + 1; $i < $max; $i++) {
       
  1242             $list[] = $this->_stack[$i];
       
  1243             unset($this->_stack[$i]);
       
  1244         }
       
  1245         unset($this->_stack[$k]);
       
  1246     }
       
  1247 
       
  1248     /**
       
  1249      * Load an associative array (Python dictionary)
       
  1250      *
       
  1251      * @return void
       
  1252      */
       
  1253     protected function _loadDict()
       
  1254     {
       
  1255         $k = $this->_lastMarker();
       
  1256         $this->_stack[$k] = array();
       
  1257 
       
  1258         // remove all elements after marker
       
  1259         $max = count($this->_stack);
       
  1260         for($i = $k + 1; $i < $max; $i++) {
       
  1261             unset($this->_stack[$i]);
       
  1262         }
       
  1263     }
       
  1264 
       
  1265     /**
       
  1266      * Load an item from a set
       
  1267      *
       
  1268      * @return void
       
  1269      */
       
  1270     protected function _loadSetItem()
       
  1271     {
       
  1272         $value =  array_pop($this->_stack);
       
  1273         $key   =  array_pop($this->_stack);
       
  1274         $dict  =& $this->_stack[count($this->_stack) - 1];
       
  1275         $dict[$key] = $value;
       
  1276     }
       
  1277 
       
  1278     /**
       
  1279      * Load an empty dictionary
       
  1280      *
       
  1281      * @return void
       
  1282      */
       
  1283     protected function _loadEmptyDict()
       
  1284     {
       
  1285         $this->_stack[] = array();
       
  1286     }
       
  1287 
       
  1288     /**
       
  1289      * Load set items
       
  1290      *
       
  1291      * @return void
       
  1292      */
       
  1293     protected function _loadSetItems()
       
  1294     {
       
  1295         $k    =  $this->_lastMarker();
       
  1296         $dict =& $this->_stack[$k - 1];
       
  1297         $max  =  count($this->_stack);
       
  1298         for ($i = $k + 1; $i < $max; $i += 2) {
       
  1299             $key        = $this->_stack[$i];
       
  1300             $value      = $this->_stack[$i + 1];
       
  1301             $dict[$key] = $value;
       
  1302             unset($this->_stack[$i], $this->_stack[$i+1]);
       
  1303         }
       
  1304         unset($this->_stack[$k]);
       
  1305     }
       
  1306 
       
  1307     /**
       
  1308      * Load a tuple
       
  1309      *
       
  1310      * @return void
       
  1311      */
       
  1312     protected function _loadTuple()
       
  1313     {
       
  1314         $k                =  $this->_lastMarker();
       
  1315         $this->_stack[$k] =  array();
       
  1316         $tuple            =& $this->_stack[$k];
       
  1317         $max              =  count($this->_stack);
       
  1318         for($i = $k + 1; $i < $max; $i++) {
       
  1319             $tuple[] = $this->_stack[$i];
       
  1320             unset($this->_stack[$i]);
       
  1321         }
       
  1322     }
       
  1323 
       
  1324     /**
       
  1325      * Load single item tuple
       
  1326      *
       
  1327      * @return void
       
  1328      */
       
  1329     protected function _loadTuple1()
       
  1330     {
       
  1331         $value1 = array_pop($this->_stack);
       
  1332         $this->_stack[] = array($value1);
       
  1333     }
       
  1334 
       
  1335     /**
       
  1336      * Load two item tuple
       
  1337      *
       
  1338      * @return void
       
  1339      */
       
  1340     protected function _loadTuple2()
       
  1341     {
       
  1342         $value2 = array_pop($this->_stack);
       
  1343         $value1 = array_pop($this->_stack);
       
  1344         $this->_stack[] = array($value1, $value2);
       
  1345     }
       
  1346 
       
  1347     /**
       
  1348      * Load three item tuple
       
  1349      *
       
  1350      * @return void
       
  1351      */
       
  1352     protected function _loadTuple3() {
       
  1353         $value3 = array_pop($this->_stack);
       
  1354         $value2 = array_pop($this->_stack);
       
  1355         $value1 = array_pop($this->_stack);
       
  1356         $this->_stack[] = array($value1, $value2, $value3);
       
  1357     }
       
  1358 
       
  1359     /**
       
  1360      * Load a proto value
       
  1361      *
       
  1362      * @return void
       
  1363      * @throws Zend_Serializer_Exception if Pickle version does not support this feature
       
  1364      */
       
  1365     protected function _loadProto()
       
  1366     {
       
  1367         $proto = ord($this->_read(1));
       
  1368         if ($proto < 2 || $proto > 3) {
       
  1369             require_once 'Zend/Serializer/Exception.php';
       
  1370             throw new Zend_Serializer_Exception('Invalid protocol version detected');
       
  1371         }
       
  1372         $this->_protocol = $proto;
       
  1373     }
       
  1374 
       
  1375     /* unserialize helper */
       
  1376 
       
  1377     /**
       
  1378      * Read a segment of the pickle
       
  1379      *
       
  1380      * @param  mixed $len
       
  1381      * @return string
       
  1382      * @throws Zend_Serializer_Exception if position matches end of data
       
  1383      */
       
  1384     protected function _read($len)
       
  1385     {
       
  1386         if (($this->_pos + $len) > $this->_pickleLen) {
       
  1387             require_once 'Zend/Serializer/Exception.php';
       
  1388             throw new Zend_Serializer_Exception('End of data');
       
  1389         }
       
  1390 
       
  1391         $this->_pos+= $len;
       
  1392         return substr($this->_pickle, ($this->_pos - $len), $len);
       
  1393     }
       
  1394 
       
  1395     /**
       
  1396      * Read a line of the pickle at once
       
  1397      *
       
  1398      * @return string
       
  1399      * @throws Zend_Serializer_Exception if no EOL character found
       
  1400      */
       
  1401     protected function _readline()
       
  1402     {
       
  1403         $eolLen = 2;
       
  1404         $eolPos = strpos($this->_pickle, "\r\n", $this->_pos);
       
  1405         if ($eolPos === false) {
       
  1406             $eolPos = strpos($this->_pickle, "\n", $this->_pos);
       
  1407             $eolLen = 1;
       
  1408         }
       
  1409 
       
  1410         if ($eolPos === false) {
       
  1411             require_once 'Zend/Serializer/Exception.php';
       
  1412             throw new Zend_Serializer_Exception('No new line found');
       
  1413         }
       
  1414         $ret        = substr($this->_pickle, $this->_pos, $eolPos-$this->_pos);
       
  1415         $this->_pos = $eolPos + $eolLen;
       
  1416 
       
  1417         return $ret;
       
  1418     }
       
  1419 
       
  1420     /**
       
  1421      * Unquote/Unescape a quoted string
       
  1422      *
       
  1423      * @param  string $str quoted string
       
  1424      * @return string unquoted string
       
  1425      */
       
  1426     protected function _unquoteString($str)
       
  1427     {
       
  1428         $quoteArr = array_flip(self::$_quoteString);
       
  1429 
       
  1430         if ($str[0] == '"') {
       
  1431             $quoteArr['\\"'] = '"';
       
  1432         } else {
       
  1433             $quoteArr["\\'"] = "'";
       
  1434         }
       
  1435 
       
  1436         return strtr(substr(trim($str), 1, -1), $quoteArr);
       
  1437     }
       
  1438 
       
  1439     /**
       
  1440      * Return last marker position in stack
       
  1441      *
       
  1442      * @return int
       
  1443      */
       
  1444     protected function _lastMarker()
       
  1445     {
       
  1446         for ($k = count($this->_stack)-1; $k >= 0; $k -= 1) {
       
  1447             if ($this->_stack[$k] === $this->_marker) {
       
  1448                 break;
       
  1449             }
       
  1450         }
       
  1451         return $k;
       
  1452     }
       
  1453 
       
  1454     /**
       
  1455      * Decode a binary long sequence
       
  1456      *
       
  1457      * @param  string $data
       
  1458      * @return int|float|string
       
  1459      */
       
  1460     protected function _decodeBinLong($data)
       
  1461     {
       
  1462         $nbytes = strlen($data);
       
  1463 
       
  1464         if ($nbytes == 0) {
       
  1465             return 0;
       
  1466         }
       
  1467 
       
  1468         $long = 0;
       
  1469 
       
  1470         if ($nbytes > 7) {
       
  1471             if (!extension_loaded('bcmath')) {
       
  1472                 return INF;
       
  1473             }
       
  1474 
       
  1475             for ($i=0; $i<$nbytes; $i++) {
       
  1476                 $long = bcadd($long, bcmul(ord($data[$i]), bcpow(256, $i, 0)));
       
  1477             }
       
  1478             if (0x80 <= ord($data[$nbytes-1])) {
       
  1479                 $long = bcsub($long, bcpow(2, $nbytes * 8));
       
  1480             }
       
  1481 
       
  1482         } else {
       
  1483             for ($i=0; $i<$nbytes; $i++) {
       
  1484                 $long+= ord($data[$i]) * pow(256, $i);
       
  1485             }
       
  1486             if (0x80 <= ord($data[$nbytes-1])) {
       
  1487                 $long-= pow(2, $nbytes * 8);
       
  1488                 // $long-= 1 << ($nbytes * 8);
       
  1489             }
       
  1490         }
       
  1491 
       
  1492         return $long;
       
  1493     }
       
  1494 }