PageRenderTime 57ms CodeModel.GetById 16ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Serializer/Adapter/PythonPickle.php

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