PageRenderTime 53ms CodeModel.GetById 3ms app.highlight 41ms RepoModel.GetById 2ms app.codeStats 0ms

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

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