File indexing completed on 2024-12-22 05:37:01

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Serializer
0017  * @subpackage Adapter
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /** @see Zend_Serializer_Adapter_AdapterAbstract */
0024 // require_once 'Zend/Serializer/Adapter/AdapterAbstract.php';
0025 
0026 /**
0027  * @link       http://www.python.org
0028  * @see        Phython3.1/Lib/pickle.py
0029  * @see        Phython3.1/Modules/_pickle.c
0030  * @link       http://pickle-js.googlecode.com
0031  * @category   Zend
0032  * @package    Zend_Serializer
0033  * @subpackage Adapter
0034  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0035  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0036  */
0037 class Zend_Serializer_Adapter_PythonPickle extends Zend_Serializer_Adapter_AdapterAbstract
0038 {
0039     /* Pickle opcodes. See pickletools.py for extensive docs.  The listing
0040        here is in kind-of alphabetical order of 1-character pickle code.
0041        pickletools groups them by purpose. */
0042     const OP_MARK            = '(';     // push special markobject on stack
0043     const OP_STOP            = '.';     // every pickle ends with STOP
0044     const OP_POP             = '0';     // discard topmost stack item
0045     const OP_POP_MARK        = '1';     // discard stack top through topmost markobject
0046     const OP_DUP             = '2';     // duplicate top stack item
0047     const OP_FLOAT           = 'F';     // push float object; decimal string argument
0048     const OP_INT             = 'I';     // push integer or bool; decimal string argument
0049     const OP_BININT          = 'J';     // push four-byte signed int
0050     const OP_BININT1         = 'K';     // push 1-byte unsigned int
0051     const OP_LONG            = 'L';     // push long; decimal string argument
0052     const OP_BININT2         = 'M';     // push 2-byte unsigned int
0053     const OP_NONE            = 'N';     // push None
0054     const OP_PERSID          = 'P';     // push persistent object; id is taken from string arg
0055     const OP_BINPERSID       = 'Q';     //  "       "         "  ;  "  "   "     "  stack
0056     const OP_REDUCE          = 'R';     // apply callable to argtuple, both on stack
0057     const OP_STRING          = 'S';     // push string; NL-terminated string argument
0058     const OP_BINSTRING       = 'T';     // push string; counted binary string argument
0059     const OP_SHORT_BINSTRING = 'U';     //  "     "   ;    "      "       "      " < 256 bytes
0060     const OP_UNICODE         = 'V';     // push Unicode string; raw-unicode-escaped'd argument
0061     const OP_BINUNICODE      = 'X';     //   "     "       "  ; counted UTF-8 string argument
0062     const OP_APPEND          = 'a';     // append stack top to list below it
0063     const OP_BUILD           = 'b';     // call __setstate__ or __dict__.update()
0064     const OP_GLOBAL          = 'c';     // push self.find_class(modname, name); 2 string args
0065     const OP_DICT            = 'd';     // build a dict from stack items
0066     const OP_EMPTY_DICT      = '}';     // push empty dict
0067     const OP_APPENDS         = 'e';     // extend list on stack by topmost stack slice
0068     const OP_GET             = 'g';     // push item from memo on stack; index is string arg
0069     const OP_BINGET          = 'h';     //   "    "    "    "   "   "  ;   "    " 1-byte arg
0070     const OP_INST            = 'i';     // build & push class instance
0071     const OP_LONG_BINGET     = 'j';     // push item from memo on stack; index is 4-byte arg
0072     const OP_LIST            = 'l';     // build list from topmost stack items
0073     const OP_EMPTY_LIST      = ']';     // push empty list
0074     const OP_OBJ             = 'o';     // build & push class instance
0075     const OP_PUT             = 'p';     // store stack top in memo; index is string arg
0076     const OP_BINPUT          = 'q';     //   "     "    "   "   " ;   "    " 1-byte arg
0077     const OP_LONG_BINPUT     = 'r';     //   "     "    "   "   " ;   "    " 4-byte arg
0078     const OP_SETITEM         = 's';     // add key+value pair to dict
0079     const OP_TUPLE           = 't';     // build tuple from topmost stack items
0080     const OP_EMPTY_TUPLE     = ')';     // push empty tuple
0081     const OP_SETITEMS        = 'u';     // modify dict by adding topmost key+value pairs
0082     const OP_BINFLOAT        = 'G';     // push float; arg is 8-byte float encoding
0083 
0084     /* Protocol 2 */
0085     const OP_PROTO           = "\x80";  // identify pickle protocol
0086     const OP_NEWOBJ          = "\x81";  // build object by applying cls.__new__ to argtuple
0087     const OP_EXT1            = "\x82";  // push object from extension registry; 1-byte index
0088     const OP_EXT2            = "\x83";  // ditto, but 2-byte index
0089     const OP_EXT4            = "\x84";  // ditto, but 4-byte index
0090     const OP_TUPLE1          = "\x85";  // build 1-tuple from stack top
0091     const OP_TUPLE2          = "\x86";  // build 2-tuple from two topmost stack items
0092     const OP_TUPLE3          = "\x87";  // build 3-tuple from three topmost stack items
0093     const OP_NEWTRUE         = "\x88";  // push True
0094     const OP_NEWFALSE        = "\x89";  // push False
0095     const OP_LONG1           = "\x8a";  // push long from < 256 bytes
0096     const OP_LONG4           = "\x8b";  // push really big long
0097 
0098     /* Protocol 3 (Python 3.x) */
0099     const OP_BINBYTES        = 'B';     // push bytes; counted binary string argument
0100     const OP_SHORT_BINBYTES  = 'C';     //  "     "   ;    "      "       "      " < 256 bytes
0101 
0102     /**
0103      * @var bool Whether or not the system is little-endian
0104      */
0105     protected static $_isLittleEndian = null;
0106 
0107     /**
0108      * @var array Strings representing quotes
0109      */
0110     protected static $_quoteString = array(
0111         '\\' => '\\\\',
0112         "\x00" => '\\x00', "\x01" => '\\x01', "\x02" => '\\x02', "\x03" => '\\x03',
0113         "\x04" => '\\x04', "\x05" => '\\x05', "\x06" => '\\x06', "\x07" => '\\x07',
0114         "\x08" => '\\x08', "\x09" => '\\t',   "\x0a" => '\\n',   "\x0b" => '\\x0b',
0115         "\x0c" => '\\x0c', "\x0d" => '\\r',   "\x0e" => '\\x0e', "\x0f" => '\\x0f',
0116         "\x10" => '\\x10', "\x11" => '\\x11', "\x12" => '\\x12', "\x13" => '\\x13',
0117         "\x14" => '\\x14', "\x15" => '\\x15', "\x16" => '\\x16', "\x17" => '\\x17',
0118         "\x18" => '\\x18', "\x19" => '\\x19', "\x1a" => '\\x1a', "\x1b" => '\\x1b',
0119         "\x1c" => '\\x1c', "\x1d" => '\\x1d', "\x1e" => '\\x1e', "\x1f" => '\\x1f',
0120         "\xff" => '\\xff'
0121     );
0122 
0123     /**
0124      * @var array Default options
0125      */
0126     protected $_options = array(
0127         'protocol'           => 0,
0128     );
0129 
0130     // process vars
0131     protected $_protocol           = 0;
0132     protected $_binary             = false;
0133     protected $_memo               = array();
0134     protected $_pickle             = '';
0135     protected $_pickleLen          = 0;
0136     protected $_pos                = 0;
0137     protected $_stack              = array();
0138     protected $_marker             = null;
0139 
0140     /**
0141      * Constructor
0142      *
0143      * @link Zend_Serializer_Adapter_AdapterAbstract::__construct()
0144      */
0145     public function __construct($opts=array())
0146     {
0147         parent::__construct($opts);
0148 
0149         // init
0150         if (self::$_isLittleEndian === null) {
0151             self::$_isLittleEndian = (pack('l', 1) === "\x01\x00\x00\x00");
0152         }
0153 
0154         $this->_marker = new stdClass();
0155     }
0156 
0157     /**
0158      * Set an option
0159      *
0160      * @link   Zend_Serializer_Adapter_AdapterAbstract::setOption()
0161      * @param  string $name
0162      * @param  mixed $value
0163      * @return Zend_Serializer_Adapter_PythonPickle
0164      */
0165     public function setOption($name, $value)
0166     {
0167         switch ($name) {
0168             case 'protocol':
0169                 $value = $this->_checkProtocolNumber($value);
0170                 break;
0171         }
0172 
0173         return parent::setOption($name, $value);
0174     }
0175 
0176     /**
0177      * Check and normalize pickle protocol number
0178      *
0179      * @param  int $number
0180      * @return int
0181      * @throws Zend_Serializer_Exception
0182      */
0183     protected function _checkProtocolNumber($number)
0184     {
0185         $int = (int) $number;
0186         if ($int < 0 || $int > 3) {
0187             // require_once 'Zend/Serializer/Exception.php';
0188             throw new Zend_Serializer_Exception('Invalid or unknown protocol version "'.$number.'"');
0189         }
0190         return $int;
0191     }
0192 
0193     /* serialize */
0194 
0195     /**
0196      * Serialize PHP to PythonPickle format
0197      *
0198      * @param  mixed $value
0199      * @param  array $opts
0200      * @return string
0201      */
0202     public function serialize($value, array $opts = array())
0203     {
0204         $opts = $opts + $this->_options;
0205 
0206         $this->_protocol = $this->_checkProtocolNumber($opts['protocol']);
0207         $this->_binary   = $this->_protocol != 0;
0208 
0209         // clear process vars before serializing
0210         $this->_memo   = array();
0211         $this->_pickle = '';
0212 
0213         // write
0214         if ($this->_protocol >= 2) {
0215             $this->_writeProto($this->_protocol);
0216         }
0217         $this->_write($value);
0218         $this->_writeStop();
0219 
0220         // clear process vars after serializing
0221         $this->_memo = array();
0222         $pickle = $this->_pickle;
0223         $this->_pickle = '';
0224 
0225         return $pickle;
0226     }
0227 
0228     /**
0229      * Write a value
0230      *
0231      * @param  mixed $value
0232      * @return void
0233      * @throws Zend_Serializer_Exception on invalid or unrecognized value type
0234      */
0235     protected function _write($value)
0236     {
0237         if ($value === null) {
0238             $this->_writeNull();
0239         } elseif ($value === true) {
0240             $this->_writeTrue();
0241         } elseif ($value === false) {
0242             $this->_writeFalse();
0243         } elseif (is_int($value)) {
0244             $this->_writeInt($value);
0245         } elseif (is_float($value)) {
0246             $this->_writeFloat($value);
0247         } elseif (is_string($value)) {
0248             // TODO: write unicode / binary
0249             $this->_writeString($value);
0250         } elseif (is_array($value)) {
0251             if ($this->_isArrayAssoc($value)) {
0252                 $this->_writeArrayDict($value);
0253             } else {
0254                 $this->_writeArrayList($value);
0255             }
0256         } elseif (is_object($value)) {
0257             $this->_writeObject($value);
0258         } else {
0259             // require_once 'Zend/Serializer/Exception.php';
0260             throw new Zend_Serializer_Exception(
0261                 'PHP-Type "'.gettype($value).'" isn\'t serializable with '.get_class($this)
0262             );
0263         }
0264     }
0265 
0266     /**
0267      * Write pickle protocol
0268      *
0269      * @param  int $protocol
0270      * @return void
0271      */
0272     protected function _writeProto($protocol)
0273     {
0274         $this->_pickle .= self::OP_PROTO . $protocol;
0275     }
0276 
0277     /**
0278      * Write a get
0279      *
0280      * @param  int $id Id of memo
0281      * @return void
0282      */
0283     protected function _writeGet($id)
0284     {
0285         if ($this->_binary) {
0286             if ($id <= 0xff) {
0287                 // BINGET + chr(i)
0288                 $this->_pickle .= self::OP_BINGET . chr($id);
0289             } else {
0290                 // LONG_BINGET + pack("<i", i)
0291                 $bin = pack('l', $id);
0292                 if (self::$_isLittleEndian === false) {
0293                     $bin = strrev($bin);
0294                 }
0295                 $this->_pickle .= self::OP_LONG_BINGET . $bin;
0296             }
0297         } else {
0298             $this->_pickle .= self::OP_GET . $id . "\r\n";
0299         }
0300     }
0301 
0302     /**
0303      * Write a put
0304      *
0305      * @param  int $id Id of memo
0306      * @return void
0307      */
0308     protected function _writePut($id)
0309     {
0310         if ($this->_binary) {
0311             if ($id <= 0xff) {
0312                 // BINPUT + chr(i)
0313                 $this->_pickle .= self::OP_BINPUT . chr($id);
0314             } else {
0315                 // LONG_BINPUT + pack("<i", i)
0316                 $bin = pack('l', $id);
0317                 if (self::$_isLittleEndian === false) {
0318                     $bin = strrev($bin);
0319                 }
0320                 $this->_pickle .= self::OP_LONG_BINPUT . $bin;
0321             }
0322         } else {
0323             $this->_pickle .= self::OP_PUT . $id . "\r\n";
0324         }
0325     }
0326 
0327     /**
0328      * Write a null as None
0329      *
0330      * @return void
0331      */
0332     protected function _writeNull()
0333     {
0334         $this->_pickle .= self::OP_NONE;
0335     }
0336 
0337     /**
0338      * Write a boolean true
0339      *
0340      * @return void
0341      */
0342     protected function _writeTrue()
0343     {
0344         if ($this->_protocol >= 2) {
0345             $this->_pickle .= self::OP_NEWTRUE;
0346         } else {
0347             $this->_pickle .= self::OP_INT . "01\r\n";
0348         }
0349     }
0350 
0351     /**
0352      * Write a boolean false
0353      *
0354      * @return void
0355      */
0356     protected function _writeFalse()
0357     {
0358         if ($this->_protocol >= 2) {
0359             $this->_pickle .= self::OP_NEWFALSE;
0360         } else {
0361             $this->_pickle .= self::OP_INT . "00\r\n";
0362         }
0363     }
0364 
0365     /**
0366      * Write an integer value
0367      *
0368      * @param  int $value
0369      * @return void
0370      */
0371     protected function _writeInt($value)
0372     {
0373         if ($this->_binary) {
0374             if ($value >= 0) {
0375                 if ($value <= 0xff) {
0376                     // self.write(BININT1 + chr(obj))
0377                     $this->_pickle .= self::OP_BININT1 . chr($value);
0378                 } elseif ($value <= 0xffff) {
0379                     // self.write("%c%c%c" % (BININT2, obj&0xff, obj>>8))
0380                     $this->_pickle .= self::OP_BININT2 . pack('v', $value);
0381                 }
0382                 return;
0383             }
0384 
0385             // Next check for 4-byte signed ints:
0386             $highBits = $value >> 31;  // note that Python shift sign-extends
0387             if ($highBits == 0 || $highBits == -1) {
0388                 // All high bits are copies of bit 2**31, so the value
0389                 // fits in a 4-byte signed int.
0390                 // self.write(BININT + pack("<i", obj))
0391                 $bin = pack('l', $value);
0392                 if (self::$_isLittleEndian === false) {
0393                     $bin = strrev($bin);
0394                 }
0395                 $this->_pickle .= self::OP_BININT . $bin;
0396                 return;
0397             }
0398         }
0399 
0400         $this->_pickle .= self::OP_INT . $value . "\r\n";
0401     }
0402 
0403     /**
0404      * Write a float value
0405      *
0406      * @param  float $value
0407      * @return void
0408      */
0409     protected function _writeFloat($value)
0410     {
0411         if ($this->_binary) {
0412             // self.write(BINFLOAT + pack('>d', obj))
0413             $bin = pack('d', $value);
0414             if (self::$_isLittleEndian === true) {
0415                 $bin = strrev($bin);
0416             }
0417             $this->_pickle .= self::OP_BINFLOAT . $bin;
0418         } else {
0419             $this->_pickle .= self::OP_FLOAT . $value . "\r\n";
0420         }
0421     }
0422 
0423     /**
0424      * Write a string value
0425      *
0426      * @param  string $value
0427      * @return void
0428      */
0429     protected function _writeString($value)
0430     {
0431         if ( ($id=$this->_searchMomo($value)) !== false ) {
0432             $this->_writeGet($id);
0433             return;
0434         }
0435 
0436         if ($this->_binary) {
0437             $n = strlen($value);
0438             if ($n <= 0xff) {
0439                 // self.write(SHORT_BINSTRING + chr(n) + obj)
0440                 $this->_pickle .= self::OP_SHORT_BINSTRING . chr($n) . $value;
0441             } else {
0442                 // self.write(BINSTRING + pack("<i", n) + obj)
0443                 $binLen = pack('l', $n);
0444                 if (self::$_isLittleEndian === false) {
0445                     $binLen = strrev($binLen);
0446                 }
0447                 $this->_pickle .= self::OP_BINSTRING . $binLen . $value;
0448             }
0449         } else {
0450             $this->_pickle .= self::OP_STRING . $this->_quoteString($value) . "\r\n";
0451         }
0452 
0453         $this->_momorize($value);
0454     }
0455 
0456     /**
0457      * Write an associative array value as dictionary
0458      *
0459      * @param  array $value
0460      * @return void
0461      */
0462     protected function _writeArrayDict(array $value)
0463     {
0464         if (($id=$this->_searchMomo($value)) !== false) {
0465             $this->_writeGet($id);;
0466             return;
0467         }
0468 
0469         $this->_pickle .= self::OP_MARK . self::OP_DICT;
0470         $this->_momorize($value);
0471 
0472         foreach ($value as $k => $v) {
0473             $this->_pickle .= $this->_write($k)
0474                             . $this->_write($v)
0475                             . self::OP_SETITEM;
0476         }
0477     }
0478 
0479     /**
0480      * Write a simple array value as list
0481      *
0482      * @param  array $value
0483      * @return void
0484      */
0485     protected function _writeArrayList(array $value)
0486     {
0487         if (($id = $this->_searchMomo($value)) !== false) {
0488             $this->_writeGet($id);
0489             return;
0490         }
0491 
0492         $this->_pickle .= self::OP_MARK . self::OP_LIST;
0493         $this->_momorize($value);
0494 
0495         foreach ($value as $k => $v) {
0496             $this->_pickle .= $this->_write($v) . self::OP_APPEND;
0497         }
0498     }
0499 
0500     /**
0501      * Write an object as an dictionary
0502      *
0503      * @param  object $value
0504      * @return void
0505      */
0506     protected function _writeObject($value)
0507     {
0508         // can't serialize php objects to python objects yet
0509         $this->_writeArrayDict(get_object_vars($value));
0510     }
0511 
0512     /**
0513      * Write stop
0514      *
0515      * @return void
0516      */
0517     protected function _writeStop()
0518     {
0519         $this->_pickle .= self::OP_STOP;
0520     }
0521 
0522     /* serialize helper */
0523 
0524     /**
0525      * Add a value to the memo and write the id
0526      *
0527      * @param mixed $value
0528      * @return void
0529      */
0530     protected function _momorize($value)
0531     {
0532         $id = count($this->_memo);
0533         $this->_memo[$id] = $value;
0534         $this->_writePut($id);
0535     }
0536 
0537     /**
0538      * Search a value in the meno and return  the id
0539      *
0540      * @param  mixed $value
0541      * @return int|false The id or false
0542      */
0543     protected function _searchMomo($value)
0544     {
0545         return array_search($value, $this->_memo, true);
0546     }
0547 
0548     /**
0549      * Is an array associative?
0550      *
0551      * @param  array $value
0552      * @return boolean
0553      */
0554     protected function _isArrayAssoc(array $value)
0555     {
0556         return array_diff_key($value, array_keys(array_keys($value)));
0557     }
0558 
0559     /**
0560      * Quote/Escape a string
0561      *
0562      * @param  string $str
0563      * @return string quoted string
0564      */
0565     protected function _quoteString($str)
0566     {
0567         $quoteArr = self::$_quoteString;
0568 
0569         if (($cntSingleQuote = substr_count($str, "'"))
0570             && ($cntDoubleQuote = substr_count($str, '"'))
0571             && ($cntSingleQuote < $cntDoubleQuote)
0572         ) {
0573             $quoteArr['"'] = '\\"';
0574             $enclosure     = '"';
0575         } else {
0576             $quoteArr["'"] = "\\'";
0577             $enclosure     = "'";
0578         }
0579 
0580         return $enclosure . strtr($str, $quoteArr) . $enclosure;
0581     }
0582 
0583     /* unserialize */
0584 
0585     /**
0586      * Unserialize from Python Pickle format to PHP
0587      *
0588      * @param  string $pickle
0589      * @param  array $opts
0590      * @return mixed
0591      * @throws Zend_Serializer_Exception on invalid Pickle string
0592      */
0593     public function unserialize($pickle, array $opts = array())
0594     {
0595         // init process vars
0596         $this->_pos       = 0;
0597         $this->_pickle    = $pickle;
0598         $this->_pickleLen = strlen($this->_pickle);
0599         $this->_memo      = array();
0600         $this->_stack     = array();
0601 
0602         // read pickle string
0603         while (($op=$this->_read(1)) !== self::OP_STOP) {
0604             $this->_load($op);
0605         }
0606 
0607         if (!count($this->_stack)) {
0608             // require_once 'Zend/Serializer/Exception.php';
0609             throw new Zend_Serializer_Exception('No data found');
0610         }
0611 
0612         $ret = array_pop($this->_stack);
0613 
0614         // clear process vars
0615         $this->_pos       = 0;
0616         $this->_pickle    = '';
0617         $this->_pickleLen = 0;
0618         $this->_memo      = array();
0619         $this->_stack     = array();
0620 
0621         return $ret;
0622     }
0623 
0624     /**
0625      * Load a pickle opcode
0626      *
0627      * @param  string $op
0628      * @return void
0629      * @throws Zend_Serializer_Exception on invalid opcode
0630      */
0631     protected function _load($op)
0632     {
0633         switch ($op) {
0634             case self::OP_PUT:
0635                 $this->_loadPut();
0636                 break;
0637             case self::OP_BINPUT:
0638                 $this->_loadBinPut();
0639                 break;
0640             case self::OP_LONG_BINPUT:
0641                 $this->_loadLongBinPut();
0642                 break;
0643             case self::OP_GET:
0644                 $this->_loadGet();
0645                 break;
0646             case self::OP_BINGET:
0647                 $this->_loadBinGet();
0648                 break;
0649             case self::OP_LONG_BINGET:
0650                 $this->_loadLongBinGet();
0651                 break;
0652             case self::OP_NONE:
0653                 $this->_loadNone();
0654                 break;
0655             case self::OP_NEWTRUE:
0656                 $this->_loadNewTrue();
0657                 break;
0658             case self::OP_NEWFALSE:
0659                 $this->_loadNewFalse();
0660                 break;
0661             case self::OP_INT:
0662                 $this->_loadInt();
0663                 break;
0664             case self::OP_BININT:
0665                 $this->_loadBinInt();
0666                 break;
0667             case self::OP_BININT1:
0668                 $this->_loadBinInt1();
0669                 break;
0670             case self::OP_BININT2:
0671                 $this->_loadBinInt2();
0672                 break;
0673             case self::OP_LONG:
0674                 $this->_loadLong();
0675                 break;
0676             case self::OP_LONG1:
0677                 $this->_loadLong1();
0678                 break;
0679             case self::OP_LONG4:
0680                 $this->_loadLong4();
0681                 break;
0682             case self::OP_FLOAT:
0683                 $this->_loadFloat();
0684                 break;
0685             case self::OP_BINFLOAT:
0686                 $this->_loadBinFloat();
0687                 break;
0688             case self::OP_STRING:
0689                 $this->_loadString();
0690                 break;
0691             case self::OP_BINSTRING:
0692                 $this->_loadBinString();
0693                 break;
0694             case self::OP_SHORT_BINSTRING:
0695                 $this->_loadShortBinString();
0696                 break;
0697             case self::OP_BINBYTES:
0698                 $this->_loadBinBytes();
0699                 break;
0700             case self::OP_SHORT_BINBYTES:
0701                 $this->_loadShortBinBytes();
0702                 break;
0703             case self::OP_UNICODE:
0704                 $this->_loadUnicode();
0705                 break;
0706             case self::OP_BINUNICODE:
0707                 $this->_loadBinUnicode();
0708                 break;
0709             case self::OP_MARK:
0710                 $this->_loadMark();
0711                 break;
0712             case self::OP_LIST:
0713                 $this->_loadList();
0714                 break;
0715             case self::OP_EMPTY_LIST:
0716                 $this->_loadEmptyList();
0717                 break;
0718             case self::OP_APPEND:
0719                 $this->_loadAppend();
0720                 break;
0721             case self::OP_APPENDS:
0722                 $this->_loadAppends();
0723                 break;
0724             case self::OP_DICT:
0725                 $this->_loadDict();
0726                 break;
0727             case self::OP_EMPTY_DICT:
0728                 $this->_loadEmptyDict();
0729                 break;
0730             case self::OP_SETITEM:
0731                 $this->_loadSetItem();
0732                 break;
0733             case self::OP_SETITEMS:
0734                 $this->_loadSetItems();
0735                 break;
0736             case self::OP_TUPLE:
0737                 $this->_loadTuple();
0738                 break;
0739             case self::OP_TUPLE1:
0740                 $this->_loadTuple1();
0741                 break;
0742             case self::OP_TUPLE2:
0743                 $this->_loadTuple2();
0744                 break;
0745             case self::OP_TUPLE3:
0746                 $this->_loadTuple3();
0747                 break;
0748             case self::OP_PROTO:
0749                 $this->_loadProto();
0750                 break;
0751             default:
0752                 // require_once 'Zend/Serializer/Exception.php';
0753                 throw new Zend_Serializer_Exception('Invalid or unknown opcode "'.$op.'"');
0754         }
0755     }
0756 
0757     /**
0758      * Load a PUT opcode
0759      *
0760      * @return void
0761      * @throws Zend_Serializer_Exception on missing stack
0762      */
0763     protected function _loadPut()
0764     {
0765         $id = (int)$this->_readline();
0766 
0767         $lastStack = count($this->_stack)-1;
0768         if (!isset($this->_stack[$lastStack])) {
0769             // require_once 'Zend/Serializer/Exception.php';
0770             throw new Zend_Serializer_Exception('No stack exist');
0771         }
0772         $this->_memo[$id] = & $this->_stack[$lastStack];
0773     }
0774 
0775     /**
0776      * Load a binary PUT
0777      *
0778      * @return void
0779      * @throws Zend_Serializer_Exception on missing stack
0780      */
0781     protected function _loadBinPut()
0782     {
0783         $id = ord($this->_read(1));
0784 
0785         $lastStack = count($this->_stack)-1;
0786         if (!isset($this->_stack[$lastStack])) {
0787             // require_once 'Zend/Serializer/Exception.php';
0788             throw new Zend_Serializer_Exception('No stack exist');
0789         }
0790         $this->_memo[$id] = & $this->_stack[$lastStack];
0791     }
0792 
0793     /**
0794      * Load a long binary PUT
0795      *
0796      * @return void
0797      * @throws Zend_Serializer_Exception on missing stack
0798      */
0799     protected function _loadLongBinPut()
0800     {
0801         $bin = $this->_read(4);
0802         if (self::$_isLittleEndian === false) {
0803             $bin = strrev($bin);
0804         }
0805         list(, $id) = unpack('l', $bin);
0806 
0807         $lastStack = count($this->_stack)-1;
0808         if (!isset($this->_stack[$lastStack])) {
0809             // require_once 'Zend/Serializer/Exception.php';
0810             throw new Zend_Serializer_Exception('No stack exist');
0811         }
0812         $this->_memo[$id] = & $this->_stack[$lastStack];
0813     }
0814 
0815     /**
0816      * Load a GET operation
0817      *
0818      * @return void
0819      * @throws Zend_Serializer_Exception on missing GET identifier
0820      */
0821     protected function _loadGet()
0822     {
0823         $id = (int)$this->_readline();
0824 
0825         if (!array_key_exists($id, $this->_memo)) {
0826             // require_once 'Zend/Serializer/Exception.php';
0827             throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
0828         }
0829         $this->_stack[] = & $this->_memo[$id];
0830     }
0831 
0832     /**
0833      * Load a binary GET operation
0834      *
0835      * @return void
0836      * @throws Zend_Serializer_Exception on missing GET identifier
0837      */
0838     protected function _loadBinGet()
0839     {
0840         $id = ord($this->_read(1));
0841 
0842         if (!array_key_exists($id, $this->_memo)) {
0843             // require_once 'Zend/Serializer/Exception.php';
0844             throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
0845         }
0846         $this->_stack[] = & $this->_memo[$id];
0847     }
0848 
0849     /**
0850      * Load a long binary GET operation
0851      *
0852      * @return void
0853      * @throws Zend_Serializer_Exception on missing GET identifier
0854      */
0855     protected function _loadLongBinGet()
0856     {
0857         $bin = $this->_read(4);
0858         if (self::$_isLittleEndian === false) {
0859             $bin = strrev($bin);
0860         }
0861         list(, $id) = unpack('l', $bin);
0862 
0863         if (!array_key_exists($id, $this->_memo)) {
0864             // require_once 'Zend/Serializer/Exception.php';
0865             throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
0866         }
0867         $this->_stack[] = & $this->_memo[$id];
0868     }
0869 
0870     /**
0871      * Load a NONE operator
0872      *
0873      * @return void
0874      */
0875     protected function _loadNone()
0876     {
0877         $this->_stack[] = null;
0878     }
0879 
0880     /**
0881      * Load a boolean TRUE operator
0882      *
0883      * @return void
0884      */
0885     protected function _loadNewTrue()
0886     {
0887         $this->_stack[] = true;
0888     }
0889 
0890     /**
0891      * Load a boolean FALSE operator
0892      *
0893      * @return void
0894      */
0895     protected function _loadNewFalse()
0896     {
0897         $this->_stack[] = false;
0898     }
0899 
0900     /**
0901      * Load an integer operator
0902      *
0903      * @return void
0904      */
0905     protected function _loadInt()
0906     {
0907         $line = $this->_readline();
0908         if ($line === '01') {
0909             $this->_stack[] = true;
0910         } elseif ($line === '00') {
0911             $this->_stack[] = false;
0912         } else {
0913             $this->_stack[] = (int)$line;
0914         }
0915     }
0916 
0917     /**
0918      * Load a binary integer operator
0919      *
0920      * @return void
0921      */
0922     protected function _loadBinInt()
0923     {
0924         $bin = $this->_read(4);
0925         if (self::$_isLittleEndian === false) {
0926             $bin = strrev($bin);
0927         }
0928         list(, $int)    = unpack('l', $bin);
0929         $this->_stack[] = $int;
0930     }
0931 
0932     /**
0933      * Load the first byte of a binary integer
0934      *
0935      * @return void
0936      */
0937     protected function _loadBinInt1()
0938     {
0939         $this->_stack[] = ord($this->_read(1));
0940     }
0941 
0942     /**
0943      * Load the second byte of a binary integer
0944      *
0945      * @return void
0946      */
0947     protected function _loadBinInt2()
0948     {
0949         $bin = $this->_read(2);
0950         list(, $int)    = unpack('v', $bin);
0951         $this->_stack[] = $int;
0952     }
0953 
0954     /**
0955      * Load a long (float) operator
0956      *
0957      * @return void
0958      */
0959     protected function _loadLong()
0960     {
0961         $data = rtrim($this->_readline(), 'L');
0962         if ($data === '') {
0963             $this->_stack[] = 0;
0964         } else {
0965             $this->_stack[] = $data;
0966         }
0967     }
0968 
0969     /**
0970      * Load a one byte long integer
0971      *
0972      * @return void
0973      */
0974     protected function _loadLong1()
0975     {
0976         $n    = ord($this->_read(1));
0977         $data = $this->_read($n);
0978         $this->_stack[] = $this->_decodeBinLong($data);
0979     }
0980 
0981     /**
0982      * Load a 4 byte long integer
0983      *
0984      * @return void
0985      */
0986     protected function _loadLong4()
0987     {
0988         $nBin = $this->_read(4);
0989         if (self::$_isLittleEndian === false) {
0990             $nBin = strrev($$nBin);
0991         }
0992         list(, $n) = unpack('l', $nBin);
0993         $data = $this->_read($n);
0994 
0995         $this->_stack[] = $this->_decodeBinLong($data);
0996     }
0997 
0998     /**
0999      * Load a float value
1000      *
1001      * @return void
1002      */
1003     protected function _loadFloat()
1004     {
1005         $float = (float)$this->_readline();
1006         $this->_stack[] = $float;
1007     }
1008 
1009     /**
1010      * Load a binary float value
1011      *
1012      * @return void
1013      */
1014     protected function _loadBinFloat()
1015     {
1016         $bin = $this->_read(8);
1017         if (self::$_isLittleEndian === true) {
1018             $bin = strrev($bin);
1019         }
1020         list(, $float)  = unpack('d', $bin);
1021         $this->_stack[] = $float;
1022     }
1023 
1024     /**
1025      * Load a string
1026      *
1027      * @return void
1028      */
1029     protected function _loadString()
1030     {
1031         $this->_stack[] = $this->_unquoteString((string)$this->_readline());
1032     }
1033 
1034     /**
1035      * Load a binary string
1036      *
1037      * @return void
1038      */
1039     protected function _loadBinString()
1040     {
1041         $bin = $this->_read(4);
1042         if (!self::$_isLittleEndian) {
1043             $bin = strrev($bin);
1044         }
1045         list(, $len)    = unpack('l', $bin);
1046         $this->_stack[] = (string)$this->_read($len);
1047     }
1048 
1049     /**
1050      * Load a short binary string
1051      *
1052      * @return void
1053      */
1054     protected function _loadShortBinString()
1055     {
1056         $len            = ord($this->_read(1));
1057         $this->_stack[] = (string)$this->_read($len);
1058     }
1059 
1060     /**
1061      * Load arbitrary binary bytes
1062      *
1063      * @return void
1064      */
1065     protected function _loadBinBytes()
1066     {
1067         // read byte length
1068         $nBin = $this->_read(4);
1069         if (self::$_isLittleEndian === false) {
1070             $nBin = strrev($$nBin);
1071         }
1072         list(, $n)      = unpack('l', $nBin);
1073         $this->_stack[] = $this->_read($n);
1074     }
1075 
1076     /**
1077      * Load a single binary byte
1078      *
1079      * @return void
1080      */
1081     protected function _loadShortBinBytes()
1082     {
1083         $n              = ord($this->_read(1));
1084         $this->_stack[] = $this->_read($n);
1085     }
1086 
1087     /**
1088      * Load a unicode string
1089      *
1090      * @return void
1091      */
1092     protected function _loadUnicode()
1093     {
1094         $data    = $this->_readline();
1095         $pattern = '/\\\\u([a-fA-F0-9]{4})/u'; // \uXXXX
1096         $data    = preg_replace_callback($pattern, array($this, '_convertMatchingUnicodeSequence2Utf8'), $data);
1097 
1098         $this->_stack[] = $data;
1099     }
1100 
1101     /**
1102      * Convert a unicode sequence to UTF-8
1103      *
1104      * @param  array $match
1105      * @return string
1106      */
1107     protected function _convertMatchingUnicodeSequence2Utf8(array $match)
1108     {
1109         return $this->_hex2Utf8($match[1]);
1110     }
1111 
1112     /**
1113      * Convert a hex string to a UTF-8 string
1114      *
1115      * @param  string $sequence
1116      * @return string
1117      * @throws Zend_Serializer_Exception on unmatched unicode sequence
1118      */
1119     protected function _hex2Utf8($hex)
1120     {
1121         $uniCode = hexdec($hex);
1122 
1123         if ($uniCode < 0x80) { // 1Byte
1124             $utf8Char = chr($uniCode);
1125 
1126         } elseif ($uniCode < 0x800) { // 2Byte
1127             $utf8Char = chr(0xC0 | $uniCode >> 6)
1128                       . chr(0x80 | $uniCode & 0x3F);
1129 
1130         } elseif ($uniCode < 0x10000) { // 3Byte
1131             $utf8Char = chr(0xE0 | $uniCode >> 12)
1132                       . chr(0x80 | $uniCode >> 6 & 0x3F)
1133                       . chr(0x80 | $uniCode & 0x3F);
1134 
1135         } elseif ($uniCode < 0x110000) { // 4Byte
1136             $utf8Char  = chr(0xF0 | $uniCode >> 18)
1137                        . chr(0x80 | $uniCode >> 12 & 0x3F)
1138                        . chr(0x80 | $uniCode >> 6 & 0x3F)
1139                        . chr(0x80 | $uniCode & 0x3F);
1140         } else {
1141             // require_once 'Zend/Serializer/Exception.php';
1142             throw new Zend_Serializer_Exception('Unsupported unicode character found "' . dechex($uniCode) . '"');
1143         }
1144 
1145         return $utf8Char;
1146     }
1147 
1148     /**
1149      * Load binary unicode sequence
1150      *
1151      * @return void
1152      */
1153     protected function _loadBinUnicode()
1154     {
1155         // read byte length
1156         $n = $this->_read(4);
1157         if (self::$_isLittleEndian === false) {
1158             $n = strrev($n);
1159         }
1160         list(, $n) = unpack('l', $n);
1161         $data      = $this->_read($n);
1162 
1163         $this->_stack[] = $data;
1164     }
1165 
1166     /**
1167      * Load a marker sequence
1168      *
1169      * @return void
1170      */
1171     protected function _loadMark()
1172     {
1173         $this->_stack[] = $this->_marker;
1174     }
1175 
1176     /**
1177      * Load an array (list)
1178      *
1179      * @return void
1180      */
1181     protected function _loadList()
1182     {
1183         $k = $this->_lastMarker();
1184         $this->_stack[$k] = array();
1185 
1186         // remove all elements after marker
1187         $max = count($this->_stack);
1188         for ($i = $k+1, $max; $i < $max; $i++) {
1189             unset($this->_stack[$i]);
1190         }
1191     }
1192 
1193     /**
1194      * Load an append (to list) sequence
1195      *
1196      * @return void
1197      */
1198     protected function _loadAppend()
1199     {
1200         $value  =  array_pop($this->_stack);
1201         $list   =& $this->_stack[count($this->_stack)-1];
1202         $list[] =  $value;
1203     }
1204 
1205     /**
1206      * Load an empty list sequence
1207      *
1208      * @return void
1209      */
1210     protected function _loadEmptyList()
1211     {
1212         $this->_stack[] = array();
1213     }
1214 
1215     /**
1216      * Load multiple append (to list) sequences at once
1217      *
1218      * @return void
1219      */
1220     protected function _loadAppends()
1221     {
1222         $k    =  $this->_lastMarker();
1223         $list =& $this->_stack[$k - 1];
1224         $max  =  count($this->_stack);
1225         for ($i = $k + 1; $i < $max; $i++) {
1226             $list[] = $this->_stack[$i];
1227             unset($this->_stack[$i]);
1228         }
1229         unset($this->_stack[$k]);
1230     }
1231 
1232     /**
1233      * Load an associative array (Python dictionary)
1234      *
1235      * @return void
1236      */
1237     protected function _loadDict()
1238     {
1239         $k = $this->_lastMarker();
1240         $this->_stack[$k] = array();
1241 
1242         // remove all elements after marker
1243         $max = count($this->_stack);
1244         for($i = $k + 1; $i < $max; $i++) {
1245             unset($this->_stack[$i]);
1246         }
1247     }
1248 
1249     /**
1250      * Load an item from a set
1251      *
1252      * @return void
1253      */
1254     protected function _loadSetItem()
1255     {
1256         $value =  array_pop($this->_stack);
1257         $key   =  array_pop($this->_stack);
1258         $dict  =& $this->_stack[count($this->_stack) - 1];
1259         $dict[$key] = $value;
1260     }
1261 
1262     /**
1263      * Load an empty dictionary
1264      *
1265      * @return void
1266      */
1267     protected function _loadEmptyDict()
1268     {
1269         $this->_stack[] = array();
1270     }
1271 
1272     /**
1273      * Load set items
1274      *
1275      * @return void
1276      */
1277     protected function _loadSetItems()
1278     {
1279         $k    =  $this->_lastMarker();
1280         $dict =& $this->_stack[$k - 1];
1281         $max  =  count($this->_stack);
1282         for ($i = $k + 1; $i < $max; $i += 2) {
1283             $key        = $this->_stack[$i];
1284             $value      = $this->_stack[$i + 1];
1285             $dict[$key] = $value;
1286             unset($this->_stack[$i], $this->_stack[$i+1]);
1287         }
1288         unset($this->_stack[$k]);
1289     }
1290 
1291     /**
1292      * Load a tuple
1293      *
1294      * @return void
1295      */
1296     protected function _loadTuple()
1297     {
1298         $k                =  $this->_lastMarker();
1299         $this->_stack[$k] =  array();
1300         $tuple            =& $this->_stack[$k];
1301         $max              =  count($this->_stack);
1302         for($i = $k + 1; $i < $max; $i++) {
1303             $tuple[] = $this->_stack[$i];
1304             unset($this->_stack[$i]);
1305         }
1306     }
1307 
1308     /**
1309      * Load single item tuple
1310      *
1311      * @return void
1312      */
1313     protected function _loadTuple1()
1314     {
1315         $value1 = array_pop($this->_stack);
1316         $this->_stack[] = array($value1);
1317     }
1318 
1319     /**
1320      * Load two item tuple
1321      *
1322      * @return void
1323      */
1324     protected function _loadTuple2()
1325     {
1326         $value2 = array_pop($this->_stack);
1327         $value1 = array_pop($this->_stack);
1328         $this->_stack[] = array($value1, $value2);
1329     }
1330 
1331     /**
1332      * Load three item tuple
1333      *
1334      * @return void
1335      */
1336     protected function _loadTuple3() {
1337         $value3 = array_pop($this->_stack);
1338         $value2 = array_pop($this->_stack);
1339         $value1 = array_pop($this->_stack);
1340         $this->_stack[] = array($value1, $value2, $value3);
1341     }
1342 
1343     /**
1344      * Load a proto value
1345      *
1346      * @return void
1347      * @throws Zend_Serializer_Exception if Pickle version does not support this feature
1348      */
1349     protected function _loadProto()
1350     {
1351         $proto = ord($this->_read(1));
1352         if ($proto < 2 || $proto > 3) {
1353             // require_once 'Zend/Serializer/Exception.php';
1354             throw new Zend_Serializer_Exception('Invalid protocol version detected');
1355         }
1356         $this->_protocol = $proto;
1357     }
1358 
1359     /* unserialize helper */
1360 
1361     /**
1362      * Read a segment of the pickle
1363      *
1364      * @param  mixed $len
1365      * @return string
1366      * @throws Zend_Serializer_Exception if position matches end of data
1367      */
1368     protected function _read($len)
1369     {
1370         if (($this->_pos + $len) > $this->_pickleLen) {
1371             // require_once 'Zend/Serializer/Exception.php';
1372             throw new Zend_Serializer_Exception('End of data');
1373         }
1374 
1375         $this->_pos+= $len;
1376         return substr($this->_pickle, ($this->_pos - $len), $len);
1377     }
1378 
1379     /**
1380      * Read a line of the pickle at once
1381      *
1382      * @return string
1383      * @throws Zend_Serializer_Exception if no EOL character found
1384      */
1385     protected function _readline()
1386     {
1387         $eolLen = 2;
1388         $eolPos = strpos($this->_pickle, "\r\n", $this->_pos);
1389         if ($eolPos === false) {
1390             $eolPos = strpos($this->_pickle, "\n", $this->_pos);
1391             $eolLen = 1;
1392         }
1393 
1394         if ($eolPos === false) {
1395             // require_once 'Zend/Serializer/Exception.php';
1396             throw new Zend_Serializer_Exception('No new line found');
1397         }
1398         $ret        = substr($this->_pickle, $this->_pos, $eolPos-$this->_pos);
1399         $this->_pos = $eolPos + $eolLen;
1400 
1401         return $ret;
1402     }
1403 
1404     /**
1405      * Unquote/Unescape a quoted string
1406      *
1407      * @param  string $str quoted string
1408      * @return string unquoted string
1409      */
1410     protected function _unquoteString($str)
1411     {
1412         $quoteArr = array_flip(self::$_quoteString);
1413 
1414         if ($str[0] == '"') {
1415             $quoteArr['\\"'] = '"';
1416         } else {
1417             $quoteArr["\\'"] = "'";
1418         }
1419 
1420         return strtr(substr(trim($str), 1, -1), $quoteArr);
1421     }
1422 
1423     /**
1424      * Return last marker position in stack
1425      *
1426      * @return int
1427      */
1428     protected function _lastMarker()
1429     {
1430         for ($k = count($this->_stack)-1; $k >= 0; $k -= 1) {
1431             if ($this->_stack[$k] === $this->_marker) {
1432                 break;
1433             }
1434         }
1435         return $k;
1436     }
1437 
1438     /**
1439      * Decode a binary long sequence
1440      *
1441      * @param  string $data
1442      * @return int|float|string
1443      */
1444     protected function _decodeBinLong($data)
1445     {
1446         $nbytes = strlen($data);
1447 
1448         if ($nbytes == 0) {
1449             return 0;
1450         }
1451 
1452         $long = 0;
1453 
1454         if ($nbytes > 7) {
1455             if (!extension_loaded('bcmath')) {
1456                 return INF;
1457             }
1458 
1459             for ($i=0; $i<$nbytes; $i++) {
1460                 $long = bcadd($long, bcmul(ord($data[$i]), bcpow(256, $i, 0)));
1461             }
1462             if (0x80 <= ord($data[$nbytes-1])) {
1463                 $long = bcsub($long, bcpow(2, $nbytes * 8));
1464             }
1465 
1466         } else {
1467             for ($i=0; $i<$nbytes; $i++) {
1468                 $long+= ord($data[$i]) * pow(256, $i);
1469             }
1470             if (0x80 <= ord($data[$nbytes-1])) {
1471                 $long-= pow(2, $nbytes * 8);
1472                 // $long-= 1 << ($nbytes * 8);
1473             }
1474         }
1475 
1476         return $long;
1477     }
1478 }