PageRenderTime 67ms CodeModel.GetById 23ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/trustly/phpseclib/File/ASN1.php

https://github.com/DaveBenNoah/PrestaShop-modules
PHP | 1273 lines | 790 code | 93 blank | 390 comment | 183 complexity | 92a5f946b266316f46576a930312d436 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
   3
   4/**
   5 * Pure-PHP ASN.1 Parser
   6 *
   7 * PHP versions 4 and 5
   8 *
   9 * ASN.1 provides the semantics for data encoded using various schemes.  The most commonly
  10 * utilized scheme is DER or the "Distinguished Encoding Rules".  PEM's are base64 encoded
  11 * DER blobs.
  12 *
  13 * File_ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
  14 *
  15 * Uses the 1988 ASN.1 syntax.
  16 *
  17 * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
  18 * of this software and associated documentation files (the "Software"), to deal
  19 * in the Software without restriction, including without limitation the rights
  20 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  21 * copies of the Software, and to permit persons to whom the Software is
  22 * furnished to do so, subject to the following conditions:
  23 * 
  24 * The above copyright notice and this permission notice shall be included in
  25 * all copies or substantial portions of the Software.
  26 * 
  27 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  29 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  30 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  31 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  32 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  33 * THE SOFTWARE.
  34 *
  35 * @category   File
  36 * @package    File_ASN1
  37 * @author     Jim Wigginton <terrafrost@php.net>
  38 * @copyright  MMXII Jim Wigginton
  39 * @license    http://www.opensource.org/licenses/mit-license.html  MIT License
  40 * @version    $Id$
  41 * @link       http://phpseclib.sourceforge.net
  42 */
  43
  44/**
  45 * Include Math_BigInteger
  46 */
  47if (!class_exists('Math_BigInteger')) {
  48    require_once(dirname(__FILE__).'/../Math/BigInteger.php');
  49}
  50
  51/**#@+
  52 * Tag Classes
  53 *
  54 * @access private
  55 * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
  56 */
  57define('FILE_ASN1_CLASS_UNIVERSAL',        0);
  58define('FILE_ASN1_CLASS_APPLICATION',      1);
  59define('FILE_ASN1_CLASS_CONTEXT_SPECIFIC', 2);
  60define('FILE_ASN1_CLASS_PRIVATE',          3);
  61/**#@-*/
  62
  63/**#@+
  64 * Tag Classes
  65 *
  66 * @access private
  67 * @link http://www.obj-sys.com/asn1tutorial/node124.html
  68 */
  69define('FILE_ASN1_TYPE_BOOLEAN',          1);
  70define('FILE_ASN1_TYPE_INTEGER',          2);
  71define('FILE_ASN1_TYPE_BIT_STRING',       3);
  72define('FILE_ASN1_TYPE_OCTET_STRING',     4);
  73define('FILE_ASN1_TYPE_NULL',             5);
  74define('FILE_ASN1_TYPE_OBJECT_IDENTIFIER',6);
  75//define('FILE_ASN1_TYPE_OBJECT_DESCRIPTOR',7);
  76//define('FILE_ASN1_TYPE_INSTANCE_OF',      8); // EXTERNAL
  77define('FILE_ASN1_TYPE_REAL',             9);
  78define('FILE_ASN1_TYPE_ENUMERATED',      10);
  79//define('FILE_ASN1_TYPE_EMBEDDED',        11);
  80define('FILE_ASN1_TYPE_UTF8_STRING',     12);
  81//define('FILE_ASN1_TYPE_RELATIVE_OID',    13);
  82define('FILE_ASN1_TYPE_SEQUENCE',        16); // SEQUENCE OF
  83define('FILE_ASN1_TYPE_SET',             17); // SET OF
  84/**#@-*/
  85/**#@+
  86 * More Tag Classes
  87 *
  88 * @access private
  89 * @link http://www.obj-sys.com/asn1tutorial/node10.html
  90 */
  91define('FILE_ASN1_TYPE_NUMERIC_STRING',  18);
  92define('FILE_ASN1_TYPE_PRINTABLE_STRING',19);
  93define('FILE_ASN1_TYPE_TELETEX_STRING',  20); // T61String
  94define('FILE_ASN1_TYPE_VIDEOTEX_STRING', 21);
  95define('FILE_ASN1_TYPE_IA5_STRING',      22);
  96define('FILE_ASN1_TYPE_UTC_TIME',        23);
  97define('FILE_ASN1_TYPE_GENERALIZED_TIME',24);
  98define('FILE_ASN1_TYPE_GRAPHIC_STRING',  25);
  99define('FILE_ASN1_TYPE_VISIBLE_STRING',  26); // ISO646String
 100define('FILE_ASN1_TYPE_GENERAL_STRING',  27);
 101define('FILE_ASN1_TYPE_UNIVERSAL_STRING',28);
 102//define('FILE_ASN1_TYPE_CHARACTER_STRING',29);
 103define('FILE_ASN1_TYPE_BMP_STRING',      30);
 104/**#@-*/
 105
 106/**#@+
 107 * Tag Aliases
 108 *
 109 * These tags are kinda place holders for other tags.
 110 *
 111 * @access private
 112 */
 113define('FILE_ASN1_TYPE_CHOICE',          -1);
 114define('FILE_ASN1_TYPE_ANY',             -2);
 115/**#@-*/
 116
 117/**
 118 * ASN.1 Element
 119 *
 120 * Bypass normal encoding rules in File_ASN1::encodeDER()
 121 *
 122 * @author  Jim Wigginton <terrafrost@php.net>
 123 * @version 0.3.0
 124 * @access  public
 125 * @package File_ASN1
 126 */
 127class File_ASN1_Element {
 128    /**
 129     * Raw element value
 130     *
 131     * @var String
 132     * @access private
 133     */
 134    var $element;
 135
 136    /**
 137     * Constructor
 138     *
 139     * @param String $encoded
 140     * @return File_ASN1_Element
 141     * @access public
 142     */
 143    function File_ASN1_Element($encoded)
 144    {
 145        $this->element = $encoded;
 146    }
 147}
 148
 149/**
 150 * Pure-PHP ASN.1 Parser
 151 *
 152 * @author  Jim Wigginton <terrafrost@php.net>
 153 * @version 0.3.0
 154 * @access  public
 155 * @package File_ASN1
 156 */
 157class File_ASN1 {
 158    /**
 159     * ASN.1 object identifier
 160     *
 161     * @var Array
 162     * @access private
 163     * @link http://en.wikipedia.org/wiki/Object_identifier
 164     */
 165    var $oids = array();
 166
 167    /**
 168     * Default date format
 169     *
 170     * @var String
 171     * @access private
 172     * @link http://php.net/class.datetime
 173     */
 174    var $format = 'D, d M y H:i:s O';
 175
 176    /**
 177     * Default date format
 178     *
 179     * @var Array
 180     * @access private
 181     * @see File_ASN1::setTimeFormat()
 182     * @see File_ASN1::asn1map()
 183     * @link http://php.net/class.datetime
 184     */
 185    var $encoded;
 186
 187    /**
 188     * Filters
 189     *
 190     * If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as?
 191     *
 192     * @var Array
 193     * @access private
 194     * @see File_ASN1::_encode_der()
 195     */
 196    var $filters;
 197
 198    /**
 199     * Type mapping table for the ANY type.
 200     *
 201     * Structured or unknown types are mapped to a FILE_ASN1_Element.
 202     * Unambiguous types get the direct mapping (int/real/bool).
 203     * Others are mapped as a choice, with an extra indexing level.
 204     *
 205     * @var Array
 206     * @access public
 207     */
 208    var $ANYmap = array(
 209        FILE_ASN1_TYPE_BOOLEAN              => true,
 210        FILE_ASN1_TYPE_INTEGER              => true,
 211        FILE_ASN1_TYPE_BIT_STRING           => 'bitString',
 212        FILE_ASN1_TYPE_OCTET_STRING         => 'octetString',
 213        FILE_ASN1_TYPE_NULL                 => 'null',
 214        FILE_ASN1_TYPE_OBJECT_IDENTIFIER    => 'objectIdentifier',
 215        FILE_ASN1_TYPE_REAL                 => true,
 216        FILE_ASN1_TYPE_ENUMERATED           => 'enumerated',
 217        FILE_ASN1_TYPE_UTF8_STRING          => 'utf8String',
 218        FILE_ASN1_TYPE_NUMERIC_STRING       => 'numericString',
 219        FILE_ASN1_TYPE_PRINTABLE_STRING     => 'printableString',
 220        FILE_ASN1_TYPE_TELETEX_STRING       => 'teletexString',
 221        FILE_ASN1_TYPE_VIDEOTEX_STRING      => 'videotexString',
 222        FILE_ASN1_TYPE_IA5_STRING           => 'ia5String',
 223        FILE_ASN1_TYPE_UTC_TIME             => 'utcTime',
 224        FILE_ASN1_TYPE_GENERALIZED_TIME     => 'generalTime',
 225        FILE_ASN1_TYPE_GRAPHIC_STRING       => 'graphicString',
 226        FILE_ASN1_TYPE_VISIBLE_STRING       => 'visibleString',
 227        FILE_ASN1_TYPE_GENERAL_STRING       => 'generalString',
 228        FILE_ASN1_TYPE_UNIVERSAL_STRING     => 'universalString',
 229        //FILE_ASN1_TYPE_CHARACTER_STRING     => 'characterString',
 230        FILE_ASN1_TYPE_BMP_STRING           => 'bmpString'
 231    );
 232
 233    /**
 234     * String type to character size mapping table.
 235     *
 236     * Non-convertable types are absent from this table.
 237     * size == 0 indicates variable length encoding.
 238     *
 239     * @var Array
 240     * @access public
 241     */
 242    var $stringTypeSize = array(
 243        FILE_ASN1_TYPE_UTF8_STRING      => 0,
 244        FILE_ASN1_TYPE_BMP_STRING       => 2,
 245        FILE_ASN1_TYPE_UNIVERSAL_STRING => 4,
 246        FILE_ASN1_TYPE_PRINTABLE_STRING => 1,
 247        FILE_ASN1_TYPE_TELETEX_STRING   => 1,
 248        FILE_ASN1_TYPE_IA5_STRING       => 1,
 249        FILE_ASN1_TYPE_VISIBLE_STRING   => 1,
 250    );
 251
 252    /**
 253     * Parse BER-encoding
 254     *
 255     * Serves a similar purpose to openssl's asn1parse
 256     *
 257     * @param String $encoded
 258     * @return Array
 259     * @access public
 260     */
 261    function decodeBER($encoded)
 262    {
 263        if (is_object($encoded) && strtolower(get_class($encoded)) == 'file_asn1_element') {
 264            $encoded = $encoded->element;
 265        }
 266
 267        $this->encoded = $encoded;
 268        return $this->_decode_ber($encoded);
 269    }
 270
 271    /**
 272     * Parse BER-encoding (Helper function)
 273     *
 274     * Sometimes we want to get the BER encoding of a particular tag.  $start lets us do that without having to reencode.
 275     * $encoded is passed by reference for the recursive calls done for FILE_ASN1_TYPE_BIT_STRING and
 276     * FILE_ASN1_TYPE_OCTET_STRING. In those cases, the indefinite length is used.
 277     *
 278     * @param String $encoded
 279     * @param Integer $start
 280     * @return Array
 281     * @access private
 282     */
 283    function _decode_ber(&$encoded, $start = 0)
 284    {
 285        $decoded = array();
 286
 287        while ( strlen($encoded) ) {
 288            $current = array('start' => $start);
 289
 290            $type = ord($this->_string_shift($encoded));
 291            $start++;
 292
 293            $constructed = ($type >> 5) & 1;
 294
 295            $tag = $type & 0x1F;
 296            if ($tag == 0x1F) {
 297                $tag = 0;
 298                // process septets (since the eighth bit is ignored, it's not an octet)
 299                do {
 300                    $loop = ord($encoded[0]) >> 7;
 301                    $tag <<= 7;
 302                    $tag |= ord($this->_string_shift($encoded)) & 0x7F;
 303                    $start++;
 304                } while ( $loop );
 305            }
 306
 307            // Length, as discussed in § 8.1.3 of X.690-0207.pdf#page=13
 308            $length = ord($this->_string_shift($encoded));
 309            $start++;
 310            if ( $length == 0x80 ) { // indefinite length
 311                // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all 
 312                //  immediately available." -- § 8.1.3.2.c
 313                //if ( !$constructed ) {
 314                //    return false;
 315                //}
 316                $length = strlen($encoded);
 317            } elseif ( $length & 0x80 ) { // definite length, long form
 318                // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
 319                // support it up to four.
 320                $length&= 0x7F;
 321                $temp = $this->_string_shift($encoded, $length);
 322                $start+= $length;
 323                extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
 324            }
 325
 326            // End-of-content, see §§ 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
 327            if (!$type && !$length) {
 328                return $decoded;
 329            }
 330            $content = $this->_string_shift($encoded, $length);
 331
 332            /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
 333               built-in types. It defines an application-independent data type that must be distinguishable from all other
 334               data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
 335               have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
 336               a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
 337               alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
 338               data type; the term CONTEXT-SPECIFIC does not appear.
 339
 340                 -- http://www.obj-sys.com/asn1tutorial/node12.html */
 341            $class = ($type >> 6) & 3;
 342            switch ($class) {
 343                case FILE_ASN1_CLASS_APPLICATION:
 344                case FILE_ASN1_CLASS_PRIVATE:
 345                case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
 346                    $decoded[] = array(
 347                        'type'     => $class,
 348                        'constant' => $tag,
 349                        'content'  => $constructed ? $this->_decode_ber($content, $start) : $content,
 350                        'length'   => $length + $start - $current['start']
 351                    ) + $current;
 352                    continue 2;
 353            }
 354
 355            $current+= array('type' => $tag);
 356
 357            // decode UNIVERSAL tags
 358            switch ($tag) {
 359                case FILE_ASN1_TYPE_BOOLEAN:
 360                    // "The contents octets shall consist of a single octet." -- § 8.2.1
 361                    //if (strlen($content) != 1) {
 362                    //    return false;
 363                    //}
 364                    $current['content'] = (bool) ord($content[0]);
 365                    break;
 366                case FILE_ASN1_TYPE_INTEGER:
 367                case FILE_ASN1_TYPE_ENUMERATED:
 368                    $current['content'] = new Math_BigInteger($content, -256);
 369                    break;
 370                case FILE_ASN1_TYPE_REAL: // not currently supported
 371                    return false;
 372                case FILE_ASN1_TYPE_BIT_STRING:
 373                    // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
 374                    // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
 375                    // seven.
 376                    if (!$constructed) {
 377                        $current['content'] = $content;
 378                    } else {
 379                        $temp = $this->_decode_ber($content, $start);
 380                        $length-= strlen($content);
 381                        $last = count($temp) - 1;
 382                        for ($i = 0; $i < $last; $i++) {
 383                            // all subtags should be bit strings
 384                            //if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
 385                            //    return false;
 386                            //}
 387                            $current['content'].= substr($temp[$i]['content'], 1);
 388                        }
 389                        // all subtags should be bit strings
 390                        //if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
 391                        //    return false;
 392                        //}
 393                        $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
 394                    }
 395                    break;
 396                case FILE_ASN1_TYPE_OCTET_STRING:
 397                    if (!$constructed) {
 398                        $current['content'] = $content;
 399                    } else {
 400                        $temp = $this->_decode_ber($content, $start);
 401                        $length-= strlen($content);
 402                        for ($i = 0, $size = count($temp); $i < $size; $i++) {
 403                            // all subtags should be octet strings
 404                            //if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
 405                            //    return false;
 406                            //}
 407                            $current['content'].= $temp[$i]['content'];
 408                        }
 409                        // $length = 
 410                    }
 411                    break;
 412                case FILE_ASN1_TYPE_NULL:
 413                    // "The contents octets shall not contain any octets." -- § 8.8.2
 414                    //if (strlen($content)) {
 415                    //    return false;
 416                    //}
 417                    break;
 418                case FILE_ASN1_TYPE_SEQUENCE:
 419                case FILE_ASN1_TYPE_SET:
 420                    $current['content'] = $this->_decode_ber($content, $start);
 421                    break;
 422                case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
 423                    $temp = ord($this->_string_shift($content));
 424                    $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
 425                    $valuen = 0;
 426                    // process septets
 427                    while (strlen($content)) {
 428                        $temp = ord($this->_string_shift($content));
 429                        $valuen <<= 7;
 430                        $valuen |= $temp & 0x7F;
 431                        if (~$temp & 0x80) {
 432                            $current['content'].= ".$valuen";
 433                            $valuen = 0;
 434                        }
 435                    }
 436                    // the eighth bit of the last byte should not be 1
 437                    //if ($temp >> 7) {
 438                    //    return false;
 439                    //}
 440                    break;
 441                /* Each character string type shall be encoded as if it had been declared:
 442                   [UNIVERSAL x] IMPLICIT OCTET STRING
 443
 444                     -- X.690-0207.pdf#page=23 (§ 8.21.3)
 445
 446                   Per that, we're not going to do any validation.  If there are any illegal characters in the string, 
 447                   we don't really care */
 448                case FILE_ASN1_TYPE_NUMERIC_STRING:
 449                    // 0,1,2,3,4,5,6,7,8,9, and space
 450                case FILE_ASN1_TYPE_PRINTABLE_STRING:
 451                    // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
 452                    // hyphen, full stop, solidus, colon, equal sign, question mark
 453                case FILE_ASN1_TYPE_TELETEX_STRING:
 454                    // The Teletex character set in CCITT's T61, space, and delete
 455                    // see http://en.wikipedia.org/wiki/Teletex#Character_sets
 456                case FILE_ASN1_TYPE_VIDEOTEX_STRING:
 457                    // The Videotex character set in CCITT's T.100 and T.101, space, and delete
 458                case FILE_ASN1_TYPE_VISIBLE_STRING:
 459                    // Printing character sets of international ASCII, and space
 460                case FILE_ASN1_TYPE_IA5_STRING:
 461                    // International Alphabet 5 (International ASCII)
 462                case FILE_ASN1_TYPE_GRAPHIC_STRING:
 463                    // All registered G sets, and space
 464                case FILE_ASN1_TYPE_GENERAL_STRING:
 465                    // All registered C and G sets, space and delete
 466                case FILE_ASN1_TYPE_UTF8_STRING:
 467                    // ????
 468                case FILE_ASN1_TYPE_BMP_STRING:
 469                    $current['content'] = $content;
 470                    break;
 471                case FILE_ASN1_TYPE_UTC_TIME:
 472                case FILE_ASN1_TYPE_GENERALIZED_TIME:
 473                    $current['content'] = $this->_decodeTime($content, $tag);
 474                default:
 475
 476            }
 477
 478            $start+= $length;
 479            $decoded[] = $current + array('length' => $start - $current['start']);
 480        }
 481
 482        return $decoded;
 483    }
 484
 485    /**
 486     * ASN.1 Decode
 487     *
 488     * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
 489     *
 490     * @param Array $decoded
 491     * @param Array $mapping
 492     * @return Array
 493     * @access public
 494     */
 495    function asn1map($decoded, $mapping)
 496    {
 497        if (isset($mapping['explicit'])) {
 498            $decoded = $decoded['content'][0];
 499        }
 500
 501        switch (true) {
 502            case $mapping['type'] == FILE_ASN1_TYPE_ANY:
 503                $intype = $decoded['type'];
 504                if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ($this->encoded[$decoded['start']] & 0x20)) {
 505                    return new File_ASN1_Element(substr($this->encoded, $decoded['start'], $decoded['length']));
 506                }
 507                $inmap = $this->ANYmap[$intype];
 508                if (is_string($inmap)) {
 509                    return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping));
 510                }
 511                break;
 512            case $mapping['type'] == FILE_ASN1_TYPE_CHOICE:
 513                foreach ($mapping['children'] as $key => $option) {
 514                    switch (true) {
 515                        case isset($option['constant']) && $option['constant'] == $decoded['constant']:
 516                        case !isset($option['constant']) && $option['type'] == $decoded['type']:
 517                            $value = $this->asn1map($decoded, $option);
 518                    }
 519                    if (isset($value)) {
 520                        return array($key => $value);
 521                    }
 522                }
 523                return NULL;
 524            case isset($mapping['implicit']):
 525            case isset($mapping['explicit']):
 526            case $decoded['type'] == $mapping['type']:
 527                break;
 528            default:
 529                return NULL;
 530        }
 531
 532        if (isset($mapping['implicit'])) {
 533            $decoded['type'] = $mapping['type'];
 534        }
 535
 536        switch ($decoded['type']) {
 537            case FILE_ASN1_TYPE_SEQUENCE:
 538                $map = array();
 539
 540                if (empty($decoded['content'])) {
 541                    return $map;
 542                }
 543
 544                // ignore the min and max
 545                if (isset($mapping['min']) && isset($mapping['max'])) {
 546                    $child = $mapping['children'];
 547                    foreach ($decoded['content'] as $content) {
 548                        $map[] = $this->asn1map($content, $child);
 549                    }
 550                    return $map;
 551                }
 552
 553                $temp = $decoded['content'][$i = 0];
 554                foreach ($mapping['children'] as $key => $child) {
 555                    if (!isset($child['optional']) && $child['type'] == FILE_ASN1_TYPE_CHOICE) {
 556                        $map[$key] = $this->asn1map($temp, $child);
 557                        $i++;
 558                        if (count($decoded['content']) == $i) {
 559                            break;
 560                        }
 561                        $temp = $decoded['content'][$i];
 562                        continue;
 563                    }
 564
 565                    $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
 566                    $constant = NULL;
 567                    if (isset($temp['constant'])) {
 568                        $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 569                    }
 570                    if (isset($child['class'])) {
 571                        $childClass = $child['class'];
 572                        $constant = $child['cast'];
 573                    }
 574                    elseif (isset($child['constant'])) {
 575                        $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 576                        $constant = $child['constant'];
 577                    }
 578
 579                    if (isset($child['optional'])) {
 580                        if (isset($constant) && isset($temp['constant'])) {
 581                            if (($constant == $temp['constant']) && ($childClass == $tempClass)) {
 582                                $map[$key] = $this->asn1map($temp, $child);
 583                                $i++;
 584                                if (count($decoded['content']) == $i) {
 585                                    break;
 586                                }
 587                                $temp = $decoded['content'][$i];
 588                            }
 589                        } elseif (!isset($child['constant'])) {
 590                            // we could do this, as well:
 591                            // $buffer = $this->asn1map($temp, $child); if (isset($buffer)) { $map[$key] = $buffer; }
 592                            if ($child['type'] == $temp['type'] || $child['type'] == FILE_ASN1_TYPE_ANY) {
 593                                $map[$key] = $this->asn1map($temp, $child);
 594                                $i++;
 595                                if (count($decoded['content']) == $i) {
 596                                    break;
 597                                }
 598                                $temp = $decoded['content'][$i];
 599                            } elseif ($child['type'] == FILE_ASN1_TYPE_CHOICE) {
 600                                $candidate = $this->asn1map($temp, $child);
 601                                if (!empty($candidate)) {
 602                                    $map[$key] = $candidate;
 603                                    $i++;
 604                                    if (count($decoded['content']) == $i) {
 605                                        break;
 606                                    }
 607                                    $temp = $decoded['content'][$i];
 608                                }
 609                            }
 610                        }
 611
 612                        if (!isset($map[$key]) && isset($child['default'])) {
 613                            $map[$key] = $child['default'];
 614                        }
 615                    } else {
 616                        $map[$key] = $this->asn1map($temp, $child);
 617                        $i++;
 618                        if (count($decoded['content']) == $i) {
 619                            break;
 620                        }
 621                        $temp = $decoded['content'][$i];
 622                    }
 623                }
 624
 625                return $map;
 626            // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
 627            case FILE_ASN1_TYPE_SET:
 628                $map = array();
 629
 630                // ignore the min and max
 631                if (isset($mapping['min']) && isset($mapping['max'])) {
 632                    $child = $mapping['children'];
 633                    foreach ($decoded['content'] as $content) {
 634                        $map[] = $this->asn1map($content, $child);
 635                    }
 636
 637                    return $map;
 638                }
 639
 640                for ($i = 0; $i < count($decoded['content']); $i++) {
 641                    foreach ($mapping['children'] as $key => $child) {
 642                        $temp = $decoded['content'][$i];
 643
 644                        if (!isset($child['optional']) && $child['type'] == FILE_ASN1_TYPE_CHOICE) {
 645                            $map[$key] = $this->asn1map($temp, $child);
 646                            continue;
 647                        }
 648
 649                        $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
 650                        $constant = NULL;
 651                        if (isset($temp['constant'])) {
 652                            $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 653                        }
 654                        if (isset($child['class'])) {
 655                            $childClass = $child['class'];
 656                            $constant = $child['cast'];
 657                        }
 658                        elseif (isset($child['constant'])) {
 659                            $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 660                            $constant = $child['constant'];
 661                        }
 662
 663                        if (isset($constant) && isset($temp['constant'])) {
 664                            if (($constant == $temp['constant']) && ($childClass == $tempClass)) {
 665                                $map[$key] = $this->asn1map($temp['content'], $child);
 666                            }
 667                        } elseif (!isset($child['constant'])) {
 668                            // we could do this, as well:
 669                            // $buffer = $this->asn1map($temp['content'], $child); if (isset($buffer)) { $map[$key] = $buffer; }
 670                            if ($child['type'] == $temp['type']) {
 671                                $map[$key] = $this->asn1map($temp, $child);
 672                            }
 673                        }
 674                    }
 675                }
 676
 677                foreach ($mapping['children'] as $key => $child) {
 678                    if (!isset($map[$key]) && isset($child['default'])) {
 679                        $map[$key] = $child['default'];
 680                    }
 681                }
 682                return $map;
 683            case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
 684                return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
 685            case FILE_ASN1_TYPE_UTC_TIME:
 686            case FILE_ASN1_TYPE_GENERALIZED_TIME:
 687                if (isset($mapping['implicit'])) {
 688                    $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
 689                }
 690                return @date($this->format, $decoded['content']);
 691            case FILE_ASN1_TYPE_BIT_STRING:
 692                if (isset($mapping['mapping'])) {
 693                    $offset = ord($decoded['content'][0]);
 694                    $size = (strlen($decoded['content']) - 1) * 8 - $offset;
 695                    /*
 696                       From X.680-0207.pdf#page=46 (21.7):
 697
 698                       "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
 699                        arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
 700                        therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
 701                        0 bits."
 702                    */
 703                    $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
 704                    for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
 705                        $current = ord($decoded['content'][$i]);
 706                        for ($j = $offset; $j < 8; $j++) {
 707                            $bits[] = (bool) ($current & (1 << $j));
 708                        }
 709                        $offset = 0;
 710                    }
 711                    $values = array();
 712                    $map = array_reverse($mapping['mapping']);
 713                    foreach ($map as $i => $value) {
 714                        if ($bits[$i]) {
 715                            $values[] = $value;
 716                        }
 717                    }
 718                    return $values;
 719                }
 720            case FILE_ASN1_TYPE_OCTET_STRING:
 721                return base64_encode($decoded['content']);
 722            case FILE_ASN1_TYPE_NULL:
 723                return '';
 724            case FILE_ASN1_TYPE_BOOLEAN:
 725                return $decoded['content'];
 726            case FILE_ASN1_TYPE_NUMERIC_STRING:
 727            case FILE_ASN1_TYPE_PRINTABLE_STRING:
 728            case FILE_ASN1_TYPE_TELETEX_STRING:
 729            case FILE_ASN1_TYPE_VIDEOTEX_STRING:
 730            case FILE_ASN1_TYPE_IA5_STRING:
 731            case FILE_ASN1_TYPE_GRAPHIC_STRING:
 732            case FILE_ASN1_TYPE_VISIBLE_STRING:
 733            case FILE_ASN1_TYPE_GENERAL_STRING:
 734            case FILE_ASN1_TYPE_UNIVERSAL_STRING:
 735            case FILE_ASN1_TYPE_UTF8_STRING:
 736            case FILE_ASN1_TYPE_BMP_STRING:
 737                return $decoded['content'];
 738            case FILE_ASN1_TYPE_INTEGER:
 739            case FILE_ASN1_TYPE_ENUMERATED:
 740                $temp = $decoded['content'];
 741                if (isset($mapping['implicit'])) {
 742                    $temp = new Math_BigInteger($decoded['content'], -256);
 743                }
 744                if (isset($mapping['mapping'])) {
 745                    $temp = (int) $temp->toString();
 746                    return isset($mapping['mapping'][$temp]) ?
 747                        $mapping['mapping'][$temp] :
 748                        false;
 749                }
 750                return $temp;
 751        }
 752    }
 753
 754    /**
 755     * ASN.1 Encode
 756     *
 757     * DER-encodes an ASN.1 semantic mapping ($mapping).  Some libraries would probably call this function
 758     * an ASN.1 compiler.
 759     *
 760     * @param String $source
 761     * @param String $mapping
 762     * @param Integer $idx
 763     * @return String
 764     * @access public
 765     */
 766    function encodeDER($source, $mapping)
 767    {
 768        $this->location = array();
 769        return $this->_encode_der($source, $mapping);
 770    }
 771
 772    /**
 773     * ASN.1 Encode (Helper function)
 774     *
 775     * @param String $source
 776     * @param String $mapping
 777     * @param Integer $idx
 778     * @return String
 779     * @access private
 780     */
 781    function _encode_der($source, $mapping, $idx = NULL)
 782    {
 783        if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') {
 784            return $source->element;
 785        }
 786
 787        // do not encode (implicitly optional) fields with value set to default
 788        if (isset($mapping['default']) && $source === $mapping['default']) {
 789            return '';
 790        }
 791
 792        if (isset($idx)) {
 793            $this->location[] = $idx;
 794        }
 795
 796        $tag = $mapping['type'];
 797
 798        switch ($tag) {
 799            case FILE_ASN1_TYPE_SET:    // Children order is not important, thus process in sequence.
 800            case FILE_ASN1_TYPE_SEQUENCE:
 801                $tag|= 0x20; // set the constructed bit
 802                $value = '';
 803
 804                // ignore the min and max
 805                if (isset($mapping['min']) && isset($mapping['max'])) {
 806                    $child = $mapping['children'];
 807
 808                    foreach ($source as $content) {
 809                        $temp = $this->_encode_der($content, $child);
 810                        if ($temp === false) {
 811                            return false;
 812                        }
 813                        $value.= $temp;
 814                    }
 815                    break;
 816                }
 817
 818                foreach ($mapping['children'] as $key => $child) {
 819                    if (!isset($source[$key])) {
 820                        if (!isset($child['optional'])) {
 821                            return false;
 822                        }
 823                        continue;
 824                    }
 825
 826                    $temp = $this->_encode_der($source[$key], $child, $key);
 827                    if ($temp === false) {
 828                        return false;
 829                    }
 830
 831                    // An empty child encoding means it has been optimized out.
 832                    // Else we should have at least one tag byte.
 833                    if ($temp === '') {
 834                        continue;
 835                    }
 836
 837                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
 838                    if (isset($child['constant'])) {
 839                        /*
 840                           From X.680-0207.pdf#page=58 (30.6):
 841
 842                           "The tagging construction specifies explicit tagging if any of the following holds:
 843                            ...
 844                            c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
 845                            AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
 846                            an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
 847                         */
 848                        if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
 849                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
 850                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
 851                        } else {
 852                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
 853                            $temp = $subtag . substr($temp, 1);
 854                        }
 855                    }
 856                    $value.= $temp;
 857                }
 858                break;
 859            case FILE_ASN1_TYPE_CHOICE:
 860                $temp = false;
 861
 862                foreach ($mapping['children'] as $key => $child) {
 863                    if (!isset($source[$key])) {
 864                        continue;
 865                    }
 866
 867                    $temp = $this->_encode_der($source[$key], $child, $key);
 868                    if ($temp === false) {
 869                        return false;
 870                    }
 871
 872                    // An empty child encoding means it has been optimized out.
 873                    // Else we should have at least one tag byte.
 874                    if ($temp === '') {
 875                        continue;
 876                    }
 877
 878                    $tag = ord($temp[0]);
 879
 880                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
 881                    if (isset($child['constant'])) {
 882                        if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
 883                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
 884                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
 885                        } else {
 886                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
 887                            $temp = $subtag . substr($temp, 1);
 888                        }
 889                    }
 890                }
 891
 892                if (isset($idx)) {
 893                    array_pop($this->location);
 894                }
 895
 896                if ($temp && isset($mapping['cast'])) {
 897                    $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
 898                }
 899
 900                return $temp;
 901            case FILE_ASN1_TYPE_INTEGER:
 902            case FILE_ASN1_TYPE_ENUMERATED:
 903                if (!isset($mapping['mapping'])) {
 904                    $value = $source->toBytes(true);
 905                } else {
 906                    $value = array_search($source, $mapping['mapping']);
 907                    if ($value === false) {
 908                        return false;
 909                    }
 910                    $value = new Math_BigInteger($value);
 911                    $value = $value->toBytes(true);
 912                }
 913                break;
 914            case FILE_ASN1_TYPE_UTC_TIME:
 915            case FILE_ASN1_TYPE_GENERALIZED_TIME:
 916                $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y';
 917                $format.= 'mdHis';
 918                $value = @gmdate($format, strtotime($source)) . 'Z';
 919                break;
 920            case FILE_ASN1_TYPE_BIT_STRING:
 921                if (isset($mapping['mapping'])) {
 922                    $bits = array_fill(0, count($mapping['mapping']), 0);
 923                    $size = 0;
 924                    for ($i = 0; $i < count($mapping['mapping']); $i++) {
 925                        if (in_array($mapping['mapping'][$i], $source)) {
 926                            $bits[$i] = 1;
 927                            $size = $i;
 928                        }
 929                    }
 930
 931                    $offset = 8 - (($size + 1) & 7);
 932                    $offset = $offset !== 8 ? $offset : 0;
 933
 934                    $value = chr($offset);
 935
 936                    for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
 937                        unset($bits[$i]);
 938                    }
 939
 940                    $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
 941                    $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
 942                    foreach ($bytes as $byte) {
 943                        $value.= chr(bindec($byte));
 944                    }
 945
 946                    break;
 947                }
 948            case FILE_ASN1_TYPE_OCTET_STRING:
 949                /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
 950                   the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
 951
 952                   -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
 953                $value = base64_decode($source);
 954                break;
 955            case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
 956                $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
 957                if ($oid === false) {
 958                    user_error('Invalid OID', E_USER_NOTICE);
 959                    return false;
 960                }
 961                $value = '';
 962                $parts = explode('.', $oid);
 963                $value = chr(40 * $parts[0] + $parts[1]);
 964                for ($i = 2; $i < count($parts); $i++) {
 965                    $temp = '';
 966                    if (!$parts[$i]) {
 967                        $temp = "\0";
 968                    } else {
 969                        while ($parts[$i]) {
 970                            $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
 971                            $parts[$i] >>= 7;
 972                        }
 973                        $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
 974                    }
 975                    $value.= $temp;
 976                }
 977                break;
 978            case FILE_ASN1_TYPE_ANY:
 979                $loc = $this->location;
 980                if (isset($idx)) {
 981                    array_pop($this->location);
 982                }
 983
 984                switch (true) {
 985                    case !isset($source):
 986                        return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping);
 987                    case is_int($source):
 988                    case is_object($source) && strtolower(get_class($source)) == 'math_biginteger':
 989                        return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping);
 990                    case is_float($source):
 991                        return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping);
 992                    case is_bool($source):
 993                        return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping);
 994                    case is_array($source) && count($source) == 1:
 995                        $typename = implode('', array_keys($source));
 996                        $outtype = array_search($typename, $this->ANYmap, true);
 997                        if ($outtype !== false) {
 998                            return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping);
 999                        }
