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