| [ Index ] |
PHP Cross Reference of YOURLS |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * YOURLS modification of a small subset from WordPress' KSES implementation. 4 * Straight from the Let's Not Reinvent The Wheel department. 5 */ 6 7 /** 8 * kses 0.2.2 - HTML/XHTML filter that only allows some elements and attributes 9 * Copyright (C) 2002, 2003, 2005 Ulf Harnhammar 10 * 11 * This program is free software and open source software; you can redistribute 12 * it and/or modify it under the terms of the GNU General Public License as 13 * published by the Free Software Foundation; either version 2 of the License, 14 * or (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, but WITHOUT 17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 18 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 19 * more details. 20 * 21 * You should have received a copy of the GNU General Public License along 22 * with this program; if not, write to the Free Software Foundation, Inc., 23 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 24 * http://www.gnu.org/licenses/gpl.html 25 * 26 * [kses strips evil scripts!] 27 * 28 * @version 0.2.2 29 * @copyright (C) 2002, 2003, 2005 30 * @author Ulf Harnhammar <http://advogato.org/person/metaur/> 31 * 32 * @package External 33 * @subpackage KSES 34 * 35 */ 36 37 /* NOTE ABOUT GLOBALS 38 * Two globals are defined: $yourls_allowedentitynames and $yourls_allowedprotocols 39 * - $yourls_allowedentitynames is used internally in KSES functions to sanitize HTML entities 40 * - $yourls_allowedprotocols is used in various parts of YOURLS, not just in KSES, albeit being defined here 41 * Two globals are not defined and unused at this moment: $yourls_allowedtags_all and $yourls_allowedtags 42 * The code for these vars is here and ready for any future use 43 */ 44 45 // Populate after plugins have loaded to allow user defined values 46 yourls_add_action( 'plugins_loaded', 'yourls_kses_init' ); 47 48 /** 49 * Init KSES globals if not already defined (by a plugin) 50 * 51 * @since 1.6 52 * @return void 53 */ 54 function yourls_kses_init() { 55 global $yourls_allowedentitynames, $yourls_allowedprotocols; 56 57 if( ! $yourls_allowedentitynames ) { 58 $yourls_allowedentitynames = yourls_apply_filter( 'kses_allowed_entities', yourls_kses_allowed_entities() ); 59 } 60 61 if( ! $yourls_allowedprotocols ) { 62 $yourls_allowedprotocols = yourls_apply_filter( 'kses_allowed_protocols', yourls_kses_allowed_protocols() ); 63 } 64 65 /** See NOTE ABOUT GLOBALS ** 66 67 if( ! $yourls_allowedtags_all ) { 68 $yourls_allowedtags_all = yourls_kses_allowed_tags_all(); 69 $yourls_allowedtags_all = array_map( '_yourls_add_global_attributes', $yourls_allowedtags_all ); 70 $yourls_allowedtags_all = yourls_apply_filter( 'kses_allowed_tags_all', $yourls_allowedtags_all ); 71 } else { 72 // User defined: let's sanitize 73 $yourls_allowedtags_all = yourls_kses_array_lc( $yourls_allowedtags_all ); 74 } 75 76 if( ! $yourls_allowedtags ) { 77 $yourls_allowedtags = yourls_kses_allowed_tags(); 78 $yourls_allowedtags = array_map( '_yourls_add_global_attributes', $yourls_allowedtags ); 79 $yourls_allowedtags = yourls_apply_filter( 'kses_allowed_tags', $yourls_allowedtags ); 80 } else { 81 // User defined: let's sanitize 82 $yourls_allowedtags = yourls_kses_array_lc( $yourls_allowedtags ); 83 } 84 85 /**/ 86 } 87 88 /** 89 * Kses global for all allowable HTML tags. 90 * 91 * Complete (?) list of HTML tags. Keep this function available for any plugin or 92 * future feature that will want to display lots of HTML. 93 * 94 * @since 1.6 95 * 96 * @return array All tags 97 */ 98 function yourls_kses_allowed_tags_all() { 99 return array( 100 'address' => array(), 101 'a' => array( 102 'href' => true, 103 'rel' => true, 104 'rev' => true, 105 'name' => true, 106 'target' => true, 107 ), 108 'abbr' => array(), 109 'acronym' => array(), 110 'area' => array( 111 'alt' => true, 112 'coords' => true, 113 'href' => true, 114 'nohref' => true, 115 'shape' => true, 116 'target' => true, 117 ), 118 'article' => array( 119 'align' => true, 120 'dir' => true, 121 'lang' => true, 122 'xml:lang' => true, 123 ), 124 'aside' => array( 125 'align' => true, 126 'dir' => true, 127 'lang' => true, 128 'xml:lang' => true, 129 ), 130 'b' => array(), 131 'big' => array(), 132 'blockquote' => array( 133 'cite' => true, 134 'lang' => true, 135 'xml:lang' => true, 136 ), 137 'br' => array(), 138 'button' => array( 139 'disabled' => true, 140 'name' => true, 141 'type' => true, 142 'value' => true, 143 ), 144 'caption' => array( 145 'align' => true, 146 ), 147 'cite' => array( 148 'dir' => true, 149 'lang' => true, 150 ), 151 'code' => array(), 152 'col' => array( 153 'align' => true, 154 'char' => true, 155 'charoff' => true, 156 'span' => true, 157 'dir' => true, 158 'valign' => true, 159 'width' => true, 160 ), 161 'del' => array( 162 'datetime' => true, 163 ), 164 'dd' => array(), 165 'details' => array( 166 'align' => true, 167 'dir' => true, 168 'lang' => true, 169 'open' => true, 170 'xml:lang' => true, 171 ), 172 'div' => array( 173 'align' => true, 174 'dir' => true, 175 'lang' => true, 176 'xml:lang' => true, 177 ), 178 'dl' => array(), 179 'dt' => array(), 180 'em' => array(), 181 'fieldset' => array(), 182 'figure' => array( 183 'align' => true, 184 'dir' => true, 185 'lang' => true, 186 'xml:lang' => true, 187 ), 188 'figcaption' => array( 189 'align' => true, 190 'dir' => true, 191 'lang' => true, 192 'xml:lang' => true, 193 ), 194 'font' => array( 195 'color' => true, 196 'face' => true, 197 'size' => true, 198 ), 199 'footer' => array( 200 'align' => true, 201 'dir' => true, 202 'lang' => true, 203 'xml:lang' => true, 204 ), 205 'form' => array( 206 'action' => true, 207 'accept' => true, 208 'accept-charset' => true, 209 'enctype' => true, 210 'method' => true, 211 'name' => true, 212 'target' => true, 213 ), 214 'h1' => array( 215 'align' => true, 216 ), 217 'h2' => array( 218 'align' => true, 219 ), 220 'h3' => array( 221 'align' => true, 222 ), 223 'h4' => array( 224 'align' => true, 225 ), 226 'h5' => array( 227 'align' => true, 228 ), 229 'h6' => array( 230 'align' => true, 231 ), 232 'header' => array( 233 'align' => true, 234 'dir' => true, 235 'lang' => true, 236 'xml:lang' => true, 237 ), 238 'hgroup' => array( 239 'align' => true, 240 'dir' => true, 241 'lang' => true, 242 'xml:lang' => true, 243 ), 244 'hr' => array( 245 'align' => true, 246 'noshade' => true, 247 'size' => true, 248 'width' => true, 249 ), 250 'i' => array(), 251 'img' => array( 252 'alt' => true, 253 'align' => true, 254 'border' => true, 255 'height' => true, 256 'hspace' => true, 257 'longdesc' => true, 258 'vspace' => true, 259 'src' => true, 260 'usemap' => true, 261 'width' => true, 262 ), 263 'ins' => array( 264 'datetime' => true, 265 'cite' => true, 266 ), 267 'kbd' => array(), 268 'label' => array( 269 'for' => true, 270 ), 271 'legend' => array( 272 'align' => true, 273 ), 274 'li' => array( 275 'align' => true, 276 ), 277 'map' => array( 278 'name' => true, 279 ), 280 'menu' => array( 281 'type' => true, 282 ), 283 'nav' => array( 284 'align' => true, 285 'dir' => true, 286 'lang' => true, 287 'xml:lang' => true, 288 ), 289 'p' => array( 290 'align' => true, 291 'dir' => true, 292 'lang' => true, 293 'xml:lang' => true, 294 ), 295 'pre' => array( 296 'width' => true, 297 ), 298 'q' => array( 299 'cite' => true, 300 ), 301 's' => array(), 302 'span' => array( 303 'dir' => true, 304 'align' => true, 305 'lang' => true, 306 'xml:lang' => true, 307 ), 308 'section' => array( 309 'align' => true, 310 'dir' => true, 311 'lang' => true, 312 'xml:lang' => true, 313 ), 314 'small' => array(), 315 'strike' => array(), 316 'strong' => array(), 317 'sub' => array(), 318 'summary' => array( 319 'align' => true, 320 'dir' => true, 321 'lang' => true, 322 'xml:lang' => true, 323 ), 324 'sup' => array(), 325 'table' => array( 326 'align' => true, 327 'bgcolor' => true, 328 'border' => true, 329 'cellpadding' => true, 330 'cellspacing' => true, 331 'dir' => true, 332 'rules' => true, 333 'summary' => true, 334 'width' => true, 335 ), 336 'tbody' => array( 337 'align' => true, 338 'char' => true, 339 'charoff' => true, 340 'valign' => true, 341 ), 342 'td' => array( 343 'abbr' => true, 344 'align' => true, 345 'axis' => true, 346 'bgcolor' => true, 347 'char' => true, 348 'charoff' => true, 349 'colspan' => true, 350 'dir' => true, 351 'headers' => true, 352 'height' => true, 353 'nowrap' => true, 354 'rowspan' => true, 355 'scope' => true, 356 'valign' => true, 357 'width' => true, 358 ), 359 'textarea' => array( 360 'cols' => true, 361 'rows' => true, 362 'disabled' => true, 363 'name' => true, 364 'readonly' => true, 365 ), 366 'tfoot' => array( 367 'align' => true, 368 'char' => true, 369 'charoff' => true, 370 'valign' => true, 371 ), 372 'th' => array( 373 'abbr' => true, 374 'align' => true, 375 'axis' => true, 376 'bgcolor' => true, 377 'char' => true, 378 'charoff' => true, 379 'colspan' => true, 380 'headers' => true, 381 'height' => true, 382 'nowrap' => true, 383 'rowspan' => true, 384 'scope' => true, 385 'valign' => true, 386 'width' => true, 387 ), 388 'thead' => array( 389 'align' => true, 390 'char' => true, 391 'charoff' => true, 392 'valign' => true, 393 ), 394 'title' => array(), 395 'tr' => array( 396 'align' => true, 397 'bgcolor' => true, 398 'char' => true, 399 'charoff' => true, 400 'valign' => true, 401 ), 402 'tt' => array(), 403 'u' => array(), 404 'ul' => array( 405 'type' => true, 406 ), 407 'ol' => array( 408 'start' => true, 409 'type' => true, 410 ), 411 'var' => array(), 412 ); 413 } 414 415 /** 416 * Kses global for default allowable HTML tags. TODO: trim down to necessary only. 417 * 418 * Short list of HTML tags used in YOURLS core for display 419 * 420 * @since 1.6 421 * 422 * @return array Allowed tags 423 */ 424 function yourls_kses_allowed_tags() { 425 return array( 426 'a' => array( 427 'href' => true, 428 'title' => true, 429 ), 430 'abbr' => array( 431 'title' => true, 432 ), 433 'acronym' => array( 434 'title' => true, 435 ), 436 'b' => array(), 437 'blockquote' => array( 438 'cite' => true, 439 ), 440 'cite' => array(), 441 'code' => array(), 442 'del' => array( 443 'datetime' => true, 444 ), 445 'em' => array(), 446 'i' => array(), 447 'q' => array( 448 'cite' => true, 449 ), 450 'strike' => array(), 451 'strong' => array(), 452 ); 453 } 454 455 /** 456 * Kses global for allowable HTML entities. 457 * 458 * @since 1.6 459 * 460 * @return array Allowed entities 461 */ 462 function yourls_kses_allowed_entities() { 463 return array( 464 'nbsp', 'iexcl', 'cent', 'pound', 'curren', 'yen', 465 'brvbar', 'sect', 'uml', 'copy', 'ordf', 'laquo', 466 'not', 'shy', 'reg', 'macr', 'deg', 'plusmn', 467 'acute', 'micro', 'para', 'middot', 'cedil', 'ordm', 468 'raquo', 'iquest', 'Agrave', 'Aacute', 'Acirc', 'Atilde', 469 'Auml', 'Aring', 'AElig', 'Ccedil', 'Egrave', 'Eacute', 470 'Ecirc', 'Euml', 'Igrave', 'Iacute', 'Icirc', 'Iuml', 471 'ETH', 'Ntilde', 'Ograve', 'Oacute', 'Ocirc', 'Otilde', 472 'Ouml', 'times', 'Oslash', 'Ugrave', 'Uacute', 'Ucirc', 473 'Uuml', 'Yacute', 'THORN', 'szlig', 'agrave', 'aacute', 474 'acirc', 'atilde', 'auml', 'aring', 'aelig', 'ccedil', 475 'egrave', 'eacute', 'ecirc', 'euml', 'igrave', 'iacute', 476 'icirc', 'iuml', 'eth', 'ntilde', 'ograve', 'oacute', 477 'ocirc', 'otilde', 'ouml', 'divide', 'oslash', 'ugrave', 478 'uacute', 'ucirc', 'uuml', 'yacute', 'thorn', 'yuml', 479 'quot', 'amp', 'lt', 'gt', 'apos', 'OElig', 480 'oelig', 'Scaron', 'scaron', 'Yuml', 'circ', 'tilde', 481 'ensp', 'emsp', 'thinsp', 'zwnj', 'zwj', 'lrm', 482 'rlm', 'ndash', 'mdash', 'lsquo', 'rsquo', 'sbquo', 483 'ldquo', 'rdquo', 'bdquo', 'dagger', 'Dagger', 'permil', 484 'lsaquo', 'rsaquo', 'euro', 'fnof', 'Alpha', 'Beta', 485 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta', 486 'Iota', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Xi', 487 'Omicron', 'Pi', 'Rho', 'Sigma', 'Tau', 'Upsilon', 488 'Phi', 'Chi', 'Psi', 'Omega', 'alpha', 'beta', 489 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 490 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 491 'omicron', 'pi', 'rho', 'sigmaf', 'sigma', 'tau', 492 'upsilon', 'phi', 'chi', 'psi', 'omega', 'thetasym', 493 'upsih', 'piv', 'bull', 'hellip', 'prime', 'Prime', 494 'oline', 'frasl', 'weierp', 'image', 'real', 'trade', 495 'alefsym', 'larr', 'uarr', 'rarr', 'darr', 'harr', 496 'crarr', 'lArr', 'uArr', 'rArr', 'dArr', 'hArr', 497 'forall', 'part', 'exist', 'empty', 'nabla', 'isin', 498 'notin', 'ni', 'prod', 'sum', 'minus', 'lowast', 499 'radic', 'prop', 'infin', 'ang', 'and', 'or', 500 'cap', 'cup', 'int', 'sim', 'cong', 'asymp', 501 'ne', 'equiv', 'le', 'ge', 'sub', 'sup', 502 'nsub', 'sube', 'supe', 'oplus', 'otimes', 'perp', 503 'sdot', 'lceil', 'rceil', 'lfloor', 'rfloor', 'lang', 504 'rang', 'loz', 'spades', 'clubs', 'hearts', 'diams', 505 ); 506 } 507 508 /** 509 * Kses global for allowable protocols. 510 * 511 * @since 1.6 512 * 513 * @return array Allowed protocols 514 */ 515 function yourls_kses_allowed_protocols() { 516 // More or less common stuff in links. From http://en.wikipedia.org/wiki/URI_scheme 517 return array( 518 // Common 519 'http://', 'https://', 'ftp://', 520 'file://', 'smb://', 521 'sftp://', 522 'feed:', 'feed://', 523 'mailto:', 524 'news:', 'nntp://', 525 526 // Old school bearded geek 527 'gopher://', 'telnet://', 'finger://', 528 'nntp://', 'worldwind://', 529 530 // Dev 531 'ssh://', 'svn://', 'svn+ssh://', 'git://', 'cvs://', 532 'apt:', 533 'market://', // Google Play 534 'view-source:', 535 536 // P2P 537 'ed2k://', 'magnet:', 'udp://', 538 539 // Streaming stuff 540 'mms://', 'lastfm://', 'spotify:', 'rtsp://', 541 542 // Text & voice 543 'aim:', 'facetime://', 'gtalk:', 'xmpp:', 544 'irc://', 'ircs://', 'mumble://', 545 'callto:', 'skype:', 'sip:', 546 'teamspeak://', 'tel:', 'ventrilo://', 'xfire:', 547 'ymsgr:', 'tg://', 'whatsapp://', 548 549 // Misc 550 'steam:', 'steam://', 551 'bitcoin:', 552 'ldap://', 'ldaps://', 553 554 // Purposedly removed for security 555 /* 556 'about:', 'chrome://', 'chrome-extension://', 557 'javascript:', 558 'data:', 559 */ 560 ); 561 } 562 563 564 /** 565 * Converts and fixes HTML entities. 566 * 567 * This function normalizes HTML entities. It will convert "AT&T" to the correct 568 * "AT&T", ":" to ":", "&#XYZZY;" to "&#XYZZY;" and so on. 569 * 570 * @since 1.6 571 * 572 * @param string $string Content to normalize entities 573 * @return string Content with normalized entities 574 */ 575 function yourls_kses_normalize_entities($string) { 576 # Disarm all entities by converting & to & 577 578 $string = str_replace('&', '&', $string); 579 580 # Change back the allowed entities in our entity whitelist 581 582 $string = preg_replace_callback('/&([A-Za-z]{2,8});/', 'yourls_kses_named_entities', $string); 583 $string = preg_replace_callback('/&#(0*[0-9]{1,7});/', 'yourls_kses_normalize_entities2', $string); 584 $string = preg_replace_callback('/&#[Xx](0*[0-9A-Fa-f]{1,6});/', 'yourls_kses_normalize_entities3', $string); 585 586 return $string; 587 } 588 589 /** 590 * Callback for yourls_kses_normalize_entities() regular expression. 591 * 592 * This function only accepts valid named entity references, which are finite, 593 * case-sensitive, and highly scrutinized by HTML and XML validators. 594 * 595 * @since 1.6 596 * 597 * @param array $matches preg_replace_callback() matches array 598 * @return string Correctly encoded entity 599 */ 600 function yourls_kses_named_entities($matches) { 601 global $yourls_allowedentitynames; 602 603 if ( empty($matches[1]) ) 604 return ''; 605 606 // KSES globals are normally populated on the 'plugins_loaded' action. This can run 607 // earlier though (eg yourls_die() on a DB connection error, before plugins load), so 608 // make sure the allowed entity names are available. 609 if ( ! is_array($yourls_allowedentitynames) ) { 610 yourls_kses_init(); 611 } 612 613 $i = $matches[1]; 614 return ( ( ! in_array($i, $yourls_allowedentitynames) ) ? "&$i;" : "&$i;" ); 615 } 616 617 /** 618 * Callback for yourls_kses_normalize_entities() regular expression. 619 * 620 * This function helps yourls_kses_normalize_entities() to only accept 16-bit values 621 * and nothing more for &#number; entities. 622 * 623 * @access private 624 * @since 1.6 625 * 626 * @param array $matches preg_replace_callback() matches array 627 * @return string Correctly encoded entity 628 */ 629 function yourls_kses_normalize_entities2($matches) { 630 if ( empty($matches[1]) ) 631 return ''; 632 633 $i = $matches[1]; 634 if (yourls_valid_unicode($i)) { 635 $i = str_pad(ltrim($i,'0'), 3, '0', STR_PAD_LEFT); 636 $i = "&#$i;"; 637 } else { 638 $i = "&#$i;"; 639 } 640 641 return $i; 642 } 643 644 /** 645 * Callback for yourls_kses_normalize_entities() for regular expression. 646 * 647 * This function helps yourls_kses_normalize_entities() to only accept valid Unicode 648 * numeric entities in hex form. 649 * 650 * @access private 651 * @since 1.6 652 * 653 * @param array $matches preg_replace_callback() matches array 654 * @return string Correctly encoded entity 655 */ 656 function yourls_kses_normalize_entities3($matches) { 657 if ( empty($matches[1]) ) 658 return ''; 659 660 $hexchars = $matches[1]; 661 return ( ( ! yourls_valid_unicode(hexdec($hexchars)) ) ? "&#x$hexchars;" : '&#x'.ltrim($hexchars,'0').';' ); 662 } 663 664 /** 665 * Helper function to add global attributes to a tag in the allowed html list. 666 * 667 * @since 1.6 668 * @access private 669 * 670 * @param array $value An array of attributes. 671 * @return array The array of attributes with global attributes added. 672 */ 673 function _yourls_add_global_attributes( $value ) { 674 $global_attributes = array( 675 'class' => true, 676 'id' => true, 677 'style' => true, 678 'title' => true, 679 ); 680 681 if ( true === $value ) 682 $value = array(); 683 684 if ( is_array( $value ) ) 685 return array_merge( $value, $global_attributes ); 686 687 return $value; 688 } 689 690 /** 691 * Helper function to determine if a Unicode value is valid. 692 * 693 * @since 1.6 694 * 695 * @param int $i Unicode value 696 * @return bool True if the value was a valid Unicode number 697 */ 698 function yourls_valid_unicode($i) { 699 return ( $i == 0x9 || $i == 0xa || $i == 0xd || 700 ($i >= 0x20 && $i <= 0xd7ff) || 701 ($i >= 0xe000 && $i <= 0xfffd) || 702 ($i >= 0x10000 && $i <= 0x10ffff) ); 703 } 704 705 /** 706 * Goes through an array and changes the keys to all lower case. 707 * 708 * @since 1.6 709 * 710 * @param array $inarray Unfiltered array 711 * @return array Fixed array with all lowercase keys 712 */ 713 function yourls_kses_array_lc($inarray) { 714 $outarray = array (); 715 716 foreach ( (array) $inarray as $inkey => $inval) { 717 $outkey = strtolower($inkey); 718 $outarray[$outkey] = array (); 719 720 foreach ( (array) $inval as $inkey2 => $inval2) { 721 $outkey2 = strtolower($inkey2); 722 $outarray[$outkey][$outkey2] = $inval2; 723 } # foreach $inval 724 } # foreach $inarray 725 726 return $outarray; 727 } 728 729 /** 730 * Convert all entities to their character counterparts. 731 * 732 * This function decodes numeric HTML entities (A and A). It doesn't do 733 * anything with other entities like ä, but we don't need them in the URL 734 * protocol whitelisting system anyway. 735 * 736 * @since 1.6 737 * 738 * @param string $string Content to change entities 739 * @return string Content after decoded entities 740 */ 741 function yourls_kses_decode_entities($string) { 742 $string = preg_replace_callback('/&#([0-9]+);/', '_yourls_kses_decode_entities_chr', $string); 743 $string = preg_replace_callback('/&#[Xx]([0-9A-Fa-f]+);/', '_yourls_kses_decode_entities_chr_hexdec', $string); 744 745 return $string; 746 } 747 748 /** 749 * Regex callback for yourls_kses_decode_entities() 750 * 751 * @since 1.6 752 * 753 * @param array $match preg match 754 * @return string 755 */ 756 function _yourls_kses_decode_entities_chr( $match ) { 757 return chr( $match[1] ); 758 } 759 760 /** 761 * Regex callback for yourls_kses_decode_entities() 762 * 763 * @since 1.6 764 * 765 * @param array $match preg match 766 * @return string 767 */ 768 function _yourls_kses_decode_entities_chr_hexdec( $match ) { 769 return chr( hexdec( $match[1] ) ); 770 } 771 772 /** 773 * Removes any null characters in $string. 774 * 775 * @since 1.6 776 * 777 * @param string $string 778 * @return string 779 */ 780 function yourls_kses_no_null($string) { 781 $string = preg_replace( '/\0+/', '', $string ); 782 $string = preg_replace( '/(\\\\0)+/', '', $string ); 783 784 return $string; 785 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Sun Aug 9 05:10:27 2026 | Cross-referenced by PHPXref 0.7.1 |