PageRenderTime 68ms CodeModel.GetById 17ms app.highlight 41ms RepoModel.GetById 1ms app.codeStats 1ms

/src/lib/Zend/Serializer/Adapter/PythonPickle.php

https://bitbucket.org/mkrasuski/magento-ce
PHP | 1478 lines | 1205 code | 58 blank | 215 comment | 55 complexity | 8eaa4d904fa3ac33cc3d405f6eeb92bf MD5 | raw file
   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-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
  20 * @version    $Id$
  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-2015 Zend Technologies USA Inc. (http://www.zend.com)
  35 * @license    http://framework.zend.com/license/new-bsd     New BSD License
  36 */
  37class 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 the system is little-endian
 104     */
 105    protected static $_isLittleEndian = null;
 106
 107    /**
 108     * @var array Strings representing quotes
 109     */
 110    protected static $_quoteString = array(
 111        '\\' => '\\\\',
 112        "\x00" => '\\x00', "\x01" => '\\x01', "\x02" => '\\x02', "\x03" => '\\x03',
 113        "\x04" => '\\x04', "\x05" => '\\x05', "\x06" => '\\x06', "\x07" => '\\x07',
 114        "\x08" => '\\x08', "\x09" => '\\t',   "\x0a" => '\\n',   "\x0b" => '\\x0b',
 115        "\x0c" => '\\x0c', "\x0d" => '\\r',   "\x0e" => '\\x0e', "\x0f" => '\\x0f',
 116        "\x10" => '\\x10', "\x11" => '\\x11', "\x12" => '\\x12', "\x13" => '\\x13',
 117        "\x14" => '\\x14', "\x15" => '\\x15', "\x16" => '\\x16', "\x17" => '\\x17',
 118        "\x18" => '\\x18', "\x19" => '\\x19', "\x1a" => '\\x1a', "\x1b" => '\\x1b',
 119        "\x1c" => '\\x1c', "\x1d" => '\\x1d', "\x1e" => '\\x1e', "\x1f" => '\\x1f',
 120        "\xff" => '\\xff'
 121    );
 122
 123    /**
 124     * @var array Default options
 125     */
 126    protected $_options = array(
 127        'protocol'           => 0,
 128    );
 129
 130    // process vars
 131    protected $_protocol           = 0;
 132    protected $_binary             = false;
 133    protected $_memo               = array();
 134    protected $_pickle             = '';
 135    protected $_pickleLen          = 0;
 136    protected $_pos                = 0;
 137    protected $_stack              = array();
 138    protected $_marker             = null;
 139
 140    /**
 141     * Constructor
 142     *
 143     * @link Zend_Serializer_Adapter_AdapterAbstract::__construct()
 144     */
 145    public function __construct($opts=array())
 146    {
 147        parent::__construct($opts);
 148
 149        // init
 150        if (self::$_isLittleEndian === null) {
 151            self::$_isLittleEndian = (pack('l', 1) === "\x01\x00\x00\x00");
 152        }
 153
 154        $this->_marker = new stdClass();
 155    }
 156
 157    /**
 158     * Set an option
 159     *
 160     * @link   Zend_Serializer_Adapter_AdapterAbstract::setOption()
 161     * @param  string $name
 162     * @param  mixed $value
 163     * @return Zend_Serializer_Adapter_PythonPickle
 164     */
 165    public function setOption($name, $value)
 166    {
 167        switch ($name) {
 168            case 'protocol':
 169                $value = $this->_checkProtocolNumber($value);
 170                break;
 171        }
 172
 173        return parent::setOption($name, $value);
 174    }
 175
 176    /**
 177     * Check and normalize pickle protocol number
 178     *
 179     * @param  int $number
 180     * @return int
 181     * @throws Zend_Serializer_Exception
 182     */
 183    protected function _checkProtocolNumber($number)
 184    {
 185        $int = (int) $number;
 186        if ($int < 0 || $int > 3) {
 187            #require_once 'Zend/Serializer/Exception.php';
 188            throw new Zend_Serializer_Exception('Invalid or unknown protocol version "'.$number.'"');
 189        }
 190        return $int;
 191    }
 192
 193    /* serialize */
 194
 195    /**
 196     * Serialize PHP to PythonPickle format
 197     *
 198     * @param  mixed $value
 199     * @param  array $opts
 200     * @return string
 201     */
 202    public function serialize($value, array $opts = array())
 203    {
 204        $opts = $opts + $this->_options;
 205
 206        $this->_protocol = $this->_checkProtocolNumber($opts['protocol']);
 207        $this->_binary   = $this->_protocol != 0;
 208
 209        // clear process vars before serializing
 210        $this->_memo   = array();
 211        $this->_pickle = '';
 212
 213        // write
 214        if ($this->_protocol >= 2) {
 215            $this->_writeProto($this->_protocol);
 216        }
 217        $this->_write($value);
 218        $this->_writeStop();
 219
 220        // clear process vars after serializing
 221        $this->_memo = array();
 222        $pickle = $this->_pickle;
 223        $this->_pickle = '';
 224
 225        return $pickle;
 226    }
 227
 228    /**
 229     * Write a value
 230     *
 231     * @param  mixed $value
 232     * @return void
 233     * @throws Zend_Serializer_Exception on invalid or unrecognized value type
 234     */
 235    protected function _write($value)
 236    {
 237        if ($value === null) {
 238            $this->_writeNull();
 239        } elseif ($value === true) {
 240            $this->_writeTrue();
 241        } elseif ($value === false) {
 242            $this->_writeFalse();
 243        } elseif (is_int($value)) {
 244            $this->_writeInt($value);
 245        } elseif (is_float($value)) {
 246            $this->_writeFloat($value);
 247        } elseif (is_string($value)) {
 248            // TODO: write unicode / binary
 249            $this->_writeString($value);
 250        } elseif (is_array($value)) {
 251            if ($this->_isArrayAssoc($value)) {
 252                $this->_writeArrayDict($value);
 253            } else {
 254                $this->_writeArrayList($value);
 255            }
 256        } elseif (is_object($value)) {
 257            $this->_writeObject($value);
 258        } else {
 259            #require_once 'Zend/Serializer/Exception.php';
 260            throw new Zend_Serializer_Exception(
 261                'PHP-Type "'.gettype($value).'" isn\'t serializable with '.get_class($this)
 262            );
 263        }
 264    }
 265
 266    /**
 267     * Write pickle protocol
 268     *
 269     * @param  int $protocol
 270     * @return void
 271     */
 272    protected function _writeProto($protocol)
 273    {
 274        $this->_pickle .= self::OP_PROTO . $protocol;
 275    }
 276
 277    /**
 278     * Write a get
 279     *
 280     * @param  int $id Id of memo
 281     * @return void
 282     */
 283    protected function _writeGet($id)
 284    {
 285        if ($this->_binary) {
 286            if ($id <= 0xff) {
 287                // BINGET + chr(i)
 288                $this->_pickle .= self::OP_BINGET . chr($id);
 289            } else {
 290                // LONG_BINGET + pack("<i", i)
 291                $bin = pack('l', $id);
 292                if (self::$_isLittleEndian === false) {
 293                    $bin = strrev($bin);
 294                }
 295                $this->_pickle .= self::OP_LONG_BINGET . $bin;
 296            }
 297        } else {
 298            $this->_pickle .= self::OP_GET . $id . "\r\n";
 299        }
 300    }
 301
 302    /**
 303     * Write a put
 304     *
 305     * @param  int $id Id of memo
 306     * @return void
 307     */
 308    protected function _writePut($id)
 309    {
 310        if ($this->_binary) {
 311            if ($id <= 0xff) {
 312                // BINPUT + chr(i)
 313                $this->_pickle .= self::OP_BINPUT . chr($id);
 314            } else {
 315                // LONG_BINPUT + pack("<i", i)
 316                $bin = pack('l', $id);
 317                if (self::$_isLittleEndian === false) {
 318                    $bin = strrev($bin);
 319                }
 320                $this->_pickle .= self::OP_LONG_BINPUT . $bin;
 321            }
 322        } else {
 323            $this->_pickle .= self::OP_PUT . $id . "\r\n";
 324        }
 325    }
 326
 327    /**
 328     * Write a null as None
 329     *
 330     * @return void
 331     */
 332    protected function _writeNull()
 333    {
 334        $this->_pickle .= self::OP_NONE;
 335    }
 336
 337    /**
 338     * Write a boolean true
 339     *
 340     * @return void
 341     */
 342    protected function _writeTrue()
 343    {
 344        if ($this->_protocol >= 2) {
 345            $this->_pickle .= self::OP_NEWTRUE;
 346        } else {
 347            $this->_pickle .= self::OP_INT . "01\r\n";
 348        }
 349    }
 350
 351    /**
 352     * Write a boolean false
 353     *
 354     * @return void
 355     */
 356    protected function _writeFalse()
 357    {
 358        if ($this->_protocol >= 2) {
 359            $this->_pickle .= self::OP_NEWFALSE;
 360        } else {
 361            $this->_pickle .= self::OP_INT . "00\r\n";
 362        }
 363    }
 364
 365    /**
 366     * Write an integer value
 367     *
 368     * @param  int $value
 369     * @return void
 370     */
 371    protected function _writeInt($value)
 372    {
 373        if ($this->_binary) {
 374            if ($value >= 0) {
 375                if ($value <= 0xff) {
 376                    // self.write(BININT1 + chr(obj))
 377                    $this->_pickle .= self::OP_BININT1 . chr($value);
 378                } elseif ($value <= 0xffff) {
 379                    // self.write("%c%c%c" % (BININT2, obj&0xff, obj>>8))
 380                    $this->_pickle .= self::OP_BININT2 . pack('v', $value);
 381                }
 382                return;
 383            }
 384
 385            // Next check for 4-byte signed ints:
 386            $highBits = $value >> 31;  // note that Python shift sign-extends
 387            if ($highBits == 0 || $highBits == -1) {
 388                // All high bits are copies of bit 2**31, so the value
 389                // fits in a 4-byte signed int.
 390                // self.write(BININT + pack("<i", obj))
 391                $bin = pack('l', $value);
 392                if (self::$_isLittleEndian === false) {
 393                    $bin = strrev($bin);
 394                }
 395                $this->_pickle .= self::OP_BININT . $bin;
 396                return;
 397            }
 398        }
 399
 400        $this->_pickle .= self::OP_INT . $value . "\r\n";
 401    }
 402
 403    /**
 404     * Write a float value
 405     *
 406     * @param  float $value
 407     * @return void
 408     */
 409    protected function _writeFloat($value)
 410    {
 411        if ($this->_binary) {
 412            // self.write(BINFLOAT + pack('>d', obj))
 413            $bin = pack('d', $value);
 414            if (self::$_isLittleEndian === true) {
 415                $bin = strrev($bin);
 416            }
 417            $this->_pickle .= self::OP_BINFLOAT . $bin;
 418        } else {
 419            $this->_pickle .= self::OP_FLOAT . $value . "\r\n";
 420        }
 421    }
 422
 423    /**
 424     * Write a string value
 425     *
 426     * @param  string $value
 427     * @return void
 428     */
 429    protected function _writeString($value)
 430    {
 431        if ( ($id=$this->_searchMomo($value)) !== false ) {
 432            $this->_writeGet($id);
 433            return;
 434        }
 435
 436        if ($this->_binary) {
 437            $n = strlen($value);
 438            if ($n <= 0xff) {
 439                // self.write(SHORT_BINSTRING + chr(n) + obj)
 440                $this->_pickle .= self::OP_SHORT_BINSTRING . chr($n) . $value;
 441            } else {
 442                // self.write(BINSTRING + pack("<i", n) + obj)
 443                $binLen = pack('l', $n);
 444                if (self::$_isLittleEndian === false) {
 445                    $binLen = strrev($binLen);
 446                }
 447                $this->_pickle .= self::OP_BINSTRING . $binLen . $value;
 448            }
 449        } else {
 450            $this->_pickle .= self::OP_STRING . $this->_quoteString($value) . "\r\n";
 451        }
 452
 453        $this->_momorize($value);
 454    }
 455
 456    /**
 457     * Write an associative array value as dictionary
 458     *
 459     * @param  array $value
 460     * @return void
 461     */
 462    protected function _writeArrayDict(array $value)
 463    {
 464        if (($id=$this->_searchMomo($value)) !== false) {
 465            $this->_writeGet($id);;
 466            return;
 467        }
 468
 469        $this->_pickle .= self::OP_MARK . self::OP_DICT;
 470        $this->_momorize($value);
 471
 472        foreach ($value as $k => $v) {
 473            $this->_pickle .= $this->_write($k)
 474                            . $this->_write($v)
 475                            . self::OP_SETITEM;
 476        }
 477    }
 478
 479    /**
 480     * Write a simple array value as list
 481     *
 482     * @param  array $value
 483     * @return void
 484     */
 485    protected function _writeArrayList(array $value)
 486    {
 487        if (($id = $this->_searchMomo($value)) !== false) {
 488            $this->_writeGet($id);
 489            return;
 490        }
 491
 492        $this->_pickle .= self::OP_MARK . self::OP_LIST;
 493        $this->_momorize($value);
 494
 495        foreach ($value as $k => $v) {
 496            $this->_pickle .= $this->_write($v) . self::OP_APPEND;
 497        }
 498    }
 499
 500    /**
 501     * Write an object as an dictionary
 502     *
 503     * @param  object $value
 504     * @return void
 505     */
 506    protected function _writeObject($value)
 507    {
 508        // can't serialize php objects to python objects yet
 509        $this->_writeArrayDict(get_object_vars($value));
 510    }
 511
 512    /**
 513     * Write stop
 514     *
 515     * @return void
 516     */
 517    protected function _writeStop()
 518    {
 519        $this->_pickle .= self::OP_STOP;
 520    }
 521
 522    /* serialize helper */
 523
 524    /**
 525     * Add a value to the memo and write the id
 526     *
 527     * @param mixed $value
 528     * @return void
 529     */
 530    protected function _momorize($value)
 531    {
 532        $id = count($this->_memo);
 533        $this->_memo[$id] = $value;
 534        $this->_writePut($id);
 535    }
 536
 537    /**
 538     * Search a value in the meno and return  the id
 539     *
 540     * @param  mixed $value
 541     * @return int|false The id or false
 542     */
 543    protected function _searchMomo($value)
 544    {
 545        return array_search($value, $this->_memo, true);
 546    }
 547
 548    /**
 549     * Is an array associative?
 550     *
 551     * @param  array $value
 552     * @return boolean
 553     */
 554    protected function _isArrayAssoc(array $value)
 555    {
 556        return array_diff_key($value, array_keys(array_keys($value)));
 557    }
 558
 559    /**
 560     * Quote/Escape a string
 561     *
 562     * @param  string $str
 563     * @return string quoted string
 564     */
 565    protected function _quoteString($str)
 566    {
 567        $quoteArr = self::$_quoteString;
 568
 569        if (($cntSingleQuote = substr_count($str, "'"))
 570            && ($cntDoubleQuote = substr_count($str, '"'))
 571            && ($cntSingleQuote < $cntDoubleQuote)
 572        ) {
 573            $quoteArr['"'] = '\\"';
 574            $enclosure     = '"';
 575        } else {
 576            $quoteArr["'"] = "\\'";
 577            $enclosure     = "'";
 578        }
 579
 580        return $enclosure . strtr($str, $quoteArr) . $enclosure;
 581    }
 582
 583    /* unserialize */
 584
 585    /**
 586     * Unserialize from Python Pickle format to PHP
 587     *
 588     * @param  string $pickle
 589     * @param  array $opts
 590     * @return mixed
 591     * @throws Zend_Serializer_Exception on invalid Pickle string
 592     */
 593    public function unserialize($pickle, array $opts = array())
 594    {
 595        // init process vars
 596        $this->_pos       = 0;
 597        $this->_pickle    = $pickle;
 598        $this->_pickleLen = strlen($this->_pickle);
 599        $this->_memo      = array();
 600        $this->_stack     = array();
 601
 602        // read pickle string
 603        while (($op=$this->_read(1)) !== self::OP_STOP) {
 604            $this->_load($op);
 605        }
 606
 607        if (!count($this->_stack)) {
 608            #require_once 'Zend/Serializer/Exception.php';
 609            throw new Zend_Serializer_Exception('No data found');
 610        }
 611
 612        $ret = array_pop($this->_stack);
 613
 614        // clear process vars
 615        $this->_pos       = 0;
 616        $this->_pickle    = '';
 617        $this->_pickleLen = 0;
 618        $this->_memo      = array();
 619        $this->_stack     = array();
 620
 621        return $ret;
 622    }
 623
 624    /**
 625     * Load a pickle opcode
 626     *
 627     * @param  string $op
 628     * @return void
 629     * @throws Zend_Serializer_Exception on invalid opcode
 630     */
 631    protected function _load($op)
 632    {
 633        switch ($op) {
 634            case self::OP_PUT:
 635                $this->_loadPut();
 636                break;
 637            case self::OP_BINPUT:
 638                $this->_loadBinPut();
 639                break;
 640            case self::OP_LONG_BINPUT:
 641                $this->_loadLongBinPut();
 642                break;
 643            case self::OP_GET:
 644                $this->_loadGet();
 645                break;
 646            case self::OP_BINGET:
 647                $this->_loadBinGet();
 648                break;
 649            case self::OP_LONG_BINGET:
 650                $this->_loadLongBinGet();
 651                break;
 652            case self::OP_NONE:
 653                $this->_loadNone();
 654                break;
 655            case self::OP_NEWTRUE:
 656                $this->_loadNewTrue();
 657                break;
 658            case self::OP_NEWFALSE:
 659                $this->_loadNewFalse();
 660                break;
 661            case self::OP_INT:
 662                $this->_loadInt();
 663                break;
 664            case self::OP_BININT:
 665                $this->_loadBinInt();
 666                break;
 667            case self::OP_BININT1:
 668                $this->_loadBinInt1();
 669                break;
 670            case self::OP_BININT2:
 671                $this->_loadBinInt2();
 672                break;
 673            case self::OP_LONG:
 674                $this->_loadLong();
 675                break;
 676            case self::OP_LONG1:
 677                $this->_loadLong1();
 678                break;
 679            case self::OP_LONG4:
 680                $this->_loadLong4();
 681                break;
 682            case self::OP_FLOAT:
 683                $this->_loadFloat();
 684                break;
 685            case self::OP_BINFLOAT:
 686                $this->_loadBinFloat();
 687                break;
 688            case self::OP_STRING:
 689                $this->_loadString();
 690                break;
 691            case self::OP_BINSTRING:
 692                $this->_loadBinString();
 693                break;
 694            case self::OP_SHORT_BINSTRING:
 695                $this->_loadShortBinString();
 696                break;
 697            case self::OP_BINBYTES:
 698                $this->_loadBinBytes();
 699                break;
 700            case self::OP_SHORT_BINBYTES:
 701                $this->_loadShortBinBytes();
 702                break;
 703            case self::OP_UNICODE:
 704                $this->_loadUnicode();
 705                break;
 706            case self::OP_BINUNICODE:
 707                $this->_loadBinUnicode();
 708                break;
 709            case self::OP_MARK:
 710                $this->_loadMark();
 711                break;
 712            case self::OP_LIST:
 713                $this->_loadList();
 714                break;
 715            case self::OP_EMPTY_LIST:
 716                $this->_loadEmptyList();
 717                break;
 718            case self::OP_APPEND:
 719                $this->_loadAppend();
 720                break;
 721            case self::OP_APPENDS:
 722                $this->_loadAppends();
 723                break;
 724            case self::OP_DICT:
 725                $this->_loadDict();
 726                break;
 727            case self::OP_EMPTY_DICT:
 728                $this->_loadEmptyDict();
 729                break;
 730            case self::OP_SETITEM:
 731                $this->_loadSetItem();
 732                break;
 733            case self::OP_SETITEMS:
 734                $this->_loadSetItems();
 735                break;
 736            case self::OP_TUPLE:
 737                $this->_loadTuple();
 738                break;
 739            case self::OP_TUPLE1:
 740                $this->_loadTuple1();
 741                break;
 742            case self::OP_TUPLE2:
 743                $this->_loadTuple2();
 744                break;
 745            case self::OP_TUPLE3:
 746                $this->_loadTuple3();
 747                break;
 748            case self::OP_PROTO:
 749                $this->_loadProto();
 750                break;
 751            default:
 752                #require_once 'Zend/Serializer/Exception.php';
 753                throw new Zend_Serializer_Exception('Invalid or unknown opcode "'.$op.'"');
 754        }
 755    }
 756
 757    /**
 758     * Load a PUT opcode
 759     *
 760     * @return void
 761     * @throws Zend_Serializer_Exception on missing stack
 762     */
 763    protected function _loadPut()
 764    {
 765        $id = (int)$this->_readline();
 766
 767        $lastStack = count($this->_stack)-1;
 768        if (!isset($this->_stack[$lastStack])) {
 769            #require_once 'Zend/Serializer/Exception.php';
 770            throw new Zend_Serializer_Exception('No stack exist');
 771        }
 772        $this->_memo[$id] = & $this->_stack[$lastStack];
 773    }
 774
 775    /**
 776     * Load a binary PUT
 777     *
 778     * @return void
 779     * @throws Zend_Serializer_Exception on missing stack
 780     */
 781    protected function _loadBinPut()
 782    {
 783        $id = ord($this->_read(1));
 784
 785        $lastStack = count($this->_stack)-1;
 786        if (!isset($this->_stack[$lastStack])) {
 787            #require_once 'Zend/Serializer/Exception.php';
 788            throw new Zend_Serializer_Exception('No stack exist');
 789        }
 790        $this->_memo[$id] = & $this->_stack[$lastStack];
 791    }
 792
 793    /**
 794     * Load a long binary PUT
 795     *
 796     * @return void
 797     * @throws Zend_Serializer_Exception on missing stack
 798     */
 799    protected function _loadLongBinPut()
 800    {
 801        $bin = $this->_read(4);
 802        if (self::$_isLittleEndian === false) {
 803            $bin = strrev($bin);
 804        }
 805        list(, $id) = unpack('l', $bin);
 806
 807        $lastStack = count($this->_stack)-1;
 808        if (!isset($this->_stack[$lastStack])) {
 809            #require_once 'Zend/Serializer/Exception.php';
 810            throw new Zend_Serializer_Exception('No stack exist');
 811        }
 812        $this->_memo[$id] = & $this->_stack[$lastStack];
 813    }
 814
 815    /**
 816     * Load a GET operation
 817     *
 818     * @return void
 819     * @throws Zend_Serializer_Exception on missing GET identifier
 820     */
 821    protected function _loadGet()
 822    {
 823        $id = (int)$this->_readline();
 824
 825        if (!array_key_exists($id, $this->_memo)) {
 826            #require_once 'Zend/Serializer/Exception.php';
 827            throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
 828        }
 829        $this->_stack[] = & $this->_memo[$id];
 830    }
 831
 832    /**
 833     * Load a binary GET operation
 834     *
 835     * @return void
 836     * @throws Zend_Serializer_Exception on missing GET identifier
 837     */
 838    protected function _loadBinGet()
 839    {
 840        $id = ord($this->_read(1));
 841
 842        if (!array_key_exists($id, $this->_memo)) {
 843            #require_once 'Zend/Serializer/Exception.php';
 844            throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
 845        }
 846        $this->_stack[] = & $this->_memo[$id];
 847    }
 848
 849    /**
 850     * Load a long binary GET operation
 851     *
 852     * @return void
 853     * @throws Zend_Serializer_Exception on missing GET identifier
 854     */
 855    protected function _loadLongBinGet()
 856    {
 857        $bin = $this->_read(4);
 858        if (self::$_isLittleEndian === false) {
 859            $bin = strrev($bin);
 860        }
 861        list(, $id) = unpack('l', $bin);
 862
 863        if (!array_key_exists($id, $this->_memo)) {
 864            #require_once 'Zend/Serializer/Exception.php';
 865            throw new Zend_Serializer_Exception('Get id "' . $id . '" not found in momo');
 866        }
 867        $this->_stack[] = & $this->_memo[$id];
 868    }
 869
 870    /**
 871     * Load a NONE operator
 872     *
 873     * @return void
 874     */
 875    protected function _loadNone()
 876    {
 877        $this->_stack[] = null;
 878    }
 879
 880    /**
 881     * Load a boolean TRUE operator
 882     *
 883     * @return void
 884     */
 885    protected function _loadNewTrue()
 886    {
 887        $this->_stack[] = true;
 888    }
 889
 890    /**
 891     * Load a boolean FALSE operator
 892     *
 893     * @return void
 894     */
 895    protected function _loadNewFalse()
 896    {
 897        $this->_stack[] = false;
 898    }
 899
 900    /**
 901     * Load an integer operator
 902     *
 903     * @return void
 904     */
 905    protected function _loadInt()
 906    {
 907        $line = $this->_readline();
 908        if ($line === '01') {
 909            $this->_stack[] = true;
 910        } elseif ($line === '00') {
 911            $this->_stack[] = false;
 912        } else {
 913            $this->_stack[] = (int)$line;
 914        }
 915    }
 916
 917    /**
 918     * Load a binary integer operator
 919     *
 920     * @return void
 921     */
 922    protected function _loadBinInt()
 923    {
 924        $bin = $this->_read(4);
 925        if (self::$_isLittleEndian === false) {
 926            $bin = strrev($bin);
 927        }
 928        list(, $int)    = unpack('l', $bin);
 929        $this->_stack[] = $int;
 930    }
 931
 932    /**
 933     * Load the first byte of a binary integer
 934     *
 935     * @return void
 936     */
 937    protected function _loadBinInt1()
 938    {
 939        $this->_stack[] = ord($this->_read(1));
 940    }
 941
 942    /**
 943     * Load the second byte of a binary integer
 944     *
 945     * @return void
 946     */
 947    protected function _loadBinInt2()
 948    {
 949        $bin = $this->_read(2);
 950        list(, $int)    = unpack('v', $bin);
 951        $this->_stack[] = $int;
 952    }
 953
 954    /**
 955     * Load a long (float) operator
 956     *
 957     * @return void
 958     */
 959    protected function _loadLong()
 960    {
 961        $data = rtrim($this->_readline(), 'L');
 962        if ($data === '') {
 963            $this->_stack[] = 0;
 964        } else {
 965            $this->_stack[] = $data;
 966        }
 967    }
 968
 969    /**
 970     * Load a one byte long integer
 971     *
 972     * @return void
 973     */
 974    protected function _loadLong1()
 975    {
 976        $n    = ord($this->_read(1));
 977        $data = $this->_read($n);
 978        $this->_stack[] = $this->_decodeBinLong($data);
 979    }
 980
 981    /**
 982     * Load a 4 byte long integer
 983     *
 984     * @return void
 985     */
 986    protected function _loadLong4()
 987    {
 988        $nBin = $this->_read(4);
 989        if (self::$_isLittleEndian === false) {
 990            $nBin = strrev($$nBin);
 991        }
 992        list(, $n) = unpack('l', $nBin);
 993        $data = $this->_read($n);
 994
 995        $this->_stack[] = $this->_decodeBinLong($data);
 996    }
 997
 998    /**
 999     * 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}