PageRenderTime 104ms CodeModel.GetById 21ms app.highlight 56ms RepoModel.GetById 14ms app.codeStats 0ms

/apps/files_external/3rdparty/phpseclib/phpseclib/File/ASN1.php

https://github.com/sezuan/core
PHP | 1277 lines | 783 code | 98 blank | 396 comment | 171 complexity | 8f26058d670da3c6bd4ff63bb33c2f17 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('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                            break;
 519                        case !isset($option['constant']) && $option['type'] == FILE_ASN1_TYPE_CHOICE:
 520                            $v = $this->asn1map($decoded, $option);
 521                            if (isset($v)) {
 522                                $value = $v;
 523                            }
 524                    }
 525                    if (isset($value)) {
 526                        return array($key => $value);
 527                    }
 528                }
 529                return NULL;
 530            case isset($mapping['implicit']):
 531            case isset($mapping['explicit']):
 532            case $decoded['type'] == $mapping['type']:
 533                break;
 534            default:
 535                return NULL;
 536        }
 537
 538        if (isset($mapping['implicit'])) {
 539            $decoded['type'] = $mapping['type'];
 540        }
 541
 542        switch ($decoded['type']) {
 543            case FILE_ASN1_TYPE_SEQUENCE:
 544                $map = array();
 545
 546                // ignore the min and max
 547                if (isset($mapping['min']) && isset($mapping['max'])) {
 548                    $child = $mapping['children'];
 549                    foreach ($decoded['content'] as $content) {
 550                        if (($map[] = $this->asn1map($content, $child)) === NULL) {
 551                            return NULL;
 552                        }
 553                    }
 554
 555                    return $map;
 556                }
 557
 558                $n = count($decoded['content']);
 559                $i = 0;
 560
 561                foreach ($mapping['children'] as $key => $child) {
 562                    $maymatch = $i < $n; // Match only existing input.
 563                    if ($maymatch) {
 564                        $temp = $decoded['content'][$i];
 565
 566                        if ($child['type'] != FILE_ASN1_TYPE_CHOICE) {
 567                            // Get the mapping and input class & constant.
 568                            $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
 569                            $constant = NULL;
 570                            if (isset($temp['constant'])) {
 571                                $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 572                            }
 573                            if (isset($child['class'])) {
 574                                $childClass = $child['class'];
 575                                $constant = $child['cast'];
 576                            }
 577                            elseif (isset($child['constant'])) {
 578                                $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 579                                $constant = $child['constant'];
 580                            }
 581
 582                            if (isset($constant) && isset($temp['constant'])) {
 583                                // Can only match if constants and class match.
 584                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
 585                            } else {
 586                                // Can only match if no constant expected and type matches or is generic.
 587                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], FILE_ASN1_TYPE_ANY, FILE_ASN1_TYPE_CHOICE)) !== false;
 588                            }
 589                        }
 590                    }
 591
 592                    if ($maymatch) {
 593                        // Attempt submapping.
 594                        $candidate = $this->asn1map($temp, $child);
 595                        $maymatch = $candidate !== NULL;
 596                    }
 597
 598                    if ($maymatch) {
 599                        // Got the match: use it.
 600                        $map[$key] = $candidate;
 601                        $i++;
 602                    } elseif (isset($child['default'])) {
 603                        $map[$key] = $child['default']; // Use default.
 604                    } elseif (!isset($child['optional'])) {
 605                        return NULL; // Syntax error.
 606                    }
 607                }
 608
 609                // Fail mapping if all input items have not been consumed.
 610                return $i < $n? NULL: $map;
 611
 612            // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
 613            case FILE_ASN1_TYPE_SET:
 614                $map = array();
 615
 616                // ignore the min and max
 617                if (isset($mapping['min']) && isset($mapping['max'])) {
 618                    $child = $mapping['children'];
 619                    foreach ($decoded['content'] as $content) {
 620                        if (($map[] = $this->asn1map($content, $child)) === NULL) {
 621                            return NULL;
 622                        }
 623                    }
 624
 625                    return $map;
 626                }
 627
 628                for ($i = 0; $i < count($decoded['content']); $i++) {
 629                    $temp = $decoded['content'][$i];
 630                    $tempClass = FILE_ASN1_CLASS_UNIVERSAL;
 631                    if (isset($temp['constant'])) {
 632                        $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 633                    }
 634
 635                    foreach ($mapping['children'] as $key => $child) {
 636                        if (isset($map[$key])) {
 637                            continue;
 638                        }
 639                        $maymatch = true;
 640                        if ($child['type'] != FILE_ASN1_TYPE_CHOICE) {
 641                            $childClass = FILE_ASN1_CLASS_UNIVERSAL;
 642                            $constant = NULL;
 643                            if (isset($child['class'])) {
 644                                $childClass = $child['class'];
 645                                $constant = $child['cast'];
 646                            }
 647                            elseif (isset($child['constant'])) {
 648                                $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC;
 649                                $constant = $child['constant'];
 650                            }
 651
 652                            if (isset($constant) && isset($temp['constant'])) {
 653                                // Can only match if constants and class match.
 654                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
 655                            } else {
 656                                // Can only match if no constant expected and type matches or is generic.
 657                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], FILE_ASN1_TYPE_ANY, FILE_ASN1_TYPE_CHOICE)) !== false;
 658                            }
 659                        }
 660
 661                        if ($maymatch) {
 662                            // Attempt submapping.
 663                            $candidate = $this->asn1map($temp, $child);
 664                            $maymatch = $candidate !== NULL;
 665                        }
 666
 667                        if (!$maymatch) {
 668                            break;
 669                        }
 670
 671                        // Got the match: use it.
 672                        $map[$key] = $candidate;
 673                        break;
 674                    }
 675                }
 676
 677                foreach ($mapping['children'] as $key => $child) {
 678                    if (!isset($map[$key])) {
 679                        if (isset($child['default'])) {
 680                            $map[$key] = $child['default'];
 681                        } elseif (!isset($child['optional'])) {
 682                            return NULL;
 683                        }
 684                    }
 685                }
 686                return $map;
 687            case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
 688                return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
 689            case FILE_ASN1_TYPE_UTC_TIME:
 690            case FILE_ASN1_TYPE_GENERALIZED_TIME:
 691                if (isset($mapping['implicit'])) {
 692                    $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
 693                }
 694                return @date($this->format, $decoded['content']);
 695            case FILE_ASN1_TYPE_BIT_STRING:
 696                if (isset($mapping['mapping'])) {
 697                    $offset = ord($decoded['content'][0]);
 698                    $size = (strlen($decoded['content']) - 1) * 8 - $offset;
 699                    /*
 700                       From X.680-0207.pdf#page=46 (21.7):
 701
 702                       "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
 703                        arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
 704                        therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
 705                        0 bits."
 706                    */
 707                    $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
 708                    for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
 709                        $current = ord($decoded['content'][$i]);
 710                        for ($j = $offset; $j < 8; $j++) {
 711                            $bits[] = (bool) ($current & (1 << $j));
 712                        }
 713                        $offset = 0;
 714                    }
 715                    $values = array();
 716                    $map = array_reverse($mapping['mapping']);
 717                    foreach ($map as $i => $value) {
 718                        if ($bits[$i]) {
 719                            $values[] = $value;
 720                        }
 721                    }
 722                    return $values;
 723                }
 724            case FILE_ASN1_TYPE_OCTET_STRING:
 725                return base64_encode($decoded['content']);
 726            case FILE_ASN1_TYPE_NULL:
 727                return '';
 728            case FILE_ASN1_TYPE_BOOLEAN:
 729                return $decoded['content'];
 730            case FILE_ASN1_TYPE_NUMERIC_STRING:
 731            case FILE_ASN1_TYPE_PRINTABLE_STRING:
 732            case FILE_ASN1_TYPE_TELETEX_STRING:
 733            case FILE_ASN1_TYPE_VIDEOTEX_STRING:
 734            case FILE_ASN1_TYPE_IA5_STRING:
 735            case FILE_ASN1_TYPE_GRAPHIC_STRING:
 736            case FILE_ASN1_TYPE_VISIBLE_STRING:
 737            case FILE_ASN1_TYPE_GENERAL_STRING:
 738            case FILE_ASN1_TYPE_UNIVERSAL_STRING:
 739            case FILE_ASN1_TYPE_UTF8_STRING:
 740            case FILE_ASN1_TYPE_BMP_STRING:
 741                return $decoded['content'];
 742            case FILE_ASN1_TYPE_INTEGER:
 743            case FILE_ASN1_TYPE_ENUMERATED:
 744                $temp = $decoded['content'];
 745                if (isset($mapping['implicit'])) {
 746                    $temp = new Math_BigInteger($decoded['content'], -256);
 747                }
 748                if (isset($mapping['mapping'])) {
 749                    $temp = (int) $temp->toString();
 750                    return isset($mapping['mapping'][$temp]) ?
 751                        $mapping['mapping'][$temp] :
 752                        false;
 753                }
 754                return $temp;
 755        }
 756    }
 757
 758    /**
 759     * ASN.1 Encode
 760     *
 761     * DER-encodes an ASN.1 semantic mapping ($mapping).  Some libraries would probably call this function
 762     * an ASN.1 compiler.
 763     *
 764     * @param String $source
 765     * @param String $mapping
 766     * @param Integer $idx
 767     * @return String
 768     * @access public
 769     */
 770    function encodeDER($source, $mapping)
 771    {
 772        $this->location = array();
 773        return $this->_encode_der($source, $mapping);
 774    }
 775
 776    /**
 777     * ASN.1 Encode (Helper function)
 778     *
 779     * @param String $source
 780     * @param String $mapping
 781     * @param Integer $idx
 782     * @return String
 783     * @access private
 784     */
 785    function _encode_der($source, $mapping, $idx = NULL)
 786    {
 787        if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') {
 788            return $source->element;
 789        }
 790
 791        // do not encode (implicitly optional) fields with value set to default
 792        if (isset($mapping['default']) && $source === $mapping['default']) {
 793            return '';
 794        }
 795
 796        if (isset($idx)) {
 797            $this->location[] = $idx;
 798        }
 799
 800        $tag = $mapping['type'];
 801
 802        switch ($tag) {
 803            case FILE_ASN1_TYPE_SET:    // Children order is not important, thus process in sequence.
 804            case FILE_ASN1_TYPE_SEQUENCE:
 805                $tag|= 0x20; // set the constructed bit
 806                $value = '';
 807
 808                // ignore the min and max
 809                if (isset($mapping['min']) && isset($mapping['max'])) {
 810                    $child = $mapping['children'];
 811
 812                    foreach ($source as $content) {
 813                        $temp = $this->_encode_der($content, $child);
 814                        if ($temp === false) {
 815                            return false;
 816                        }
 817                        $value.= $temp;
 818                    }
 819                    break;
 820                }
 821
 822                foreach ($mapping['children'] as $key => $child) {
 823                    if (!isset($source[$key])) {
 824                        if (!isset($child['optional'])) {
 825                            return false;
 826                        }
 827                        continue;
 828                    }
 829
 830                    $temp = $this->_encode_der($source[$key], $child, $key);
 831                    if ($temp === false) {
 832                        return false;
 833                    }
 834
 835                    // An empty child encoding means it has been optimized out.
 836                    // Else we should have at least one tag byte.
 837                    if ($temp === '') {
 838                        continue;
 839                    }
 840
 841                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
 842                    if (isset($child['constant'])) {
 843                        /*
 844                           From X.680-0207.pdf#page=58 (30.6):
 845
 846                           "The tagging construction specifies explicit tagging if any of the following holds:
 847                            ...
 848                            c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
 849                            AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
 850                            an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
 851                         */
 852                        if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
 853                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
 854                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
 855                        } else {
 856                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
 857                            $temp = $subtag . substr($temp, 1);
 858                        }
 859                    }
 860                    $value.= $temp;
 861                }
 862                break;
 863            case FILE_ASN1_TYPE_CHOICE:
 864                $temp = false;
 865
 866                foreach ($mapping['children'] as $key => $child) {
 867                    if (!isset($source[$key])) {
 868                        continue;
 869                    }
 870
 871                    $temp = $this->_encode_der($source[$key], $child, $key);
 872                    if ($temp === false) {
 873                        return false;
 874                    }
 875
 876                    // An empty child encoding means it has been optimized out.
 877                    // Else we should have at least one tag byte.
 878                    if ($temp === '') {
 879                        continue;
 880                    }
 881
 882                    $tag = ord($temp[0]);
 883
 884                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
 885                    if (isset($child['constant'])) {
 886                        if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) {
 887                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
 888                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
 889                        } else {
 890                            $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
 891                            $temp = $subtag . substr($temp, 1);
 892                        }
 893                    }
 894                }
 895
 896                if (isset($idx)) {
 897                    array_pop($this->location);
 898                }
 899
 900                if ($temp && isset($mapping['cast'])) {
 901                    $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
 902                }
 903
 904                return $temp;
 905            case FILE_ASN1_TYPE_INTEGER:
 906            case FILE_ASN1_TYPE_ENUMERATED:
 907                if (!isset($mapping['mapping'])) {
 908                    $value = $source->toBytes(true);
 909                } else {
 910                    $value = array_search($source, $mapping['mapping']);
 911                    if ($value === false) {
 912                        return false;
 913                    }
 914                    $value = new Math_BigInteger($value);
 915                    $value = $value->toBytes(true);
 916                }
 917                break;
 918            case FILE_ASN1_TYPE_UTC_TIME:
 919            case FILE_ASN1_TYPE_GENERALIZED_TIME:
 920                $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y';
 921                $format.= 'mdHis';
 922                $value = @gmdate($format, strtotime($source)) . 'Z';
 923                break;
 924            case FILE_ASN1_TYPE_BIT_STRING:
 925                if (isset($mapping['mapping'])) {
 926                    $bits = array_fill(0, count($mapping['mapping']), 0);
 927                    $size = 0;
 928                    for ($i = 0; $i < count($mapping['mapping']); $i++) {
 929                        if (in_array($mapping['mapping'][$i], $source)) {
 930                            $bits[$i] = 1;
 931                            $size = $i;
 932                        }
 933                    }
 934
 935                    $offset = 8 - (($size + 1) & 7);
 936                    $offset = $offset !== 8 ? $offset : 0;
 937
 938                    $value = chr($offset);
 939
 940                    for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
 941                        unset($bits[$i]);
 942                    }
 943
 944                    $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
 945                    $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
 946                    foreach ($bytes as $byte) {
 947                        $value.= chr(bindec($byte));
 948                    }
 949
 950                    break;
 951                }
 952            case FILE_ASN1_TYPE_OCTET_STRING:
 953                /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
 954                   the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
 955
 956                   -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
 957                $value = base64_decode($source);
 958                break;
 959            case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
 960                $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
 961                if ($oid === false) {
 962                    user_error('Invalid OID');
 963                    return false;
 964                }
 965                $value = '';
 966                $parts = explode('.', $oid);
 967                $value = chr(40 * $parts[0] + $parts[1]);
 968                for ($i = 2; $i < count($parts); $i++) {
 969                    $temp = '';
 970                    if (!$parts[$i]) {
 971                        $temp = "\0";
 972                    } else {
 973                        while ($parts[$i]) {
 974                            $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
 975                            $parts[$i] >>= 7;
 976                        }
 977                        $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
 978                    }
 979                    $value.= $temp;
 980                }
 981                break;
 982            case FILE_ASN1_TYPE_ANY:
 983                $loc = $this->location;
 984                if (isset($idx)) {
 985                    array_pop($this->location);
 986                }
 987
 988                switch (true) {
 989                    case !isset($source):
 990                        return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping);
 991                    case is_int($source):
 992                    case is_object($source) && strtolower(get_class($source)) == 'math_biginteger':
 993                        return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping);
 994                    case is_float($source):
 995                        return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping);
 996                    case is_bool($source):
 997                        return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping);
 998                    case is_array($source) && count($source) == 1:
 999                        $typename = implode('', array_keys($source));
