Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

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