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