1000                        $outtype = array_search($typename, $this->ANYmap, true);
1001                        if ($outtype !== false) {
1002                            return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping);
1003                        }
1004                    }
1005
1006                $filters = $this->filters;
1007                foreach ($loc as $part) {
1008                    if (!isset($filters[$part])) {
1009                        $filters = false;
1010                        break;
1011                    }
1012                    $filters = $filters[$part];
1013                }
1014                if ($filters === false) {
1015                    user_error('No filters defined for ' . implode('/', $loc));
1016                    return false;
1017                }
1018                return $this->_encode_der($source, $filters + $mapping);
1019            case FILE_ASN1_TYPE_NULL:
1020                $value = '';
1021                break;
1022            case FILE_ASN1_TYPE_NUMERIC_STRING:
1023            case FILE_ASN1_TYPE_TELETEX_STRING:
1024            case FILE_ASN1_TYPE_PRINTABLE_STRING:
1025            case FILE_ASN1_TYPE_UNIVERSAL_STRING:
1026            case FILE_ASN1_TYPE_UTF8_STRING:
1027            case FILE_ASN1_TYPE_BMP_STRING:
1028            case FILE_ASN1_TYPE_IA5_STRING:
1029            case FILE_ASN1_TYPE_VISIBLE_STRING:
1030            case FILE_ASN1_TYPE_VIDEOTEX_STRING:
1031            case FILE_ASN1_TYPE_GRAPHIC_STRING:
1032            case FILE_ASN1_TYPE_GENERAL_STRING:
1033                $value = $source;
1034                break;
1035            case FILE_ASN1_TYPE_BOOLEAN:
1036                $value = $source ? "\xFF" : "\x00";
1037                break;
1038            default:
1039                user_error('Mapping provides no type definition for ' . implode('/', $this->location));
1040                return false;
1041        }
1042
1043        if (isset($idx)) {
1044            array_pop($this->location);
1045        }
1046
1047        if (isset($mapping['cast'])) {
1048            $tag = ($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast'];
1049        }
1050
1051        return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1052    }
1053
1054    /**
1055     * DER-encode the length
1056     *
1057     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
1058     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information.
1059     *
1060     * @access private
1061     * @param Integer $length
1062     * @return String
1063     */
1064    function _encodeLength($length)
1065    {
1066        if ($length <= 0x7F) {
1067            return chr($length);
1068        }
1069
1070        $temp = ltrim(pack('N', $length), chr(0));
1071        return pack('Ca*', 0x80 | strlen($temp), $temp);
1072    }
1073
1074    /**
1075     * BER-decode the time
1076     *
1077     * Called by _decode_ber() and in the case of implicit tags asn1map().
1078     *
1079     * @access private
1080     * @param String $content
1081     * @param Integer $tag
1082     * @return String
1083     */
1084    function _decodeTime($content, $tag)
1085    {
1086        /* UTCTime:
1087           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1088           http://www.obj-sys.com/asn1tutorial/node15.html
1089
1090           GeneralizedTime:
1091           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1092           http://www.obj-sys.com/asn1tutorial/node14.html */
1093
1094        $pattern = $tag == FILE_ASN1_TYPE_UTC_TIME ?
1095            '#(..)(..)(..)(..)(..)(..)(.*)#' :
1096            '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';
1097
1098        preg_match($pattern, $content, $matches);
1099
1100        list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;
1101
1102        if ($tag == FILE_ASN1_TYPE_UTC_TIME) {
1103            $year = $year >= 50 ? "19$year" : "20$year";
1104        }
1105
1106        if ($timezone == 'Z') {
1107            $mktime = 'gmmktime';
1108            $timezone = 0;
1109        } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
1110            $mktime = 'gmmktime';
1111            $timezone = 60 * $matches[3] + 3600 * $matches[2];
1112            if ($matches[1] == '-') {
1113                $timezone = -$timezone;
1114            }
1115        } else {
1116            $mktime = 'mktime';
1117            $timezone = 0;
1118        }
1119
1120        return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
1121    }
1122
1123    /**
1124     * Set the time format
1125     *
1126     * Sets the time / date format for asn1map().
1127     *
1128     * @access public
1129     * @param String $format
1130     */
1131    function setTimeFormat($format)
1132    {
1133        $this->format = $format;
1134    }
1135
1136    /**
1137     * Load OIDs
1138     *
1139     * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1140     *
1141     * @access public
1142     * @param Array $oids
1143     */
1144    function loadOIDs($oids)
1145    {
1146        $this->oids = $oids;
1147    }
1148
1149    /**
1150     * Load filters
1151     *
1152     * See File_X509, etc, for an example.
1153     *
1154     * @access public
1155     * @param Array $filters
1156     */
1157    function loadFilters($filters)
1158    {
1159        $this->filters = $filters;
1160    }
1161
1162    /**
1163     * String Shift
1164     *
1165     * Inspired by array_shift
1166     *
1167     * @param String $string
1168     * @param optional Integer $index
1169     * @return String
1170     * @access private
1171     */
1172    function _string_shift(&$string, $index = 1)
1173    {
1174        $substr = substr($string, 0, $index);
1175        $string = substr($string, $index);
1176        return $substr;
1177    }
1178
1179    /**
1180     * String type conversion
1181     *
1182     * This is a lazy conversion, dealing only with character size.
1183     * No real conversion table is used.
1184     *
1185     * @param String $in
1186     * @param optional Integer $from
1187     * @param optional Integer $to
1188     * @return String
1189     * @access public
1190     */
1191    function convert($in, $from = FILE_ASN1_TYPE_UTF8_STRING, $to = FILE_ASN1_TYPE_UTF8_STRING)
1192    {
1193        if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1194            return false;
1195        }
1196        $insize = $this->stringTypeSize[$from];
1197        $outsize = $this->stringTypeSize[$to];
1198        $inlength = strlen($in);
1199        $out = '';
1200
1201        for ($i = 0; $i < $inlength;) {
1202            if ($inlength - $i < $insize) {
1203                return false;
1204            }
1205
1206            // Get an input character as a 32-bit value.
1207            $c = ord($in[$i++]);
1208            switch (true) {
1209                case $insize == 4:
1210                    $c = ($c << 8) | ord($in[$i++]);
1211                    $c = ($c << 8) | ord($in[$i++]);
1212                case $insize == 2:
1213                    $c = ($c << 8) | ord($in[$i++]);
1214                case $insize == 1:
1215                    break;
1216                case ($c & 0x80) == 0x00:
1217                    break;
1218                case ($c & 0x40) == 0x00:
1219                    return false;
1220                default:
1221                    $bit = 6;
1222                    do {
1223                        if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1224                            return false;
1225                        }
1226                        $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1227                        $bit += 5;
1228                        $mask = 1 << $bit;
1229                    } while ($c & $bit);
1230                    $c &= $mask - 1;
1231                    break;
1232            }
1233
1234            // Convert and append the character to output string.
1235            $v = '';
1236            switch (true) {
1237                case $outsize == 4:
1238                    $v .= chr($c & 0xFF);
1239                    $c >>= 8;
1240                    $v .= chr($c & 0xFF);
1241                    $c >>= 8;
1242                case $outsize == 2:
1243                    $v .= chr($c & 0xFF);
1244                    $c >>= 8;
1245                case $outsize == 1:
1246                    $v .= chr($c & 0xFF);
1247                    $c >>= 8;
1248                    if ($c) {
1249                        return false;
1250                    }
1251                    break;
1252                case ($c & 0x80000000) != 0:
1253                    return false;
1254                case $c >= 0x04000000:
1255                    $v .= chr(0x80 | ($c & 0x3F));
1256                    $c = ($c >> 6) | 0x04000000;
1257                case $c >= 0x00200000:
1258                    $v .= chr(0x80 | ($c & 0x3F));
1259                    $c = ($c >> 6) | 0x00200000;
1260                case $c >= 0x00010000:
1261                    $v .= chr(0x80 | ($c & 0x3F));
1262                    $c = ($c >> 6) | 0x00010000;
1263  

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