1000                    }
1001
1002                $filters = $this->filters;
1003                foreach ($loc as $part) {
1004                    if (!isset($filters[$part])) {
1005                        $filters = false;
1006                        break;
1007                    }
1008                    $filters = $filters[$part];
1009                }
1010                if ($filters === false) {
1011                    user_error('No filters defined for ' . implode('/', $loc), E_USER_NOTICE);
1012                    return false;
1013                }
1014                return $this->_encode_der($source, $filters + $mapping);
1015            case FILE_ASN1_TYPE_NULL:
1016                $value = '';
1017                break;
1018            case FILE_ASN1_TYPE_NUMERIC_STRING:
1019            case FILE_ASN1_TYPE_TELETEX_STRING:
1020            case FILE_ASN1_TYPE_PRINTABLE_STRING:
1021            case FILE_ASN1_TYPE_UNIVERSAL_STRING:
1022            case FILE_ASN1_TYPE_UTF8_STRING:
1023            case FILE_ASN1_TYPE_BMP_STRING:
1024            case FILE_ASN1_TYPE_IA5_STRING:
1025            case FILE_ASN1_TYPE_VISIBLE_STRING:
1026            case FILE_ASN1_TYPE_VIDEOTEX_STRING:
1027            case FILE_ASN1_TYPE_GRAPHIC_STRING:
1028            case FILE_ASN1_TYPE_GENERAL_STRING:
1029                $value = $source;
1030                break;
1031            case FILE_ASN1_TYPE_BOOLEAN:
1032                $value = $source ? "\xFF" : "\x00";
1033                break;
1034            default:
1035                user_error('Mapping provides no type definition for ' . implode('/', $this->location), E_USER_NOTICE);
1036                return false;
1037        }
1038
1039        if (isset($idx)) {
1040            array_pop($this->location);
1041        }
1042
1043        if (isset($mapping['cast'])) {
1044            $tag = ($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast'];
1045        }
1046
1047        return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1048    }
1049
1050    /**
1051     * DER-encode the length
1052     *
1053     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
1054     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information.
1055     *
1056     * @access private
1057     * @param Integer $length
1058     * @return String
1059     */
1060    function _encodeLength($length)
1061    {
1062        if ($length <= 0x7F) {
1063            return chr($length);
1064        }
1065
1066        $temp = ltrim(pack('N', $length), chr(0));
1067        return pack('Ca*', 0x80 | strlen($temp), $temp);
1068    }
1069
1070    /**
1071     * BER-decode the time
1072     *
1073     * Called by _decode_ber() and in the case of implicit tags asn1map().
1074     *
1075     * @access private
1076     * @param String $content
1077     * @param Integer $tag
1078     * @return String
1079     */
1080    function _decodeTime($content, $tag)
1081    {
1082        /* UTCTime:
1083           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1084           http://www.obj-sys.com/asn1tutorial/node15.html
1085
1086           GeneralizedTime:
1087           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1088           http://www.obj-sys.com/asn1tutorial/node14.html */
1089
1090        $pattern = $tag == FILE_ASN1_TYPE_UTC_TIME ?
1091            '#(..)(..)(..)(..)(..)(..)(.*)#' :
1092            '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';
1093
1094        preg_match($pattern, $content, $matches);
1095
1096        list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;
1097
1098        if ($tag == FILE_ASN1_TYPE_UTC_TIME) {
1099            $year = $year >= 50 ? "19$year" : "20$year";
1100        }
1101
1102        if ($timezone == 'Z') {
1103            $mktime = 'gmmktime';
1104            $timezone = 0;
1105        } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
1106            $mktime = 'gmmktime';
1107            $timezone = 60 * $matches[3] + 3600 * $matches[2];
1108            if ($matches[1] == '-') {
1109                $timezone = -$timezone;
1110            }
1111        } else {
1112            $mktime = 'mktime';
1113            $timezone = 0;
1114        }
1115
1116        return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
1117    }
1118
1119    /**
1120     * Set the time format
1121     *
1122     * Sets the time / date format for asn1map().
1123     *
1124     * @access public
1125     * @param String $format
1126     */
1127    function setTimeFormat($format)
1128    {
1129        $this->format = $format;
1130    }
1131
1132    /**
1133     * Load OIDs
1134     *
1135     * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1136     *
1137     * @access public
1138     * @param Array $oids
1139     */
1140    function loadOIDs($oids)
1141    {
1142        $this->oids = $oids;
1143    }
1144
1145    /**
1146     * Load filters
1147     *
1148     * See File_X509, etc, for an example.
1149     *
1150     * @access public
1151     * @param Array $filters
1152     */
1153    function loadFilters($filters)
1154    {
1155        $this->filters = $filters;
1156    }
1157
1158    /**
1159     * String Shift
1160     *
1161     * Inspired by array_shift
1162     *
1163     * @param String $string
1164     * @param optional Integer $index
1165     * @return String
1166     * @access private
1167     */
1168    function _string_shift(&$string, $index = 1)
1169    {
1170        $substr = substr($string, 0, $index);
1171        $string = substr($string, $index);
1172        return $substr;
1173    }
1174
1175    /**
1176     * String type conversion
1177     *
1178     * This is a lazy conversion, dealing only with character size.
1179     * No real conversion table is used.
1180     *
1181     * @param String $in
1182     * @param optional Integer $from
1183     * @param optional Integer $to
1184     * @return String
1185     * @access public
1186     */
1187    function convert($in, $from = FILE_ASN1_TYPE_UTF8_STRING, $to = FILE_ASN1_TYPE_UTF8_STRING)
1188    {
1189        if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1190            return false;
1191        }
1192        $insize = $this->stringTypeSize[$from];
1193        $outsize = $this->stringTypeSize[$to];
1194        $inlength = strlen($in);
1195        $out = '';
1196
1197        for ($i = 0; $i < $inlength;) {
1198            if ($inlength - $i < $insize) {
1199                return false;
1200            }
1201
1202            // Get an input character as a 32-bit value.
1203            $c = ord($in[$i++]);
1204            switch (true) {
1205                case $insize == 4:
1206                    $c = ($c << 8) | ord($in[$i++]);
1207                    $c = ($c << 8) | ord($in[$i++]);
1208                case $insize == 2:
1209                    $c = ($c << 8) | ord($in[$i++]);
1210                case $insize == 1:
1211                    break;
1212                case ($c & 0x80) == 0x00:
1213                    break;
1214                case ($c & 0x40) == 0x00:
1215                    return false;
1216                default:
1217                    $bit = 6;
1218                    do {
1219                        if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1220                            return false;
1221                        }
1222                        $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1223                        $bit += 5;
1224                        $mask = 1 << $bit;
1225                    } while ($c & $bit);
1226                    $c &= $mask - 1;
1227                    break;
1228            }
1229
1230            // Convert and append the character to output string.
1231            $v = '';
1232            switch (true) {
1233                case $outsize == 4:
1234                    $v .= chr($c & 0xFF);
1235                    $c >>= 8;
1236                    $v .= chr($c & 0xFF);
1237                    $c >>= 8;
1238                case $outsize == 2:
1239                    $v .= chr($c & 0xFF);
1240                    $c >>= 8;
1241                case $outsize == 1:
1242                    $v .= chr($c & 0xFF);
1243                    $c >>= 8;
1244                    if ($c) {
1245                        return false;
1246                    }
1247                    break;
1248                case ($c & 0x80000000) != 0:
1249                    return false;
1250                case $c >= 0x04000000:
1251                    $v .= chr(0x80 | ($c & 0x3F));
1252                    $c = ($c >> 6) | 0x04000000;
1253                case $c >= 0x00200000:
1254                    $v .= chr(0x80 | ($c & 0x3F));
1255                    $c = ($c >> 6) | 0x00200000;
1256                case $c >= 0x00010000:
1257                    $v .= chr(0x80 | ($c & 0x3F));
1258               

Large files files are truncated, but you can click here to view the full file