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