1<?php
2
3/**
4 * Pure-PHP ASN.1 Parser
5 *
6 * PHP version 5
7 *
8 * ASN.1 provides the semantics for data encoded using various schemes.  The most commonly
9 * utilized scheme is DER or the "Distinguished Encoding Rules".  PEM's are base64 encoded
10 * DER blobs.
11 *
12 * \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
13 *
14 * Uses the 1988 ASN.1 syntax.
15 *
16 * @category  File
17 * @package   ASN1
18 * @author    Jim Wigginton <terrafrost@php.net>
19 * @copyright 2012 Jim Wigginton
20 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
21 * @link      http://phpseclib.sourceforge.net
22 */
23
24namespace phpseclib\File;
25
26use phpseclib\File\ASN1\Element;
27use phpseclib\Math\BigInteger;
28use DateTime;
29use DateTimeZone;
30
31/**
32 * Pure-PHP ASN.1 Parser
33 *
34 * @package ASN1
35 * @author  Jim Wigginton <terrafrost@php.net>
36 * @access  public
37 */
38class ASN1
39{
40    /**#@+
41     * Tag Classes
42     *
43     * @access private
44     * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
45     */
46    const CLASS_UNIVERSAL        = 0;
47    const CLASS_APPLICATION      = 1;
48    const CLASS_CONTEXT_SPECIFIC = 2;
49    const CLASS_PRIVATE          = 3;
50    /**#@-*/
51
52    /**#@+
53     * Tag Classes
54     *
55     * @access private
56     * @link http://www.obj-sys.com/asn1tutorial/node124.html
57    */
58    const TYPE_BOOLEAN           = 1;
59    const TYPE_INTEGER           = 2;
60    const TYPE_BIT_STRING        = 3;
61    const TYPE_OCTET_STRING      = 4;
62    const TYPE_NULL              = 5;
63    const TYPE_OBJECT_IDENTIFIER = 6;
64    //const TYPE_OBJECT_DESCRIPTOR = 7;
65    //const TYPE_INSTANCE_OF       = 8; // EXTERNAL
66    const TYPE_REAL              = 9;
67    const TYPE_ENUMERATED        = 10;
68    //const TYPE_EMBEDDED          = 11;
69    const TYPE_UTF8_STRING       = 12;
70    //const TYPE_RELATIVE_OID      = 13;
71    const TYPE_SEQUENCE          = 16; // SEQUENCE OF
72    const TYPE_SET               = 17; // SET OF
73    /**#@-*/
74    /**#@+
75     * More Tag Classes
76     *
77     * @access private
78     * @link http://www.obj-sys.com/asn1tutorial/node10.html
79    */
80    const TYPE_NUMERIC_STRING   = 18;
81    const TYPE_PRINTABLE_STRING = 19;
82    const TYPE_TELETEX_STRING   = 20; // T61String
83    const TYPE_VIDEOTEX_STRING  = 21;
84    const TYPE_IA5_STRING       = 22;
85    const TYPE_UTC_TIME         = 23;
86    const TYPE_GENERALIZED_TIME = 24;
87    const TYPE_GRAPHIC_STRING   = 25;
88    const TYPE_VISIBLE_STRING   = 26; // ISO646String
89    const TYPE_GENERAL_STRING   = 27;
90    const TYPE_UNIVERSAL_STRING = 28;
91    //const TYPE_CHARACTER_STRING = 29;
92    const TYPE_BMP_STRING       = 30;
93    /**#@-*/
94
95    /**#@+
96     * Tag Aliases
97     *
98     * These tags are kinda place holders for other tags.
99     *
100     * @access private
101    */
102    const TYPE_CHOICE = -1;
103    const TYPE_ANY    = -2;
104    /**#@-*/
105
106    /**
107     * ASN.1 object identifier
108     *
109     * @var array
110     * @access private
111     * @link http://en.wikipedia.org/wiki/Object_identifier
112     */
113    var $oids = array();
114
115    /**
116     * Default date format
117     *
118     * @var string
119     * @access private
120     * @link http://php.net/class.datetime
121     */
122    var $format = 'D, d M Y H:i:s O';
123
124    /**
125     * Default date format
126     *
127     * @var array
128     * @access private
129     * @see self::setTimeFormat()
130     * @see self::asn1map()
131     * @link http://php.net/class.datetime
132     */
133    var $encoded;
134
135    /**
136     * Filters
137     *
138     * If the mapping type is self::TYPE_ANY what do we actually encode it as?
139     *
140     * @var array
141     * @access private
142     * @see self::_encode_der()
143     */
144    var $filters;
145
146    /**
147     * Type mapping table for the ANY type.
148     *
149     * Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element.
150     * Unambiguous types get the direct mapping (int/real/bool).
151     * Others are mapped as a choice, with an extra indexing level.
152     *
153     * @var array
154     * @access public
155     */
156    var $ANYmap = array(
157        self::TYPE_BOOLEAN              => true,
158        self::TYPE_INTEGER              => true,
159        self::TYPE_BIT_STRING           => 'bitString',
160        self::TYPE_OCTET_STRING         => 'octetString',
161        self::TYPE_NULL                 => 'null',
162        self::TYPE_OBJECT_IDENTIFIER    => 'objectIdentifier',
163        self::TYPE_REAL                 => true,
164        self::TYPE_ENUMERATED           => 'enumerated',
165        self::TYPE_UTF8_STRING          => 'utf8String',
166        self::TYPE_NUMERIC_STRING       => 'numericString',
167        self::TYPE_PRINTABLE_STRING     => 'printableString',
168        self::TYPE_TELETEX_STRING       => 'teletexString',
169        self::TYPE_VIDEOTEX_STRING      => 'videotexString',
170        self::TYPE_IA5_STRING           => 'ia5String',
171        self::TYPE_UTC_TIME             => 'utcTime',
172        self::TYPE_GENERALIZED_TIME     => 'generalTime',
173        self::TYPE_GRAPHIC_STRING       => 'graphicString',
174        self::TYPE_VISIBLE_STRING       => 'visibleString',
175        self::TYPE_GENERAL_STRING       => 'generalString',
176        self::TYPE_UNIVERSAL_STRING     => 'universalString',
177        //self::TYPE_CHARACTER_STRING     => 'characterString',
178        self::TYPE_BMP_STRING           => 'bmpString'
179    );
180
181    /**
182     * String type to character size mapping table.
183     *
184     * Non-convertable types are absent from this table.
185     * size == 0 indicates variable length encoding.
186     *
187     * @var array
188     * @access public
189     */
190    var $stringTypeSize = array(
191        self::TYPE_UTF8_STRING      => 0,
192        self::TYPE_BMP_STRING       => 2,
193        self::TYPE_UNIVERSAL_STRING => 4,
194        self::TYPE_PRINTABLE_STRING => 1,
195        self::TYPE_TELETEX_STRING   => 1,
196        self::TYPE_IA5_STRING       => 1,
197        self::TYPE_VISIBLE_STRING   => 1,
198    );
199
200    /**
201     * Parse BER-encoding
202     *
203     * Serves a similar purpose to openssl's asn1parse
204     *
205     * @param string $encoded
206     * @return array
207     * @access public
208     */
209    function decodeBER($encoded)
210    {
211        if ($encoded instanceof Element) {
212            $encoded = $encoded->element;
213        }
214
215        $this->encoded = $encoded;
216        // encapsulate in an array for BC with the old decodeBER
217        return array($this->_decode_ber($encoded));
218    }
219
220    /**
221     * Parse BER-encoding (Helper function)
222     *
223     * Sometimes we want to get the BER encoding of a particular tag.  $start lets us do that without having to reencode.
224     * $encoded is passed by reference for the recursive calls done for self::TYPE_BIT_STRING and
225     * self::TYPE_OCTET_STRING. In those cases, the indefinite length is used.
226     *
227     * @param string $encoded
228     * @param int $start
229     * @param int $encoded_pos
230     * @return array
231     * @access private
232     */
233    function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
234    {
235        $current = array('start' => $start);
236
237        $type = ord($encoded[$encoded_pos++]);
238        $startOffset = 1;
239
240        $constructed = ($type >> 5) & 1;
241
242        $tag = $type & 0x1F;
243        if ($tag == 0x1F) {
244            $tag = 0;
245            // process septets (since the eighth bit is ignored, it's not an octet)
246            do {
247                $temp = ord($encoded[$encoded_pos++]);
248                $startOffset++;
249                $loop = $temp >> 7;
250                $tag <<= 7;
251                $temp &= 0x7F;
252                // "bits 7 to 1 of the first subsequent octet shall not all be zero"
253                if ($startOffset == 2 && $temp == 0) {
254                    return false;
255                }
256                $tag |= $temp;
257            } while ($loop);
258        }
259
260        $start+= $startOffset;
261
262        // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
263        $length = ord($encoded[$encoded_pos++]);
264        $start++;
265        if ($length == 0x80) { // indefinite length
266            // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
267            //  immediately available." -- paragraph 8.1.3.2.c
268            $length = strlen($encoded) - $encoded_pos;
269        } elseif ($length & 0x80) { // definite length, long form
270            // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
271            // support it up to four.
272            $length&= 0x7F;
273            $temp = substr($encoded, $encoded_pos, $length);
274            $encoded_pos += $length;
275            // tags of indefinte length don't really have a header length; this length includes the tag
276            $current+= array('headerlength' => $length + 2);
277            $start+= $length;
278            extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
279        } else {
280            $current+= array('headerlength' => 2);
281        }
282
283        if ($length > (strlen($encoded) - $encoded_pos)) {
284            return false;
285        }
286
287        $content = substr($encoded, $encoded_pos, $length);
288        $content_pos = 0;
289
290        // at this point $length can be overwritten. it's only accurate for definite length things as is
291
292        /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
293           built-in types. It defines an application-independent data type that must be distinguishable from all other
294           data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
295           have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
296           a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
297           alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
298           data type; the term CONTEXT-SPECIFIC does not appear.
299
300             -- http://www.obj-sys.com/asn1tutorial/node12.html */
301        $class = ($type >> 6) & 3;
302        switch ($class) {
303            case self::CLASS_APPLICATION:
304            case self::CLASS_PRIVATE:
305            case self::CLASS_CONTEXT_SPECIFIC:
306                if (!$constructed) {
307                    return array(
308                        'type'     => $class,
309                        'constant' => $tag,
310                        'content'  => $content,
311                        'length'   => $length + $start - $current['start']
312                    );
313                }
314
315                $newcontent = array();
316                $remainingLength = $length;
317                while ($remainingLength > 0) {
318                    $temp = $this->_decode_ber($content, $start, $content_pos);
319                    if ($temp === false) {
320                        break;
321                    }
322                    $length = $temp['length'];
323                    // end-of-content octets - see paragraph 8.1.5
324                    if (substr($content, $content_pos + $length, 2) == "\0\0") {
325                        $length+= 2;
326                        $start+= $length;
327                        $newcontent[] = $temp;
328                        break;
329                    }
330                    $start+= $length;
331                    $remainingLength-= $length;
332                    $newcontent[] = $temp;
333                    $content_pos += $length;
334                }
335
336                return array(
337                    'type'     => $class,
338                    'constant' => $tag,
339                    // the array encapsulation is for BC with the old format
340                    'content'  => $newcontent,
341                    // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
342                    // the absence of $content['headerlength'] is how we know if something is indefinite or not.
343                    // technically, it could be defined to be 2 and then another indicator could be used but whatever.
344                    'length'   => $start - $current['start']
345                ) + $current;
346        }
347
348        $current+= array('type' => $tag);
349
350        // decode UNIVERSAL tags
351        switch ($tag) {
352            case self::TYPE_BOOLEAN:
353                // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
354                if ($constructed || strlen($content) != 1) {
355                    return false;
356                }
357                $current['content'] = (bool) ord($content[$content_pos]);
358                break;
359            case self::TYPE_INTEGER:
360            case self::TYPE_ENUMERATED:
361                if ($constructed) {
362                    return false;
363                }
364                $current['content'] = new BigInteger(substr($content, $content_pos), -256);
365                break;
366            case self::TYPE_REAL: // not currently supported
367                return false;
368            case self::TYPE_BIT_STRING:
369                // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
370                // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
371                // seven.
372                if (!$constructed) {
373                    $current['content'] = substr($content, $content_pos);
374                } else {
375                    $temp = $this->_decode_ber($content, $start, $content_pos);
376                    if ($temp === false) {
377                        return false;
378                    }
379                    $length-= (strlen($content) - $content_pos);
380                    $last = count($temp) - 1;
381                    for ($i = 0; $i < $last; $i++) {
382                        // all subtags should be bit strings
383                        if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
384                            return false;
385                        }
386                        $current['content'].= substr($temp[$i]['content'], 1);
387                    }
388                    // all subtags should be bit strings
389                    if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
390                        return false;
391                    }
392                    $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
393                }
394                break;
395            case self::TYPE_OCTET_STRING:
396                if (!$constructed) {
397                    $current['content'] = substr($content, $content_pos);
398                } else {
399                    $current['content'] = '';
400                    $length = 0;
401                    while (substr($content, $content_pos, 2) != "\0\0") {
402                        $temp = $this->_decode_ber($content, $length + $start, $content_pos);
403                        if ($temp === false) {
404                            return false;
405                        }
406                        $content_pos += $temp['length'];
407                        // all subtags should be octet strings
408                        if ($temp['type'] != self::TYPE_OCTET_STRING) {
409                            return false;
410                        }
411                        $current['content'].= $temp['content'];
412                        $length+= $temp['length'];
413                    }
414                    if (substr($content, $content_pos, 2) == "\0\0") {
415                        $length+= 2; // +2 for the EOC
416                    }
417                }
418                break;
419            case self::TYPE_NULL:
420                // "The contents octets shall not contain any octets." -- paragraph 8.8.2
421                if ($constructed || strlen($content)) {
422                    return false;
423                }
424                break;
425            case self::TYPE_SEQUENCE:
426            case self::TYPE_SET:
427                if (!$constructed) {
428                    return false;
429                }
430                $offset = 0;
431                $current['content'] = array();
432                $content_len = strlen($content);
433                while ($content_pos < $content_len) {
434                    // if indefinite length construction was used and we have an end-of-content string next
435                    // see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
436                    if (!isset($current['headerlength']) && substr($content, $content_pos, 2) == "\0\0") {
437                        $length = $offset + 2; // +2 for the EOC
438                        break 2;
439                    }
440                    $temp = $this->_decode_ber($content, $start + $offset, $content_pos);
441                    if ($temp === false) {
442                        return false;
443                    }
444                    $content_pos += $temp['length'];
445                    $current['content'][] = $temp;
446                    $offset+= $temp['length'];
447                }
448                break;
449            case self::TYPE_OBJECT_IDENTIFIER:
450                if ($constructed) {
451                    return false;
452                }
453                $current['content'] = $this->_decodeOID(substr($content, $content_pos));
454                if ($current['content'] === false) {
455                    return false;
456                }
457                break;
458            /* Each character string type shall be encoded as if it had been declared:
459               [UNIVERSAL x] IMPLICIT OCTET STRING
460
461                 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
462
463               Per that, we're not going to do any validation.  If there are any illegal characters in the string,
464               we don't really care */
465            case self::TYPE_NUMERIC_STRING:
466                // 0,1,2,3,4,5,6,7,8,9, and space
467            case self::TYPE_PRINTABLE_STRING:
468                // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
469                // hyphen, full stop, solidus, colon, equal sign, question mark
470            case self::TYPE_TELETEX_STRING:
471                // The Teletex character set in CCITT's T61, space, and delete
472                // see http://en.wikipedia.org/wiki/Teletex#Character_sets
473            case self::TYPE_VIDEOTEX_STRING:
474                // The Videotex character set in CCITT's T.100 and T.101, space, and delete
475            case self::TYPE_VISIBLE_STRING:
476                // Printing character sets of international ASCII, and space
477            case self::TYPE_IA5_STRING:
478                // International Alphabet 5 (International ASCII)
479            case self::TYPE_GRAPHIC_STRING:
480                // All registered G sets, and space
481            case self::TYPE_GENERAL_STRING:
482                // All registered C and G sets, space and delete
483            case self::TYPE_UTF8_STRING:
484                // ????
485            case self::TYPE_BMP_STRING:
486                if ($constructed) {
487                    return false;
488                }
489                $current['content'] = substr($content, $content_pos);
490                break;
491            case self::TYPE_UTC_TIME:
492            case self::TYPE_GENERALIZED_TIME:
493                if ($constructed) {
494                    return false;
495                }
496                $current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag);
497                break;
498            default:
499                return false;
500        }
501
502        $start+= $length;
503
504        // ie. length is the length of the full TLV encoding - it's not just the length of the value
505        return $current + array('length' => $start - $current['start']);
506    }
507
508    /**
509     * ASN.1 Map
510     *
511     * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
512     *
513     * "Special" mappings may be applied on a per tag-name basis via $special.
514     *
515     * @param array $decoded
516     * @param array $mapping
517     * @param array $special
518     * @return array
519     * @access public
520     */
521    function asn1map($decoded, $mapping, $special = array())
522    {
523        if (!is_array($decoded)) {
524            return false;
525        }
526
527        if (isset($mapping['explicit']) && is_array($decoded['content'])) {
528            $decoded = $decoded['content'][0];
529        }
530
531        switch (true) {
532            case $mapping['type'] == self::TYPE_ANY:
533                $intype = $decoded['type'];
534                if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || (ord($this->encoded[$decoded['start']]) & 0x20)) {
535                    return new Element(substr($this->encoded, $decoded['start'], $decoded['length']));
536                }
537                $inmap = $this->ANYmap[$intype];
538                if (is_string($inmap)) {
539                    return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
540                }
541                break;
542            case $mapping['type'] == self::TYPE_CHOICE:
543                foreach ($mapping['children'] as $key => $option) {
544                    switch (true) {
545                        case isset($option['constant']) && $option['constant'] == $decoded['constant']:
546                        case !isset($option['constant']) && $option['type'] == $decoded['type']:
547                            $value = $this->asn1map($decoded, $option, $special);
548                            break;
549                        case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
550                            $v = $this->asn1map($decoded, $option, $special);
551                            if (isset($v)) {
552                                $value = $v;
553                            }
554                    }
555                    if (isset($value)) {
556                        if (isset($special[$key])) {
557                            $value = call_user_func($special[$key], $value);
558                        }
559                        return array($key => $value);
560                    }
561                }
562                return null;
563            case isset($mapping['implicit']):
564            case isset($mapping['explicit']):
565            case $decoded['type'] == $mapping['type']:
566                break;
567            default:
568                // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
569                // let it through
570                switch (true) {
571                    case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18
572                    case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30
573                    case $mapping['type'] < 18:
574                    case $mapping['type'] > 30:
575                        return null;
576                }
577        }
578
579        if (isset($mapping['implicit'])) {
580            $decoded['type'] = $mapping['type'];
581        }
582
583        switch ($decoded['type']) {
584            case self::TYPE_SEQUENCE:
585                $map = array();
586
587                // ignore the min and max
588                if (isset($mapping['min']) && isset($mapping['max'])) {
589                    $child = $mapping['children'];
590                    foreach ($decoded['content'] as $content) {
591                        if (($map[] = $this->asn1map($content, $child, $special)) === null) {
592                            return null;
593                        }
594                    }
595
596                    return $map;
597                }
598
599                $n = count($decoded['content']);
600                $i = 0;
601
602                foreach ($mapping['children'] as $key => $child) {
603                    $maymatch = $i < $n; // Match only existing input.
604                    if ($maymatch) {
605                        $temp = $decoded['content'][$i];
606
607                        if ($child['type'] != self::TYPE_CHOICE) {
608                            // Get the mapping and input class & constant.
609                            $childClass = $tempClass = self::CLASS_UNIVERSAL;
610                            $constant = null;
611                            if (isset($temp['constant'])) {
612                                $tempClass = $temp['type'];
613                            }
614                            if (isset($child['class'])) {
615                                $childClass = $child['class'];
616                                $constant = $child['cast'];
617                            } elseif (isset($child['constant'])) {
618                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
619                                $constant = $child['constant'];
620                            }
621
622                            if (isset($constant) && isset($temp['constant'])) {
623                                // Can only match if constants and class match.
624                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
625                            } else {
626                                // Can only match if no constant expected and type matches or is generic.
627                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
628                            }
629                        }
630                    }
631
632                    if ($maymatch) {
633                        // Attempt submapping.
634                        $candidate = $this->asn1map($temp, $child, $special);
635                        $maymatch = $candidate !== null;
636                    }
637
638                    if ($maymatch) {
639                        // Got the match: use it.
640                        if (isset($special[$key])) {
641                            $candidate = call_user_func($special[$key], $candidate);
642                        }
643                        $map[$key] = $candidate;
644                        $i++;
645                    } elseif (isset($child['default'])) {
646                        $map[$key] = $child['default']; // Use default.
647                    } elseif (!isset($child['optional'])) {
648                        return null; // Syntax error.
649                    }
650                }
651
652                // Fail mapping if all input items have not been consumed.
653                return $i < $n ? null: $map;
654
655            // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
656            case self::TYPE_SET:
657                $map = array();
658
659                // ignore the min and max
660                if (isset($mapping['min']) && isset($mapping['max'])) {
661                    $child = $mapping['children'];
662                    foreach ($decoded['content'] as $content) {
663                        if (($map[] = $this->asn1map($content, $child, $special)) === null) {
664                            return null;
665                        }
666                    }
667
668                    return $map;
669                }
670
671                for ($i = 0; $i < count($decoded['content']); $i++) {
672                    $temp = $decoded['content'][$i];
673                    $tempClass = self::CLASS_UNIVERSAL;
674                    if (isset($temp['constant'])) {
675                        $tempClass = $temp['type'];
676                    }
677
678                    foreach ($mapping['children'] as $key => $child) {
679                        if (isset($map[$key])) {
680                            continue;
681                        }
682                        $maymatch = true;
683                        if ($child['type'] != self::TYPE_CHOICE) {
684                            $childClass = self::CLASS_UNIVERSAL;
685                            $constant = null;
686                            if (isset($child['class'])) {
687                                $childClass = $child['class'];
688                                $constant = $child['cast'];
689                            } elseif (isset($child['constant'])) {
690                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
691                                $constant = $child['constant'];
692                            }
693
694                            if (isset($constant) && isset($temp['constant'])) {
695                                // Can only match if constants and class match.
696                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
697                            } else {
698                                // Can only match if no constant expected and type matches or is generic.
699                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
700                            }
701                        }
702
703                        if ($maymatch) {
704                            // Attempt submapping.
705                            $candidate = $this->asn1map($temp, $child, $special);
706                            $maymatch = $candidate !== null;
707                        }
708
709                        if (!$maymatch) {
710                            break;
711                        }
712
713                        // Got the match: use it.
714                        if (isset($special[$key])) {
715                            $candidate = call_user_func($special[$key], $candidate);
716                        }
717                        $map[$key] = $candidate;
718                        break;
719                    }
720                }
721
722                foreach ($mapping['children'] as $key => $child) {
723                    if (!isset($map[$key])) {
724                        if (isset($child['default'])) {
725                            $map[$key] = $child['default'];
726                        } elseif (!isset($child['optional'])) {
727                            return null;
728                        }
729                    }
730                }
731                return $map;
732            case self::TYPE_OBJECT_IDENTIFIER:
733                return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
734            case self::TYPE_UTC_TIME:
735            case self::TYPE_GENERALIZED_TIME:
736                // for explicitly tagged optional stuff
737                if (is_array($decoded['content'])) {
738                    $decoded['content'] = $decoded['content'][0]['content'];
739                }
740                // for implicitly tagged optional stuff
741                // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
742                // in the wild that OpenSSL decodes without issue so we'll support them as well
743                if (!is_object($decoded['content'])) {
744                    $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
745                }
746                return $decoded['content'] ? $decoded['content']->format($this->format) : false;
747            case self::TYPE_BIT_STRING:
748                if (isset($mapping['mapping'])) {
749                    $offset = ord($decoded['content'][0]);
750                    $size = (strlen($decoded['content']) - 1) * 8 - $offset;
751                    /*
752                       From X.680-0207.pdf#page=46 (21.7):
753
754                       "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
755                        arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
756                        therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
757                        0 bits."
758                    */
759                    $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
760                    for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
761                        $current = ord($decoded['content'][$i]);
762                        for ($j = $offset; $j < 8; $j++) {
763                            $bits[] = (bool) ($current & (1 << $j));
764                        }
765                        $offset = 0;
766                    }
767                    $values = array();
768                    $map = array_reverse($mapping['mapping']);
769                    foreach ($map as $i => $value) {
770                        if ($bits[$i]) {
771                            $values[] = $value;
772                        }
773                    }
774                    return $values;
775                }
776            case self::TYPE_OCTET_STRING:
777                return base64_encode($decoded['content']);
778            case self::TYPE_NULL:
779                return '';
780            case self::TYPE_BOOLEAN:
781                return $decoded['content'];
782            case self::TYPE_NUMERIC_STRING:
783            case self::TYPE_PRINTABLE_STRING:
784            case self::TYPE_TELETEX_STRING:
785            case self::TYPE_VIDEOTEX_STRING:
786            case self::TYPE_IA5_STRING:
787            case self::TYPE_GRAPHIC_STRING:
788            case self::TYPE_VISIBLE_STRING:
789            case self::TYPE_GENERAL_STRING:
790            case self::TYPE_UNIVERSAL_STRING:
791            case self::TYPE_UTF8_STRING:
792            case self::TYPE_BMP_STRING:
793                return $decoded['content'];
794            case self::TYPE_INTEGER:
795            case self::TYPE_ENUMERATED:
796                $temp = $decoded['content'];
797                if (isset($mapping['implicit'])) {
798                    $temp = new BigInteger($decoded['content'], -256);
799                }
800                if (isset($mapping['mapping'])) {
801                    $temp = (int) $temp->toString();
802                    return isset($mapping['mapping'][$temp]) ?
803                        $mapping['mapping'][$temp] :
804                        false;
805                }
806                return $temp;
807        }
808    }
809
810    /**
811     * ASN.1 Encode
812     *
813     * DER-encodes an ASN.1 semantic mapping ($mapping).  Some libraries would probably call this function
814     * an ASN.1 compiler.
815     *
816     * "Special" mappings can be applied via $special.
817     *
818     * @param string $source
819     * @param string $mapping
820     * @param array $special
821     * @return string
822     * @access public
823     */
824    function encodeDER($source, $mapping, $special = array())
825    {
826        $this->location = array();
827        return $this->_encode_der($source, $mapping, null, $special);
828    }
829
830    /**
831     * ASN.1 Encode (Helper function)
832     *
833     * @param string $source
834     * @param string $mapping
835     * @param int $idx
836     * @param array $special
837     * @return string
838     * @access private
839     */
840    function _encode_der($source, $mapping, $idx = null, $special = array())
841    {
842        if ($source instanceof Element) {
843            return $source->element;
844        }
845
846        // do not encode (implicitly optional) fields with value set to default
847        if (isset($mapping['default']) && $source === $mapping['default']) {
848            return '';
849        }
850
851        if (isset($idx)) {
852            if (isset($special[$idx])) {
853                $source = call_user_func($special[$idx], $source);
854            }
855            $this->location[] = $idx;
856        }
857
858        $tag = $mapping['type'];
859
860        switch ($tag) {
861            case self::TYPE_SET:    // Children order is not important, thus process in sequence.
862            case self::TYPE_SEQUENCE:
863                $tag|= 0x20; // set the constructed bit
864
865                // ignore the min and max
866                if (isset($mapping['min']) && isset($mapping['max'])) {
867                    $value = array();
868                    $child = $mapping['children'];
869
870                    foreach ($source as $content) {
871                        $temp = $this->_encode_der($content, $child, null, $special);
872                        if ($temp === false) {
873                            return false;
874                        }
875                        $value[]= $temp;
876                    }
877                    /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
878                        as octet strings with the shorter components being padded at their trailing end with 0-octets.
879                        NOTE - The padding octets are for comparison purposes only and do not appear in the encodings."
880
881                       -- sec 11.6 of http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf  */
882                    if ($mapping['type'] == self::TYPE_SET) {
883                        sort($value);
884                    }
885                    $value = implode('', $value);
886                    break;
887                }
888
889                $value = '';
890                foreach ($mapping['children'] as $key => $child) {
891                    if (!array_key_exists($key, $source)) {
892                        if (!isset($child['optional'])) {
893                            return false;
894                        }
895                        continue;
896                    }
897
898                    $temp = $this->_encode_der($source[$key], $child, $key, $special);
899                    if ($temp === false) {
900                        return false;
901                    }
902
903                    // An empty child encoding means it has been optimized out.
904                    // Else we should have at least one tag byte.
905                    if ($temp === '') {
906                        continue;
907                    }
908
909                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
910                    if (isset($child['constant'])) {
911                        /*
912                           From X.680-0207.pdf#page=58 (30.6):
913
914                           "The tagging construction specifies explicit tagging if any of the following holds:
915                            ...
916                            c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
917                            AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
918                            an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
919                         */
920                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
921                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
922                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
923                        } else {
924                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
925                            $temp = $subtag . substr($temp, 1);
926                        }
927                    }
928                    $value.= $temp;
929                }
930                break;
931            case self::TYPE_CHOICE:
932                $temp = false;
933
934                foreach ($mapping['children'] as $key => $child) {
935                    if (!isset($source[$key])) {
936                        continue;
937                    }
938
939                    $temp = $this->_encode_der($source[$key], $child, $key, $special);
940                    if ($temp === false) {
941                        return false;
942                    }
943
944                    // An empty child encoding means it has been optimized out.
945                    // Else we should have at least one tag byte.
946                    if ($temp === '') {
947                        continue;
948                    }
949
950                    $tag = ord($temp[0]);
951
952                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
953                    if (isset($child['constant'])) {
954                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
955                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
956                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
957                        } else {
958                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
959                            $temp = $subtag . substr($temp, 1);
960                        }
961                    }
962                }
963
964                if (isset($idx)) {
965                    array_pop($this->location);
966                }
967
968                if ($temp && isset($mapping['cast'])) {
969                    $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
970                }
971
972                return $temp;
973            case self::TYPE_INTEGER:
974            case self::TYPE_ENUMERATED:
975                if (!isset($mapping['mapping'])) {
976                    if (is_numeric($source)) {
977                        $source = new BigInteger($source);
978                    }
979                    $value = $source->toBytes(true);
980                } else {
981                    $value = array_search($source, $mapping['mapping']);
982                    if ($value === false) {
983                        return false;
984                    }
985                    $value = new BigInteger($value);
986                    $value = $value->toBytes(true);
987                }
988                if (!strlen($value)) {
989                    $value = chr(0);
990                }
991                break;
992            case self::TYPE_UTC_TIME:
993            case self::TYPE_GENERALIZED_TIME:
994                $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
995                $format.= 'mdHis';
996                $date = new DateTime($source, new DateTimeZone('GMT'));
997                $value = $date->format($format) . 'Z';
998                break;
999            case self::TYPE_BIT_STRING:
1000                if (isset($mapping['mapping'])) {
1001                    $bits = array_fill(0, count($mapping['mapping']), 0);
1002                    $size = 0;
1003                    for ($i = 0; $i < count($mapping['mapping']); $i++) {
1004                        if (in_array($mapping['mapping'][$i], $source)) {
1005                            $bits[$i] = 1;
1006                            $size = $i;
1007                        }
1008                    }
1009
1010                    if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
1011                        $size = $mapping['min'] - 1;
1012                    }
1013
1014                    $offset = 8 - (($size + 1) & 7);
1015                    $offset = $offset !== 8 ? $offset : 0;
1016
1017                    $value = chr($offset);
1018
1019                    for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
1020                        unset($bits[$i]);
1021                    }
1022
1023                    $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
1024                    $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
1025                    foreach ($bytes as $byte) {
1026                        $value.= chr(bindec($byte));
1027                    }
1028
1029                    break;
1030                }
1031            case self::TYPE_OCTET_STRING:
1032                /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
1033                   the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
1034
1035                   -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
1036                $value = base64_decode($source);
1037                break;
1038            case self::TYPE_OBJECT_IDENTIFIER:
1039                $value = $this->_encodeOID($source);
1040                break;
1041            case self::TYPE_ANY:
1042                $loc = $this->location;
1043                if (isset($idx)) {
1044                    array_pop($this->location);
1045                }
1046
1047                switch (true) {
1048                    case !isset($source):
1049                        return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special);
1050                    case is_int($source):
1051                    case $source instanceof BigInteger:
1052                        return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special);
1053                    case is_float($source):
1054                        return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special);
1055                    case is_bool($source):
1056                        return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special);
1057                    case is_array($source) && count($source) == 1:
1058                        $typename = implode('', array_keys($source));
1059                        $outtype = array_search($typename, $this->ANYmap, true);
1060                        if ($outtype !== false) {
1061                            return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
1062                        }
1063                }
1064
1065                $filters = $this->filters;
1066                foreach ($loc as $part) {
1067                    if (!isset($filters[$part])) {
1068                        $filters = false;
1069                        break;
1070                    }
1071                    $filters = $filters[$part];
1072                }
1073                if ($filters === false) {
1074                    user_error('No filters defined for ' . implode('/', $loc));
1075                    return false;
1076                }
1077                return $this->_encode_der($source, $filters + $mapping, null, $special);
1078            case self::TYPE_NULL:
1079                $value = '';
1080                break;
1081            case self::TYPE_NUMERIC_STRING:
1082            case self::TYPE_TELETEX_STRING:
1083            case self::TYPE_PRINTABLE_STRING:
1084            case self::TYPE_UNIVERSAL_STRING:
1085            case self::TYPE_UTF8_STRING:
1086            case self::TYPE_BMP_STRING:
1087            case self::TYPE_IA5_STRING:
1088            case self::TYPE_VISIBLE_STRING:
1089            case self::TYPE_VIDEOTEX_STRING:
1090            case self::TYPE_GRAPHIC_STRING:
1091            case self::TYPE_GENERAL_STRING:
1092                $value = $source;
1093                break;
1094            case self::TYPE_BOOLEAN:
1095                $value = $source ? "\xFF" : "\x00";
1096                break;
1097            default:
1098                user_error('Mapping provides no type definition for ' . implode('/', $this->location));
1099                return false;
1100        }
1101
1102        if (isset($idx)) {
1103            array_pop($this->location);
1104        }
1105
1106        if (isset($mapping['cast'])) {
1107            if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
1108                $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1109                $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
1110            } else {
1111                $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
1112            }
1113        }
1114
1115        return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
1116    }
1117
1118    /**
1119     * DER-encode the length
1120     *
1121     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
1122     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
1123     *
1124     * @access private
1125     * @param int $length
1126     * @return string
1127     */
1128    function _encodeLength($length)
1129    {
1130        if ($length <= 0x7F) {
1131            return chr($length);
1132        }
1133
1134        $temp = ltrim(pack('N', $length), chr(0));
1135        return pack('Ca*', 0x80 | strlen($temp), $temp);
1136    }
1137
1138    /**
1139     * BER-decode the OID
1140     *
1141     * Called by _decode_ber()
1142     *
1143     * @access private
1144     * @param string $content
1145     * @return string
1146     */
1147    function _decodeOID($content)
1148    {
1149        static $eighty;
1150        if (!$eighty) {
1151            $eighty = new BigInteger(80);
1152        }
1153
1154        $oid = array();
1155        $pos = 0;
1156        $len = strlen($content);
1157
1158        if (ord($content[$len - 1]) & 0x80) {
1159            return false;
1160        }
1161
1162        $n = new BigInteger();
1163        while ($pos < $len) {
1164            $temp = ord($content[$pos++]);
1165            $n = $n->bitwise_leftShift(7);
1166            $n = $n->bitwise_or(new BigInteger($temp & 0x7F));
1167            if (~$temp & 0x80) {
1168                $oid[] = $n;
1169                $n = new BigInteger();
1170            }
1171        }
1172        $part1 = array_shift($oid);
1173        $first = floor(ord($content[0]) / 40);
1174        /*
1175          "This packing of the first two object identifier components recognizes that only three values are allocated from the root
1176           node, and at most 39 subsequent values from nodes reached by X = 0 and X = 1."
1177
1178          -- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22
1179        */
1180        if ($first <= 2) { // ie. 0 <= ord($content[0]) < 120 (0x78)
1181            array_unshift($oid, ord($content[0]) % 40);
1182            array_unshift($oid, $first);
1183        } else {
1184            array_unshift($oid, $part1->subtract($eighty));
1185            array_unshift($oid, 2);
1186        }
1187
1188        return implode('.', $oid);
1189    }
1190
1191    /**
1192     * DER-encode the OID
1193     *
1194     * Called by _encode_der()
1195     *
1196     * @access private
1197     * @param string $source
1198     * @return string
1199     */
1200    function _encodeOID($source)
1201    {
1202        static $mask, $zero, $forty;
1203        if (!$mask) {
1204            $mask = new BigInteger(0x7F);
1205            $zero = new BigInteger();
1206            $forty = new BigInteger(40);
1207        }
1208
1209        $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
1210        if ($oid === false) {
1211            user_error('Invalid OID');
1212            return false;
1213        }
1214        $parts = explode('.', $oid);
1215        $part1 = array_shift($parts);
1216        $part2 = array_shift($parts);
1217
1218        $first = new BigInteger($part1);
1219        $first = $first->multiply($forty);
1220        $first = $first->add(new BigInteger($part2));
1221
1222        array_unshift($parts, $first->toString());
1223
1224        $value = '';
1225        foreach ($parts as $part) {
1226            if (!$part) {
1227                $temp = "\0";
1228            } else {
1229                $temp = '';
1230                $part = new BigInteger($part);
1231                while (!$part->equals($zero)) {
1232                    $submask = $part->bitwise_and($mask);
1233                    $submask->setPrecision(8);
1234                    $temp = (chr(0x80) | $submask->toBytes()) . $temp;
1235                    $part = $part->bitwise_rightShift(7);
1236                }
1237                $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
1238            }
1239            $value.= $temp;
1240        }
1241
1242        return $value;
1243    }
1244
1245    /**
1246     * BER-decode the time
1247     *
1248     * Called by _decode_ber() and in the case of implicit tags asn1map().
1249     *
1250     * @access private
1251     * @param string $content
1252     * @param int $tag
1253     * @return string
1254     */
1255    function _decodeTime($content, $tag)
1256    {
1257        /* UTCTime:
1258           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
1259           http://www.obj-sys.com/asn1tutorial/node15.html
1260
1261           GeneralizedTime:
1262           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
1263           http://www.obj-sys.com/asn1tutorial/node14.html */
1264
1265        $format = 'YmdHis';
1266
1267        if ($tag == self::TYPE_UTC_TIME) {
1268            // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds
1269            // element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the
1270            // browsers parse it phpseclib ought to too
1271            if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) {
1272                $content = $matches[1] . '00' . $matches[2];
1273            }
1274            $prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
1275            $content = $prefix . $content;
1276        } elseif (strpos($content, '.') !== false) {
1277            $format.= '.u';
1278        }
1279
1280        if ($content[strlen($content) - 1] == 'Z') {
1281            $content = substr($content, 0, -1) . '+0000';
1282        }
1283
1284        if (strpos($content, '-') !== false || strpos($content, '+') !== false) {
1285            $format.= 'O';
1286        }
1287
1288        // error supression isn't necessary as of PHP 7.0:
1289        // http://php.net/manual/en/migration70.other-changes.php
1290        return @DateTime::createFromFormat($format, $content);
1291    }
1292
1293    /**
1294     * Set the time format
1295     *
1296     * Sets the time / date format for asn1map().
1297     *
1298     * @access public
1299     * @param string $format
1300     */
1301    function setTimeFormat($format)
1302    {
1303        $this->format = $format;
1304    }
1305
1306    /**
1307     * Load OIDs
1308     *
1309     * Load the relevant OIDs for a particular ASN.1 semantic mapping.
1310     *
1311     * @access public
1312     * @param array $oids
1313     */
1314    function loadOIDs($oids)
1315    {
1316        $this->oids = $oids;
1317    }
1318
1319    /**
1320     * Load filters
1321     *
1322     * See \phpseclib\File\X509, etc, for an example.
1323     *
1324     * @access public
1325     * @param array $filters
1326     */
1327    function loadFilters($filters)
1328    {
1329        $this->filters = $filters;
1330    }
1331
1332    /**
1333     * String Shift
1334     *
1335     * Inspired by array_shift
1336     *
1337     * @param string $string
1338     * @param int $index
1339     * @return string
1340     * @access private
1341     */
1342    function _string_shift(&$string, $index = 1)
1343    {
1344        $substr = substr($string, 0, $index);
1345        $string = substr($string, $index);
1346        return $substr;
1347    }
1348
1349    /**
1350     * String type conversion
1351     *
1352     * This is a lazy conversion, dealing only with character size.
1353     * No real conversion table is used.
1354     *
1355     * @param string $in
1356     * @param int $from
1357     * @param int $to
1358     * @return string
1359     * @access public
1360     */
1361    function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
1362    {
1363        if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
1364            return false;
1365        }
1366        $insize = $this->stringTypeSize[$from];
1367        $outsize = $this->stringTypeSize[$to];
1368        $inlength = strlen($in);
1369        $out = '';
1370
1371        for ($i = 0; $i < $inlength;) {
1372            if ($inlength - $i < $insize) {
1373                return false;
1374            }
1375
1376            // Get an input character as a 32-bit value.
1377            $c = ord($in[$i++]);
1378            switch (true) {
1379                case $insize == 4:
1380                    $c = ($c << 8) | ord($in[$i++]);
1381                    $c = ($c << 8) | ord($in[$i++]);
1382                case $insize == 2:
1383                    $c = ($c << 8) | ord($in[$i++]);
1384                case $insize == 1:
1385                    break;
1386                case ($c & 0x80) == 0x00:
1387                    break;
1388                case ($c & 0x40) == 0x00:
1389                    return false;
1390                default:
1391                    $bit = 6;
1392                    do {
1393                        if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
1394                            return false;
1395                        }
1396                        $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
1397                        $bit += 5;
1398                        $mask = 1 << $bit;
1399                    } while ($c & $bit);
1400                    $c &= $mask - 1;
1401                    break;
1402            }
1403
1404            // Convert and append the character to output string.
1405            $v = '';
1406            switch (true) {
1407                case $outsize == 4:
1408                    $v .= chr($c & 0xFF);
1409                    $c >>= 8;
1410                    $v .= chr($c & 0xFF);
1411                    $c >>= 8;
1412                case $outsize == 2:
1413                    $v .= chr($c & 0xFF);
1414                    $c >>= 8;
1415                case $outsize == 1:
1416                    $v .= chr($c & 0xFF);
1417                    $c >>= 8;
1418                    if ($c) {
1419                        return false;
1420                    }
1421                    break;
1422                case ($c & 0x80000000) != 0:
1423                    return false;
1424                case $c >= 0x04000000:
1425                    $v .= chr(0x80 | ($c & 0x3F));
1426                    $c = ($c >> 6) | 0x04000000;
1427                case $c >= 0x00200000:
1428                    $v .= chr(0x80 | ($c & 0x3F));
1429                    $c = ($c >> 6) | 0x00200000;
1430                case $c >= 0x00010000:
1431                    $v .= chr(0x80 | ($c & 0x3F));
1432                    $c = ($c >> 6) | 0x00010000;
1433                case $c >= 0x00000800:
1434                    $v .= chr(0x80 | ($c & 0x3F));
1435                    $c = ($c >> 6) | 0x00000800;
1436                case $c >= 0x00000080:
1437                    $v .= chr(0x80 | ($c & 0x3F));
1438                    $c = ($c >> 6) | 0x000000C0;
1439                default:
1440                    $v .= chr($c);
1441                    break;
1442            }
1443            $out .= strrev($v);
1444        }
1445        return $out;
1446    }
1447}
1448