PluginProbe
ManageWP Worker / 4.9.25
ManageWP Worker v4.9.25
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / src / PHPSecLib / File / ASN1.php

ASN1.php in ManageWP Worker 4.9.25, at src/PHPSecLib/File/ASN1.php

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