PageRenderTime 570ms CodeModel.GetById 121ms app.highlight 272ms RepoModel.GetById 121ms app.codeStats 0ms

/phpseclib/File/ASN1.php

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

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