postfilter.php
1 <?php 2 include_once("rpc.php"); 3 include_once("util.php"); 4 5 function out_of_order_keys($post, $f1, $f2) 6 { 7 $pkeys = array_keys($_POST); 8 9 return array_search($f1, $pkeys) > array_search($f2, $pkeys); 10 } 11 12 // style 1 - no spaces, just letters+numbers in fields 13 function is_random_text($fields) 14 { 15 foreach($fields as $field) { 16 $num = preg_match('/[0-9]/', $field); 17 $lc = preg_match('/[a-z]/', $field); 18 $uc = preg_match('/[A-Z]/', $field); 19 $spc = preg_match('/ /', $field); 20 if ($spc || ($num + $lc + $uc)<2) return false; 21 } 22 23 return true; 24 } 25 26 // style 2 - uc+lc+spaces in fields 27 function is_random_word_text($fields) 28 { 29 foreach($fields as $field) { 30 $other = preg_match('/[^a-zA-Z \n]/', $field); 31 $lc = preg_match('/[a-z]/', $field); 32 $uc = preg_match('/[A-Z]/', $field); 33 $spc = preg_match('/ /', $field); 34 if ($other || !$spc || !$lc || !$uc) return false; 35 } 36 37 return true; 38 } 39 40 function has_cyrillic($txt) 41 { 42 return preg_match('/[\xD0-\xD3][\x80-\xBF]/', $txt) > 0; 43 } 44 45 function expand_short_urls($com) 46 { 47 $urls = array(); 48 $urltext = ""; 49 50 $com = preg_replace("/\(dot\)|DOT/", ".", $com); 51 preg_replace("@(([A-Za-z0-9_-]+\.)?((tinyurl|go2cut|doiop|x2t|snipr|gorkk|veeox|gurlx|goshrink|shrink[a-z]+|shortmaker|notlong|2gohere|urlmr|adjix|icanhaz|linkbee|oeeq|url9|urlzen|cladpal|redirx|yuarel|llfk|as2h|at|url-go|shrten|eyodude|urlxp|myurlz|allno1|nlz2|ye-s|way|lymme|unrelo|qfwr|urluda|golinkgo|cnekt|12n3|peqno|pasukan|vktw|snipie|4gk|82au|[a-z]+url|tinyden)\\.com|(lix|urlm|t2w|trigg|flib)\\.in|j\\.mp|hub\\.tm|(smarturl|fogz|urlink)\\.eu|sn\\.vc|atu\\.ca|(a|is)\\.gd|(xrl|nuurl|kore|p3n|txtn|hepy|w95|freepl|0x3|2tu)\\.us|dduri\\.info|cc\\.st|mzan\\.si|(\xe2\x9d\xbd|cctv)\\.ws|(metamark|sogood|idek|2tr|ln-s|urlaxe|littleurl)\\.net|(fyad|linkmenow|linkplug)\\.org|ad\\.vu|(bit|xa|ow|3|smal|to)\\.ly|safe\\.mn|goo\\.gl|is\\.gd|zi\\.ma|(tr|sn|jar|gow)\\.im|twurl\\.(cc|nl)|(fon|cli|rod|mug)\\.gs|(urlenco|dboost)\\.de|(tiny|bizz|blu|juu)\\.cc|2big\\.at|(crum|sk9)\\.pl|(bloat|pnt|lnq)\\.me|kl\\.am|(showip|2so)\\.be|minilink\\.me|lurl\\.no|(jai|cvm)\\.biz|xs\\.md|short\\.to|(yep|trunc)\\.it|a\\.nf|shortlinks\\.co\\.uk|redir\\.ec|tim\\.pe)(/[?A-Za-z0-9_-]*)?)@ei", '$urls[] = "http://$1";', $com); 52 53 foreach($urls as $url) { 54 $com .= strtolower(rpc_find_real_url(str_replace("preview.tinyurl","tinyurl",$url))); 55 } 56 57 return $com; 58 } 59 60 function normalize_ascii($text, $preserve_case = 0) 61 { 62 $text = preg_replace( '#[(\[={]dot[)\]=}]#i', '.', $text ); 63 64 $t = Transliterator::create("Any-Latin; nfd; [:nonspacing mark:] remove; nfkc; Latin-ASCII"); 65 if (!$t) return $text; 66 $text = $t->transliterate($text); 67 if (!$preserve_case) $text = strtolower($text); 68 69 return $text; 70 } 71 72 function strip_zerowidth($text) { 73 $text = preg_replace('/ 74 [\x17\x8\x1f]|\x{0702}|\x{1D176}|\x{008D}|\x{00A0}|\x{205F}|\x{FEFF}|\x{11A6}|\x{00AD}|\x{3164}|\x{2800}|\x{180B}|\x{180C}|\x{180D}| 75 \x{115F}|\x{1160}|\x{FFA0}|\x{034f}|\x{180e}|\x{17B4}|\x{17B5}| 76 [\x{0001}-\x{0008}\x{000E}\x{000F}\x{0010}-\x{001F}\x{007F}-\x{009F}]| 77 [\x{2000}-\x{200F}]| 78 [\x{2028}-\x{202F}]| 79 [\x{2060}-\x{206F}]| 80 [\x{fe00}-\x{fe0f}]| 81 [\x{FFF0}-\x{FFFB}]| 82 [\x{E0100}-\x{E01EF}]| 83 [\x{E0001}-\x{E007F}] 84 /ux', '', $text); 85 return $text; 86 } 87 88 /** 89 * Removes codepoints above 3134F: 90 * E0000..E007F; Tags 91 * E0100..E01EF; Variation Selectors Supplement 92 * F0000..FFFFF; Supplementary Private Use Area-A 93 * 100000..10FFFF; Supplementary Private Use Area-B 94 */ 95 function strip_private_unicode($str) { 96 if ($str === '') { 97 return $str; 98 } 99 100 return preg_replace('/[^\x{0000}-\x{3134F}]/u', '', $str); 101 } 102 103 function strip_emoticons($text, $has_sjis_art = false) { 104 $regex = '[\x{2300}-\x{2311}\x{2313}-\x{23FF}]|[\x{3200}-\x{32FF}\x{2190}-\x{21FF}\x{2580}-\x{259F}\x{2600}-\x{26FF}\x{2B00}-\x{2BFE}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7D8}\x{1F780}-\x{1F7D8}\x{1F800}-\x{1F8FF}\x{1F900}-\x{1F9FF}]|[\x{1F200}-\x{1F2FF}]|[\x{2460}-\x{24FF}]|[\x{1F100}-\x{1F1FF}]|[\x{1F600}-\x{1F64F}]|[\x{1F300}-\x{1F5FF}]|[\x{1F680}-\x{1F6FF}]|[\x{2600}-\x{26FF}]|[\x{2700}-\x{27BF}]|[\x{1F000}-\x{1F02F}]|[\x{1F0A0}-\x{1F0FF}]|[\x{2139}\x{23F2}]|[\x{1F910}-\x{1F9E6}]|[\x{0365}]|\x{FDFD}|[\x{0488}\x{0489}\x{1abe}\x{20dd}\x{20de}\x{20df}\x{20e0}\x{20e2}\x{20e3}\x{20e4}\x{a670}\x{a671}\x{a672}\x{061c}\x{070F}\x{0332}\x{0305}\x{2B55}]|[\x{202A}-\x{202E}\x{2060}-\x{206F}]|[\x{200E}\x{200F}\x{180e}\x{2b50}\x{23b3}\x{23F1}]|[\x{1F780}-\x{1F7FF}\x{1FA70}-\x{1FAFF}]|[\x{1D173}-\x{1D17A}\x{13000}-\x{1342F}\x{fe00}-\x{fe0f}]'; 105 106 if (!$has_sjis_art) { 107 $regex .= '|[\x{2502}-\x{257F}]'; 108 } 109 110 return preg_replace("/$regex/u", '', $text); 111 } 112 113 function strip_fake_capcodes($str) { 114 // double FULLWIDTH NUMBER SIGN or # 115 // PLACE OF INTEREST SIGN 116 return preg_replace('/[\x{FF03}#]{2,}|\x{2318}/u', '', $str); 117 } 118 119 function normalize_text($text, $filter = '') { 120 $text = normalize_ascii($text); 121 $text = strip_zerowidth($text); 122 123 //if ($filter) $text = $filter($text); 124 125 $text = preg_replace('@[^a-zA-Z0-9.,/&:;?=~_-]@', '', $text); 126 127 return $text; 128 } 129 130 function normalize_content( $name ) 131 { 132 // this needs some absolutely retarded shit to get this to not suck, however 133 // it is an almost fool proof way of translating to ascii letters 134 // without breaking kanji, cyrillic etc 135 136 $name = preg_replace( '#[\x{2600}-\x{26FF}]#u', '', $name ); 137 138 // set internal incoding to utf-8 139 //die($name); 140 $oldEncoding = mb_internal_encoding(); 141 mb_internal_encoding('UTF-8'); 142 $name = convert_to_utf8($name); 143 // Done, back to old encoding 144 145 $newname = ''; 146 147 $len = mb_strlen( $name ); 148 for( $i = 0; $i < $len; $i++ ) { 149 trans_similar_to_ascii( $newname, mb_substr( $name, $i, 1 ) ); 150 } 151 152 mb_internal_encoding($oldEncoding); 153 return $newname; 154 } 155 156 function convert_to_utf8( $content ) 157 { 158 if( !mb_check_encoding( $content, 'UTF-8' ) || !($content === mb_convert_encoding(mb_convert_encoding($content, 'UTF-32', 'UTF-8' ), 'UTF-8', 'UTF-32')) ) { 159 $content = mb_convert_encoding($content, 'UTF-8'); 160 } 161 162 return $content; 163 } 164 165 function mb_ord( $char ) 166 { 167 mb_detect_order( array( 'UTF-8', 'ISO-8859-15', 'ISO-8859-1', 'ASCII' ) ); 168 $result = unpack( 'N', mb_convert_encoding( $char, 'UCS-4BE', 'UTF-8' ) ); 169 170 if( is_array( $result ) === true ) return $result[1]; 171 172 return ord($char); 173 } 174 175 function normalize_check($com,$sub,$f) 176 { 177 $n = normalize_text($com.$sub, "expand_short_urls"); 178 $n2 = preg_replace("/[\x80-\xFF]/", "", html_entity_decode($com, ENT_QUOTES, "UTF-8")); 179 180 record_post_info($f, "from: $com$sub\nto: $n\ndeutf8: $n2"); 181 } 182 183 function match_banned_text($links, $text, $is_re) 184 { 185 $badlink = ""; 186 $should_ban = false; 187 188 foreach ($links as $l) { 189 if ($l == '#') continue; 190 191 $badlink = $l; 192 193 if ($is_re) { 194 $should_ban = preg_match($l, $text, $m) > 0; 195 $badlink = TEST_BOARD ? "'$l' ({$m[0]})" : "'{$m[0]}'"; 196 } else { 197 $should_ban = strpos($text, $l) !== FALSE; 198 } 199 200 if ($should_ban) 201 break; 202 } 203 204 return $should_ban ? $badlink : ""; 205 } 206 207 function check_banned_links($text, $links, $priv, $pub, $is_re, $name, $dest, $ban, $long, $perm = false) 208 { 209 //check_banned_links($normalized_com, $sex, "sex spam links", S_BANNEDLINK, false, $name, $dest, true, false); 210 $badlink = match_banned_text($links, $text, $is_re); 211 $should_ban = ($badlink != ""); 212 $len = $long ? 14 : 1; 213 $len = $perm ? -1 : $len; 214 215 if ($should_ban == true) { 216 $privres = sprintf("banned %s %s: %s", $is_re?"regex":"string",htmlspecialchars($badlink),$priv); 217 if ($ban) { 218 $pub = str_replace('Error: ', '', $pub); 219 auto_ban_poster($name, $len, 1, $privres, $pub, true); 220 } 221 if (TEST_BOARD) $pub .= "<br>".$privres; 222 error($pub, $dest); 223 } 224 } 225 226 // this used to check the autobans table but now it just permabans 227 function auto_ban($name, $reason) { 228 auto_ban_poster($name, 7, 1, $reason); 229 } 230 231 function get_jpeg_dimensions($contents) 232 { 233 // this is faster than getimagesize 234 235 $i = 0; 236 $len = strlen($contents); 237 238 if( ord($contents{0}) == 0xFF & ord($contents{1}) == 0xD8 && ord($contents{2}) == 0xFF & ord($contents{3}) == 0xE0 ) { 239 $i = 4; 240 241 if( $contents{$i+2} == 'J' && $contents{$i+3} == 'F' && $contents{$i+4} == 'I' && $contents{$i+5} == 'F' && ord($contents{$i+6}) == 0x00 ) { 242 // valid image. 243 $block_length = ord($contents{$i}) * 256 + ord($contents{$i+1}); 244 245 while( $i < $len ) { 246 $i += $block_length; 247 248 if( $i > $len ) { 249 return false; 250 } 251 252 if( ord($contents{$i}) != 0xFF ) { 253 return false; 254 } 255 256 257 if( ord($contents{$i+1}) == 0xC0 ) { 258 $width = ord($contents{$i+7})*256 + ord($contents{$i+8}); 259 $height = ord($contents{$i+5})*256 + ord($contents{$i+6}); 260 261 return array($width, $height); 262 } else { 263 $i+=2; 264 $block_length = ord($contents{$i}) * 256 + ord($contents{$i+1}); 265 } 266 } 267 } 268 } 269 270 return false; 271 } 272 273 function file_too_big_for_type( $ext, $w, $h, $fsize ) 274 { 275 if ($ext === ".gif" || $ext === ".pdf" || $ext === '.webm' || $ext === '.mp4') { 276 return NO; 277 } 278 279 $uncompressed_size = $w * $h * 4; 280 281 return ($fsize > (3*$uncompressed_size)) ? YES : NO; 282 } 283 284 function regex_ignoring_nulls($words) 285 { 286 $rwords = preg_replace("/./", "$0[^\\\\x01-\\\\xFF]*", $words); 287 $rwords = str_replace(".", "\\.", $rwords); 288 return "/".implode("|", $rwords)."/i"; 289 } 290 291 /** 292 * Strips exif from JPEG images 293 * $file needs to be safe to use as shell argument 294 */ 295 function strip_jpeg_exif($file) { 296 return system("/usr/local/bin/jpegtran -copy none -outfile '$file' '$file'") !== false; 297 } 298 299 /** 300 * Strips non-whitelisted PNG chunks. 301 * Returns an error if an animated PNG is detected. 302 * Overwrites input file if modifications have been made. 303 * $file needs to be safe to use as shell argument 304 * Returns the number of chunks skipped or an error code (negative value). 305 */ 306 function strip_png_chunks($file, $max_chunk_len = 16 * 1024 * 1024) { 307 $keep_chunks = [ 308 'ihdr', 309 'plte', 310 'idat', 311 'iend', 312 'trns', 313 'gama', 314 'sbit', 315 'phys', 316 'srgb', 317 'bkgd', 318 'time', 319 'chrm', 320 'iccp' 321 ]; 322 323 $img = fopen($file, 'rb'); 324 325 if (!$img) { 326 return -9; 327 } 328 329 $data = fread($img, 8); 330 331 if ($data !== "\x89PNG\r\n\x1a\n") { 332 fclose($img); 333 return -1; 334 } 335 336 $output = ''; 337 338 $skip_count = 0; 339 340 while (!feof($img)) { 341 $chunk_len_buf = fread($img, 4); 342 343 if (!$chunk_len_buf) { 344 break; 345 } 346 347 if (strlen($chunk_len_buf) !== 4) { 348 return -1; 349 } 350 351 $chunk_len = unpack('N', $chunk_len_buf)[1]; 352 353 if ($chunk_len > $max_chunk_len) { 354 return -1; 355 } 356 357 $chunk_type_buf = fread($img, 4); 358 359 if (strlen($chunk_type_buf) !== 4) { 360 return -1; 361 } 362 363 $chunk_type = strtolower($chunk_type_buf); 364 365 // aPNG is not supported 366 if ($chunk_type === 'actl' || $chink_type === 'fctl' || $chink_type === 'fdat') { 367 return -2; 368 } 369 370 if (in_array($chunk_type, $keep_chunks)) { 371 if ($chunk_len > 0) { 372 $data = fread($img, $chunk_len); 373 374 if (strlen($data) !== $chunk_len) { 375 return -1; 376 } 377 } 378 else { 379 $data = ''; 380 } 381 382 $crc = fread($img, 4); 383 384 if (strlen($crc) !== 4) { 385 return -1; 386 } 387 388 $output .= $chunk_len_buf . $chunk_type_buf . $data . $crc; 389 390 if ($chunk_type === 'iend') { 391 fread($img, 1); 392 if (!feof($img)) { 393 $skip_count++; 394 } 395 break; 396 } 397 } 398 else { 399 fseek($img, $chunk_len + 4, SEEK_CUR); 400 $skip_count++; 401 } 402 } 403 404 fclose($img); 405 406 if ($output === '') { 407 return -1; 408 } 409 410 if ($skip_count === 0) { 411 return 0; 412 } 413 414 $out_file = $file . '_pngtmp'; 415 416 $out = fopen($out_file, 'wb'); 417 418 if (!$out) { 419 return -9; 420 } 421 422 if (fwrite($out, "\x89PNG\r\n\x1a\n") === false) { 423 return -9; 424 } 425 426 if (fwrite($out, $output) === false) { 427 return -9; 428 } 429 430 fclose($out); 431 432 if (rename($out_file, $file) === false) { 433 return -9; 434 } 435 436 return $skip_count; 437 } 438 439 // Calculates the actual image data inside a JPEG file and errors out 440 // if it's smaller than the reported size. 441 function validate_jpeg_size($file, $reported_size) { 442 $eof = false; 443 444 $img = fopen($file, 'rb'); 445 446 $data = fread($img, 2); 447 448 if ($data !== "\xff\xd8") { 449 fclose($img); 450 return false; 451 } 452 453 while (!feof($img)) { 454 $data = fread($img, 1); 455 456 if ($data !== "\xff") { 457 continue; 458 } 459 460 while (!feof($img)) { 461 $data = fread($img, 1); 462 463 if ($data !== "\xff") { 464 break; 465 } 466 } 467 468 if (feof($img)) { 469 break; 470 } 471 472 $byte = unpack('C', $data)[1]; 473 474 if ($byte === 217) { 475 $eof = ftell($img); 476 break; 477 } 478 479 if ($byte === 0 || $byte === 1 || ($byte >= 208 && $byte <= 216)) { 480 continue; 481 } 482 483 $data = fread($img, 2); 484 485 $length = unpack('n', $data)[1]; 486 487 if ($length < 1) { 488 break; 489 } 490 491 fseek($img, $length - 2, SEEK_CUR); 492 } 493 494 fclose($img); 495 496 // 50 KB 497 if ($reported_size - $eof >= 51200) { 498 error(S_IMGCONTAINSFILE, $file); 499 } 500 501 return $eof; 502 } 503 504 /** 505 * Checks for extensions and comments 506 * and calculates actual GIF data. 507 * Strips extra data if it exists. 508 * $file needs to be safe to use as shell argument. 509 */ 510 function strip_gif_extra_data($file, $reported_size) { 511 $binary = '/usr/local/bin/gifsicle'; 512 513 $res = shell_exec("$binary --sinfo \"$file\" 2>&1"); 514 515 if ($res !== null) { 516 $size = 0; 517 518 $need_strip = false; 519 520 if (preg_match('/ extensions [0-9]+| comment /', $res)) { 521 $need_strip = true; 522 } 523 else if (preg_match_all('/compressed size ([0-9]+)/', $res, $m)) { 524 foreach ($m[1] as $frame_size) { 525 $size += (int)$frame_size; 526 } 527 528 // Strip if 50+ KB of extra data is found 529 if ($reported_size - $size >= 51200) { 530 $need_strip = true; 531 } 532 } 533 534 if ($need_strip) { 535 if (system("$binary --no-comments --no-extensions \"$file\" -o \"$file\" >/dev/null 2>&1") === false) { 536 // gifsicle error 537 return -1; 538 } 539 else { 540 // file was modified 541 return 1; 542 } 543 } 544 } 545 else { 546 // gifsicle error 547 return -1; 548 } 549 550 // nothing changed 551 return 0; 552 } 553 554 // No longer used 555 function spam_filter_post_image($name, $dest, $md5, $upfile_name, $ext, $w, $h, $fsize) 556 { 557 if( $upfile_name == '' ) error('Blank file names are not supported.'); 558 559 if (file_too_big_for_type($ext, $w, $h, $fsize) === YES) { 560 $lim = 3*4*$w*$h; 561 error(S_IMGCONTAINSFILE, $dest); 562 } 563 564 $img_bytes = file_get_contents($dest); 565 $img_beginning = strlen($img_bytes) > 0x50000 ? substr($img_bytes, 0, 0x40000).substr($img_bytes, -(0x10000)) : $img_bytes; 566 567 global $silent_reject; 568 $silent_reject = 0; 569 570 // protect against IE's retarded MIME-sniffing XSS vulnerability 571 // by doing our own sniffing and rejecting exploitable files 572 { 573 $negative_match = regex_ignoring_nulls(array("minitokyonet", "urchin.js")); 574 //except minitokyo from this, it causes false positives 575 if (preg_match($negative_match, $img_beginning)===0) 576 { 577 // '<body', '<head', '<html', '<plaintext', '<pre', '<table', '<title', '<channel', '<scriptlet', '<span', 578 // taken from URLMON.DLL in win2k... 579 $positive_match = regex_ignoring_nulls(array('<a href', '<script', '<iframe', 'unescape', 'base64', 'charAt', 'del %0', 'WScript.Shell')); 580 if (preg_match($positive_match, $img_beginning, $m)) 581 { 582 $foundxss = true; 583 $foundstr = htmlentities($m[0]); 584 // $xssban = true; 585 } 586 } 587 588 if ($foundxss) { 589 if ($xssban) auto_ban_poster($name, 7, 1, "script in image (from image, found $foundstr at $xsspos)", "Posting image with embedded virus."); 590 if (TEST_BOARD) error("Script in image: '$foundstr' at $xsspos"); 591 error('Detected possible malicious code in image file.', $dest); 592 } 593 594 /* if (BOARD_DIR == 'b' && strpos($img_beginning, "AppleMark")) { 595 record_post_info("/www/perhost/applemark.txt"); 596 $silent_reject = true; 597 return; 598 } */ 599 } 600 // don't allow embedded zips and rars 601 { 602 $rar2 = 'REs^'; 603 $rar1 = "Rar!\x1A\x07\x00"; 604 $zip = "PK\x03\x04"; 605 $sevenz = "7z\xBC\xAF'\x1C"; 606 $pfbind = "pFBind"; 607 $deny = array( 608 'REs^', 609 "Rar!\x1A\x07\x00", 610 "PK\x03\x04", 611 "7z\xBC\xAF'\x1C"/*, 612 "pFBind", 613 "RIFF", 614 "matroska", 615 616 // stupid ogg shit 617 "OggS\x00", 618 'libVorbis', 619 "moot\x00", 620 "Krni\x00"*/ 621 ); 622 623 624 foreach($deny as $arc_string) 625 { 626 if(strpos($img_bytes, $arc_string) !== false) 627 { 628 error(S_IMGCONTAINSFILE, $dest); 629 } 630 } 631 } 632 // reject APNG 633 if($ext == '.png') 634 { 635 if(strpos($img_bytes, 'acTL') !== false && strpos($img_bytes, 'fcTL') !== false && strpos($img_bytes, 'fdAT') !== false) 636 { 637 error('APNG format not supported.', $dest); 638 } 639 } 640 } 641 642 function register_postfilter_hit($filter_id) { 643 $long_ip = (int)ip2long($_SERVER['REMOTE_ADDR']); 644 645 $filter_id = (int)$filter_id; 646 647 $query = <<<SQL 648 SELECT id FROM postfilter_hits 649 WHERE filter_id = $filter_id AND long_ip = $long_ip AND created_on > DATE_SUB(NOW(), INTERVAL 1 HOUR) LIMIT 1 650 SQL; 651 652 $res = mysql_global_call($query); 653 654 if ($res && mysql_num_rows($res) === 1) { 655 return true; 656 } 657 658 $query = "INSERT INTO postfilter_hits (filter_id, board, long_ip) VALUES($filter_id, '%s', $long_ip)"; 659 660 return mysql_global_call($query, BOARD_DIR); 661 } 662 663 function log_postfilter_hit($filter, $board, $thread_id, $name, $sub, $com, $upfile_name) { 664 $ip = $_SERVER['REMOTE_ADDR']; 665 666 $country = $_SERVER['HTTP_X_GEO_COUNTRY']; 667 668 $threat_score = spam_filter_get_threat_score($country, !$thread_id, true); 669 670 $meta = spam_filter_format_http_headers("$name\n$sub\n$com", $country, $upfile_name, $threat_score); 671 672 $action = "filter_{$filter['id']}"; 673 674 $query = <<<SQL 675 INSERT INTO event_log(`type`, `board`, `thread_id`, `ip`, `meta`) 676 VALUES('%s', '%s', %d, '%s', '%s') 677 SQL; 678 679 mysql_global_call($query, $action, $board, $thread_id, $ip, $meta); 680 } 681 682 /** 683 * New postfilter. Uses the database. 684 * Errors-out if a Reject filter matches. 685 * Returns true if an Autosage filter matches. 686 * Otherwise returns false. 687 */ 688 function spam_filter_post_content_new($board, $resto, $com, $sub, $name, $upfile_name, $pwd = null, $pass_id = null) { 689 if (preg_match('/^php../', $upfile_name) === 1 && strpos($upfile_name, '.') === false) { 690 auto_ban_poster($name, 14, 1, 'PHP proxy (via filename check)', 'Proxy/Tor exit node.'); 691 error(S_GENERICERROR); 692 } 693 694 // Postfilter 695 $tbl = 'postfilter'; 696 697 $query = <<<SQL 698 SELECT id, pattern, autosage, log, regex, quiet, lenient, ops_only, min_count, board, ban_days FROM $tbl 699 WHERE active = 1 AND (board = '' OR board = '%s') 700 SQL; 701 702 $res = mysql_global_call($query, $board); 703 704 if (!$res) { 705 return false; 706 } 707 708 // Remove bbcode 709 if (strpos($com, '[') !== false) { 710 $com = preg_replace('/\[\/?(?:spoiler|code|sjis)\]/', '', $com); 711 } 712 713 // For string filters 714 $normalized_com = normalize_text($name.$sub.$upfile_name.$com); 715 // For regex filters 716 $expanded_com = "$name $sub $com"; 717 // For autosage filters 718 if (!$resto) { 719 $normalized_com_sage = preg_replace('/[.,!:>\/]+|>/', ' ', $sub . ' ' . $com . ' '. $name); 720 $normalized_com_sage = ucwords(strtolower($normalized_com_sage)); 721 $normalized_com_sage = normalize_ascii($normalized_com_sage, 1); 722 } 723 724 $userpwd = UserPwd::getSession(); 725 726 if ($userpwd && $userpwd->isUserKnownOrVerified(1440) && $userpwd->postCount() > 10) { // 24 hours, 10 posts 727 $user_is_known = true; 728 } 729 else { 730 $user_is_known = false; 731 } 732 733 $matched_filter = false; 734 735 while ($filter = mysql_fetch_assoc($res)) { 736 // Counter mode: triggers when the number of matches is at least $min_count 737 $min_count = (int)$filter['min_count']; 738 739 if ($min_count < 1) { 740 $min_count = 1; 741 } 742 743 // Lenient filter 744 if ($user_is_known && $filter['lenient']) { 745 continue; 746 } 747 748 // OPs-only filter 749 if ($filter['ops_only'] && $resto) { 750 continue; 751 } 752 753 if ($filter['autosage']) { 754 // Autosage filter but the post is a reply 755 if ($resto) { 756 continue; 757 } 758 // Regex filter 759 if ($filter['regex']) { 760 if ($min_count > 1) { 761 if (preg_match_all($filter['pattern'], $expanded_com) >= $min_count) { 762 $matched_filter = $filter; 763 break; 764 } 765 } 766 else { 767 if (preg_match($filter['pattern'], $expanded_com) === 1) { 768 $matched_filter = $filter; 769 break; 770 } 771 } 772 } 773 // String filter for autosaging 774 else { 775 if ($min_count > 1) { 776 if (substr_count($normalized_com_sage, $filter['pattern']) >= $min_count) { 777 $matched_filter = $filter; 778 break; 779 } 780 } 781 else { 782 if (strpos($normalized_com_sage, $filter['pattern']) !== false) { 783 $matched_filter = $filter; 784 break; 785 } 786 } 787 } 788 } 789 // Regex filter 790 if ($filter['regex']) { 791 if ($min_count > 1) { 792 if (preg_match_all($filter['pattern'], $expanded_com) >= $min_count) { 793 $matched_filter = $filter; 794 break; 795 } 796 } 797 else { 798 if (preg_match($filter['pattern'], $expanded_com) === 1) { 799 $matched_filter = $filter; 800 break; 801 } 802 } 803 } 804 // String filter 805 else { 806 if ($min_count > 1) { 807 if (substr_count($normalized_com, $filter['pattern']) >= $min_count) { 808 $matched_filter = $filter; 809 break; 810 } 811 } 812 else { 813 if (strpos($normalized_com, $filter['pattern']) !== false) { 814 $matched_filter = $filter; 815 break; 816 } 817 } 818 } 819 } 820 821 if ($matched_filter !== false) { 822 // Update hit stats 823 register_postfilter_hit($matched_filter['id']); 824 825 // Autosage 826 if ($matched_filter['autosage']) { 827 return true; 828 } 829 // Log 830 else if ($matched_filter['log']) { 831 log_postfilter_hit($matched_filter, $board, $resto, $name, $sub, $com, $upfile_name); 832 } 833 // Reject 834 else { 835 if ($matched_filter['ban_days']) { 836 $err = S_BANNEDTEXT; 837 $ban_days = (int)$matched_filter['ban_days']; 838 $private_reason = 'banned string in comment (filter ID: ' . $matched_filter['id'] . ')'; 839 $public_reason = $err; 840 auto_ban_poster($name, $ban_days, 1, $private_reason, $public_reason, true, $pwd, $pass_id); 841 } 842 else { 843 $err = S_REJECTTEXT; 844 } 845 846 if ($matched_filter['quiet']) { 847 show_post_successful_fake($resto); 848 die(); 849 } 850 851 if (TEST_BOARD) { 852 $err .= ' (filter ID: ' . $matched_filter['id'] . ')'; 853 } 854 855 error($err); 856 } 857 } 858 859 // Other 860 if ($sub !== '') { 861 $normalized_sub = normalize_text($sub); 862 863 if (stripos($sub, 'moot') !== false) { 864 error("You can't post with that subject."); 865 } 866 867 if (stripos($normalized_com, '##') !== false || stripos($sub, 'admin') !== false) { 868 error("You can't post with that subject."); 869 } 870 } 871 872 return false; 873 } 874 875 function isIPRangeBannedReport($long_ip, $asn, $board, $userpwd = null) { 876 return isIPRangeBanned($long_ip, $asn, 877 [ 878 'board' => $board, 879 'is_report' => true, 880 'userpwd' => $userpwd, 881 ] 882 ); 883 } 884 885 // Checks if the IP is rangebanned 886 // options: 887 // board(string), is_sfw(bool, requires board) 888 // userpwd(UserPwd): instance of UserPwd or null, 889 // is_report(bool), is_op(bool), has_img(bool), 890 // browser_id(string), 891 // op_content(string): content of the thread OP for per-thread bans (unused) 892 // returns the rangeban database entry if the IP is banned, false otherwise 893 function isIPRangeBanned($long_ip, $asn, $options = []) { 894 $long_ip = (int)$long_ip; 895 896 $asn = (int)$asn; 897 898 $now = (int)$_SERVER['REQUEST_TIME']; 899 900 $cols = 'created_on, updated_on, expires_on, active, boards, ops_only, img_only, lenient, report_only, ua_ids'; 901 902 $query = <<<SQL 903 (SELECT SQL_NO_CACHE $cols FROM iprangebans 904 WHERE range_start <= $long_ip AND range_end >= $long_ip AND active = 1 905 AND (expires_on = 0 OR expires_on > $now)) 906 SQL; 907 908 if ($asn > 0) { 909 $query .= <<<SQL 910 UNION (SELECT $cols FROM iprangebans 911 WHERE asn = $asn AND active = 1 AND (expires_on = 0 OR expires_on > $now)) 912 SQL; 913 } 914 915 $query .= ' ORDER BY lenient ASC'; 916 917 $res = mysql_global_call($query); 918 919 if (!$res) { 920 return false; 921 } 922 923 // Parameters 924 if (isset($options['board'])) { 925 $board = $options['board']; 926 $is_sfw = isset($options['is_sfw']) && $options['is_sfw']; 927 } 928 else { 929 $board = null; 930 $is_sfw = false; 931 } 932 933 if (isset($options['browser_id'])) { 934 $browser_id = $options['browser_id']; 935 } 936 else { 937 $browser_id = null; 938 } 939 940 if (isset($options['req_sig'])) { 941 $req_sig = $options['req_sig']; 942 } 943 else { 944 $req_sig = null; 945 } 946 947 if (isset($options['op_content']) && $options['op_content'] !== '') { 948 $op_content = $options['op_content']; 949 } 950 else { 951 $op_content = null; 952 } 953 954 $is_op = isset($options['is_op']) && $options['is_op']; 955 $is_report = isset($options['is_report']) && $options['is_report']; 956 $has_img = isset($options['has_img']) && $options['has_img']; 957 958 if (isset($options['userpwd']) && $options['userpwd'] && $options['userpwd'] instanceof UserPwd) { 959 $userpwd = $options['userpwd']; 960 } 961 else { 962 $userpwd = null; 963 } 964 965 // OP-only and Image-only lenient rangebans also require a certain number of posts 966 $post_count_ok = $userpwd && $userpwd->postCount() >= 3 && ($userpwd->maskLifetime() > 900 || $userpwd->postCount() >= 15); 967 968 while ($range = mysql_fetch_assoc($res)) { 969 if ($range['boards']) { 970 if ($board === null) { 971 continue; 972 } 973 974 $board_matcher = ",{$range['boards']},"; 975 976 if (strpos($board_matcher, ",$board,") === false) { 977 // _ws_ scope affects all work safe boards 978 if ($is_sfw) { 979 if (strpos($board_matcher, ",_ws_,") === false) { 980 continue; 981 } 982 } 983 else { 984 continue; 985 } 986 } 987 } 988 989 $post_count_check = true; 990 991 if ($range['report_only'] && !$is_report) { 992 continue; 993 } 994 995 if ($range['ops_only']) { 996 if (!$is_op) { 997 continue; 998 } 999 else { 1000 $post_count_check = $post_count_ok; 1001 } 1002 } 1003 1004 if ($range['img_only']) { 1005 if (!$has_img) { 1006 continue; 1007 } 1008 else { 1009 $post_count_check = $post_count_ok; 1010 } 1011 } 1012 1013 if ($range['ua_ids']) { 1014 $_skip = true; 1015 1016 if ($browser_id && strpos($range['ua_ids'], $browser_id) !== false) { 1017 $_skip = false; 1018 } 1019 1020 if ($_skip && $req_sig && strpos($range['ua_ids'], $req_sig) !== false) { 1021 $_skip = false; 1022 } 1023 1024 if ($_skip) { 1025 continue; 1026 } 1027 } 1028 1029 if ($userpwd && $range['lenient']) { 1030 $lenient = (int)$range['lenient']; 1031 1032 if ($range['updated_on']) { 1033 $since_ts = (int)$range['updated_on']; 1034 } 1035 else { 1036 $since_ts = (int)$range['created_on']; 1037 } 1038 1039 // Mode 1: Known 24h or Verified 1040 if ($lenient === 1 && ($userpwd->verifiedLevel() || ($userpwd->isUserKnown(1440, $since_ts) && $post_count_check))) { 1041 continue; 1042 } 1043 // Mode 2: Known 24h only 1044 else if ($lenient === 2 && $userpwd->isUserKnown(1440, $since_ts) && $post_count_check) { 1045 continue; 1046 } 1047 // Mode 3: Verified only 1048 else if ($lenient === 3 && $userpwd->verifiedLevel()) { 1049 continue; 1050 } 1051 } 1052 1053 return $range; 1054 } 1055 1056 return false; 1057 } 1058 1059 /** 1060 * Checks if the IP has enough posting history 1061 * $mode: 0 = check for replies, 1 = check for image replies, 2 = check of threads 1062 * Caches results. 1063 */ 1064 function spam_filter_is_ip_known($long_ip, $board = null, $mode = 0, $minutes_min = 0, $posts_min = 1) { 1065 static $cache = array(); 1066 1067 $long_ip = (int)$long_ip; 1068 1069 if (!$long_ip) { 1070 return false; 1071 } 1072 1073 $cache_key = "$long_ip.$board.$mode.$minutes_min.$posts_min"; 1074 1075 if (isset($cache[$cache_key])) { 1076 return $cache[$cache_key]; 1077 } 1078 1079 // Not after (3 days) 1080 $minutes_max = 4320; 1081 1082 // Not before 1083 $minutes_min = (int)$minutes_min; 1084 1085 // At least X replies 1086 $posts_min = (int)$posts_min; 1087 1088 // Board 1089 if ($board) { 1090 $board_clause = "AND board = '" . mysql_real_escape_string($board) . "'"; 1091 } 1092 else { 1093 $board_clause = ''; 1094 } 1095 1096 // Mode: 1 = image replies, 2 = threads, 0 = any reply 1097 if ($mode === 1) { 1098 $action_clause = "AND action = 'new_reply' AND had_image = 1"; 1099 } 1100 else if ($mode === 2) { 1101 $action_clause = "AND action = 'new_thread'"; 1102 } 1103 else { 1104 $action_clause = "AND action = 'new_reply'"; 1105 } 1106 1107 // Not before 1108 if (!$minutes_min) { 1109 $time_clause = "time >= DATE_SUB(NOW(), INTERVAL $minutes_max MINUTE)"; 1110 } 1111 else { 1112 $time_clause = "(time BETWEEN DATE_SUB(NOW(), INTERVAL $minutes_max MINUTE) AND DATE_SUB(NOW(), INTERVAL $minutes_min MINUTE))"; 1113 } 1114 1115 // Check posting history 1116 $query = <<<SQL 1117 SELECT SQL_NO_CACHE COUNT(*) FROM user_actions 1118 WHERE ip = $long_ip $action_clause $board_clause 1119 AND $time_clause 1120 SQL; 1121 1122 $res = mysql_global_call($query); 1123 1124 if (!$res) { 1125 return false; 1126 } 1127 1128 $count = (int)mysql_fetch_row($res)[0]; 1129 1130 if ($count < $posts_min) { 1131 $cache[$cache_key] = false; 1132 return false; 1133 } 1134 1135 // Check deletion history 1136 /* 1137 $query = <<<SQL 1138 SELECT SQL_NO_CACHE COUNT(*) FROM user_actions 1139 WHERE ip = $long_ip AND action = 'delete' 1140 AND time >= DATE_SUB(NOW(), INTERVAL 48 HOUR) 1141 SQL; 1142 1143 $res = mysql_global_call($query); 1144 1145 if (!$res) { 1146 return false; 1147 } 1148 1149 $count = (int)mysql_fetch_row($res)[0]; 1150 1151 if ($count > 0) { 1152 $cache[$cache_key] = false; 1153 return false; 1154 } 1155 */ 1156 1157 $cache[$cache_key] = true; 1158 1159 return true; 1160 } 1161 1162 /* 1163 * Checks if the user has a valid posting history to bypass a rangeban (FIXME: deprecated) 1164 */ 1165 function spam_filter_is_user_known($long_ip, $board = null, $pwd = null, $minutes = 15, $count = 1) { 1166 static $cache = array(); 1167 1168 $pwd = null; // FIXME 1169 1170 $interval = (int)$minutes; 1171 1172 $cache_key_ip = "{$long_ip}:{$interval}"; 1173 1174 if (isset($cache[$cache_key_ip])) { 1175 return $cache[$cache_key_ip]; 1176 } 1177 1178 if ($pwd && $board) { 1179 $cache_key_pwd = "{$board}:{$pwd}:{$interval}"; 1180 1181 if (isset($cache[$cache_key_pwd])) { 1182 return $cache[$cache_key_pwd]; 1183 } 1184 } 1185 1186 $count = (int)$count; 1187 1188 if ($count < 1) { 1189 $count = 1; 1190 } 1191 1192 // Check the IP 1193 $query = <<<SQL 1194 SELECT 1 FROM user_actions 1195 WHERE ip = %d AND action = 'new_reply' 1196 AND time < DATE_SUB(NOW(), INTERVAL $interval MINUTE) 1197 LIMIT $count 1198 SQL; 1199 1200 $res = mysql_global_call($query, $long_ip); 1201 1202 if (!$res) { 1203 return true; 1204 } 1205 1206 if (mysql_num_rows($res) === $count) { 1207 $cache[$cache_key_ip] = true; 1208 return true; 1209 } 1210 1211 $cache[$cache_key_ip] = false; 1212 1213 // Check the password 1214 if ($pwd && $board) { 1215 $time_lim = $_SERVER['REQUEST_TIME'] - ($interval * 60); 1216 1217 $query = <<<SQL 1218 SELECT 1 FROM `%s` WHERE pwd = '%s' AND time < $time_lim LIMIT 1 1219 SQL; 1220 1221 $res = mysql_board_call($query, $board, $pwd); 1222 1223 if (!$res) { 1224 return true; 1225 } 1226 1227 if (mysql_num_rows($res)) { 1228 $cache[$cache_key_pwd] = true; 1229 return true; 1230 } 1231 1232 $cache[$cache_key_pwd] = false; 1233 } 1234 1235 return false; 1236 } 1237 1238 /** 1239 * Generates the ID of the device from browser's user agent 1240 */ 1241 function spam_filter_get_browser_id() { 1242 static $cache = null; 1243 1244 if ($cache !== null) { 1245 return $cache; 1246 } 1247 1248 $ua = $_SERVER['HTTP_USER_AGENT']; 1249 1250 if (!$ua) { 1251 return '0deadbeef'; 1252 } 1253 1254 if (preg_match('/Android|iPhone|iPad|Dalvik|Clover|Kuroba|ChanOS/', $ua)) { 1255 $is_mobile = 1; 1256 } 1257 else if (isset($_SERVER['HTTP_SEC_CH_UA_MOBILE']) && $_SERVER['HTTP_SEC_CH_UA_MOBILE'] === '?1') { 1258 $is_mobile = 1; 1259 } 1260 else { 1261 $is_mobile = 0; 1262 } 1263 1264 $clean_ua = preg_replace('/(\/|:)[.0-9]+/', '', $ua); 1265 1266 if (isset($_SERVER['HTTP_SEC_CH_UA_MODEL']) && $_SERVER['HTTP_SEC_CH_UA_MODEL'] && $_SERVER['HTTP_SEC_CH_UA_MODEL'] != '""') { 1267 $fmn = isset($_SERVER['HTTP_SEC_FETCH_MODE']) && $_SERVER['HTTP_SEC_FETCH_MODE'] === 'navigate'; 1268 1269 if (strpos($ua, '(Linux; Android 10; K)') !== false && !$fmn) { 1270 $clean_ua .= $_SERVER['HTTP_SEC_CH_UA_MODEL']; 1271 } 1272 } 1273 1274 $hmac_secret = 'd8d9616bce7b0a8fc83b422a7001b0492e17b4c16debeb43b77cf0060d9bdae3'; 1275 1276 $sig = hash_hmac('sha1', $clean_ua, $hmac_secret, true); 1277 1278 if (!$sig) { 1279 return ''; 1280 } 1281 1282 $cache = $is_mobile . bin2hex(substr($sig, 0, 4)); 1283 1284 return $cache; 1285 } 1286 1287 /** 1288 * Checks for rangebans when posting 1289 */ 1290 function spam_filter_post_ip($userpwd = null, $thread_id = null, $has_img = false) { 1291 global $captcha_bypass, $rangeban_bypass; 1292 1293 if (CAPTCHA) { 1294 if ($captcha_bypass === true) { 1295 return false; 1296 } 1297 } 1298 else { 1299 if ($rangeban_bypass) { 1300 return false; 1301 } 1302 } 1303 1304 $ip = $_SERVER['REMOTE_ADDR']; 1305 1306 if (isset($_SERVER['HTTP_X_GEO_ASN'])) { 1307 $asn = (int)$_SERVER['HTTP_X_GEO_ASN']; 1308 } 1309 else { 1310 $_asninfo = GeoIP2::get_asn($ip); 1311 1312 if ($_asninfo) { 1313 $asn = (int)$_asninfo['asn']; 1314 } 1315 else { 1316 $asn = 0; 1317 } 1318 } 1319 1320 $long_ip = ip2long($ip); 1321 1322 if (!$long_ip) { 1323 return false; 1324 } 1325 1326 $browser_id = spam_filter_get_browser_id(); 1327 $req_sig = spam_filter_get_req_sig(); 1328 1329 $options = [ 1330 'board' => BOARD_DIR, 1331 'is_sfw' => DEFAULT_BURICHAN, 1332 'userpwd' => $userpwd, 1333 'is_op' => $thread_id == 0, 1334 'has_img' => $has_img, 1335 'browser_id' => $browser_id, 1336 'req_sig' => $req_sig 1337 ]; 1338 1339 if ($range = isIPRangeBanned($long_ip, $asn, $options)) { 1340 if ($range['lenient'] && $userpwd) { 1341 $userpwd->setCookie('.' . L::d(BOARD_DIR)); 1342 } 1343 1344 // Images only 1345 if ($range['img_only']) { 1346 $_err = S_IPRANGE_BLOCKED_IMG; 1347 } 1348 // Threads only 1349 else if ($range['ops_only']) { 1350 $_err = S_IPRANGE_BLOCKED_OP; 1351 } 1352 else { 1353 $_err = S_IPRANGE_BLOCKED; 1354 } 1355 1356 // Temporarily or Permanently 1357 if ($range['expires_on'] || $range['lenient']) { 1358 $_err .= ' ' . S_IPRANGE_BLOCKED_TEMP; 1359 } 1360 else { 1361 $_err .= ' ' . S_IPRANGE_BLOCKED_PERM; 1362 } 1363 1364 // Bypassed by verified or known users 1365 if ($range['lenient'] == 1) { 1366 $_err .= S_IPRANGE_BLOCKED_L1; 1367 } 1368 // Bypassed by known users only 1369 else if ($range['lenient'] == 2) { 1370 $_err .= S_IPRANGE_BLOCKED_L2; 1371 } 1372 // Bypassed by verified users only 1373 else if ($range['lenient'] == 3) { 1374 $_err .= S_IPRANGE_BLOCKED_L3; 1375 } 1376 1377 // 4chan pass mention 1378 $_err .= S_IPRANGE_BLOCKED_PASS; 1379 1380 error($_err); 1381 } 1382 1383 // Auto-rangebans, Mobile only 1384 // Bypassed by verified users or known users for at least 2h 1385 // or users who have made at least one post on the board 15 minutes ago 1386 if ($thread_id !== null && $browser_id[0] === '1') { 1387 $since_ts = 0; 1388 1389 if ($userpwd) { 1390 $user_known = $userpwd->isUserKnownOrVerified(120, 1); 1391 1392 $now = $_SERVER['REQUEST_TIME']; 1393 1394 if ($userpwd->postCount() > 0 && $userpwd->maskTs() <= $now - 900) { 1395 $since_ts = $userpwd->maskTs(); 1396 } 1397 } 1398 else { 1399 $user_known = false; 1400 } 1401 1402 if (!$user_known) { 1403 if (is_ip_auto_rangebanned($ip, BOARD_DIR, $thread_id, $browser_id, $since_ts)) { 1404 write_to_event_log('auto_range_hit', $ip, [ 1405 'board' => BOARD_DIR, 1406 'thread_id' => $thread_id, 1407 'ua_sig' => $browser_id 1408 ]); 1409 1410 if ($userpwd) { 1411 $userpwd->setCookie('.' . L::d(BOARD_DIR)); 1412 } 1413 1414 // Temporary, bypassablee by known or verified users 1415 error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1 . S_IPRANGE_BLOCKED_PASS); 1416 } 1417 } 1418 } 1419 1420 return false; 1421 } 1422 1423 function is_ip_auto_rangebanned($ip, $board, $thread_id, $browser_id, $since_ts = 0) { 1424 $range_sql = explode('.', $ip); 1425 1426 $range_sql = "{$range_sql[0]}.{$range_sql[1]}.%"; 1427 1428 $thread_id = (int)$thread_id; 1429 1430 if ($since_ts > 0) { 1431 $since_sql = ' AND created_on <= FROM_UNIXTIME(' . ((int)$since_ts) . ')'; 1432 } 1433 else { 1434 $since_sql = ''; 1435 } 1436 1437 $sql =<<<SQL 1438 SELECT id FROM event_log WHERE 1439 type = 'rangeban' AND board = '%s' AND thread_id = $thread_id AND ua_sig = '%s' 1440 AND ip LIKE '%s' 1441 AND created_on > DATE_SUB(NOW(), INTERVAL 120 MINUTE)$since_sql 1442 LIMIT 1 1443 SQL; 1444 1445 $res = mysql_global_call($sql, $board, $browser_id, $range_sql); 1446 1447 if (!$res) { 1448 return false; 1449 } 1450 1451 if (mysql_num_rows($res)) { 1452 return true; 1453 } 1454 1455 return false; 1456 } 1457 1458 /** 1459 * Dumps and formats HTTP headers and other request information for logging 1460 */ 1461 function spam_filter_format_http_headers($com = null, $country = null, $filename = null, $threat_score = null, $req_sig = null) { 1462 $bot_headers = ''; 1463 1464 foreach ($_SERVER as $_h_name => $_h_val) { 1465 if (substr($_h_name, 0, 5) == 'HTTP_') { 1466 if ($_h_name === 'HTTP_COOKIE') { 1467 $_cookies = array_keys($_COOKIE); 1468 $_cookies = array_intersect($_cookies, ['ws_style', 'nws_style', '4chan_pass', '_tcs', '_ga', 'cf_clearance' ]); 1469 $_cookie_count = count($_COOKIE); 1470 $bot_headers .= "HTTP_COOKIE: " . htmlspecialchars(implode(', ', $_cookies)) . " ($_cookie_count in total)\n"; 1471 } 1472 else if (strpos($_h_name, 'AUTH') !== false) { 1473 continue; 1474 } 1475 else { 1476 $bot_headers .= "$_h_name: " . htmlspecialchars($_h_val) . "\n"; 1477 } 1478 } 1479 } 1480 1481 $bot_headers .= "_POST: " . htmlspecialchars(implode(', ', array_keys($_POST))) . "\n"; 1482 1483 if ($country !== null) { 1484 $bot_headers .= "_Country: $country\n"; 1485 } 1486 1487 if ($threat_score !== null) { 1488 $bot_headers .= "_Score: " . $threat_score . "\n"; 1489 } 1490 1491 if ($req_sig !== null) { 1492 $bot_headers .= "_Sig: " . $req_sig . "\n"; 1493 } 1494 1495 if (isset($_COOKIE['_tcs'])) { 1496 $bot_headers .= "_TCS: " . htmlspecialchars($_COOKIE['_tcs']) . "\n"; 1497 } 1498 1499 if (isset($_COOKIE['4chan_pass'])) { 1500 $userpwd = UserPwd::getSession(); 1501 1502 if ($userpwd) { 1503 $bot_headers .= "_Pwd: " . htmlspecialchars($userpwd->getPwd()) . "\n"; 1504 } 1505 } 1506 1507 if ($filename !== null) { 1508 $bot_headers .= "_File: " . htmlspecialchars($filename) . "\n"; 1509 } 1510 1511 if ($com !== null) { 1512 $bot_headers .= "_Comment: $com"; 1513 } 1514 1515 return $bot_headers; 1516 } 1517 1518 function spam_filter_get_req_sig() { 1519 static $cache = null; 1520 1521 if ($cache !== null) { 1522 return $cache; 1523 } 1524 1525 $pick_headers = [ 1526 'HTTP_SEC_CH_UA_PLATFORM', 1527 'HTTP_SEC_CH_UA_MOBILE', 1528 'HTTP_SEC_CH_UA_MODEL', 1529 'HTTP_USER_AGENT', 1530 'HTTP_ACCEPT_LANGUAGE', 1531 'HTTP_SEC_FETCH_SITE', 1532 'HTTP_SEC_FETCH_MODE', 1533 'HTTP_SEC_FETCH_DEST', 1534 ]; 1535 1536 $need_headers = [ 1537 'HTTP_USER_AGENT', 1538 'HTTP_ACCEPT', 1539 'HTTP_REFERER', 1540 'HTTP_ACCEPT_LANGUAGE', 1541 ]; 1542 1543 $datapoints = []; 1544 1545 $keys = []; 1546 1547 foreach ($_SERVER as $k => $v) { 1548 if (in_array($k, $pick_headers)) { 1549 $keys[] = $k; 1550 } 1551 } 1552 1553 $keyline = implode('.', $keys); 1554 1555 $pointlines = [ 1556 'fetch_smd' => 'HTTP_SEC_FETCH_SITE.HTTP_SEC_FETCH_MODE.HTTP_SEC_FETCH_DEST', 1557 'fetch_dms' => 'HTTP_SEC_FETCH_DEST.HTTP_SEC_FETCH_MODE.HTTP_SEC_FETCH_SITE', 1558 'fetch_any' => 'HTTP_SEC_FETCH_' 1559 ]; 1560 1561 foreach ($pointlines as $key => $value) { 1562 if (strpos($keyline, $value) !== false) { 1563 $datapoints[] = $key; 1564 } 1565 } 1566 1567 if (isset($_SERVER['HTTP_SEC_GPC']) || isset($_SERVER['HTTP_DNT'])) { 1568 $datapoints[] = 'dnt_gpc'; 1569 } 1570 1571 if (isset($_SERVER['HTTP_X_REQUESTED_WITH'])) { 1572 $datapoints[] = 'xrw'; 1573 } 1574 1575 if (preg_match('/HTTP_SEC_CH_UA_[^.]+\.HTTP_SEC_CH_UA_[^.]+\.HTTP_SEC_CH_UA_/', $keyline)) { 1576 $datapoints[] = 'ch_ua_block'; 1577 } 1578 1579 foreach ($need_headers as $k) { 1580 if (!isset($_SERVER[$k])) { 1581 $datapoints[] = 'missing'; 1582 break; 1583 } 1584 } 1585 1586 $sig = implode('+', $datapoints); 1587 1588 if (!$sig) { 1589 $sig = 'deadbeef'; 1590 } 1591 1592 $cache = substr(md5($sig), 0, 8); 1593 1594 return $cache; 1595 } 1596 1597 // Covers 251044 (79.14 %) unique IPs and 10454 (77.09 %) unique bans 1598 // IPs %: 0.1 | Bans %: 0 | GR1 all: 0 | Any EU: false 1599 function spam_filter_is_asn_whitelisted() { 1600 static $val = null; 1601 1602 static $whitelist = [ 1603 21928, 6167, 7922, 7018, 812, 701, 1221, 20115, 22773, 2856, 3320, 6805, 577, 1604 3209, 852, 20057, 20001, 5089, 4804, 10796, 8151, 5617, 7545, 11427, 209, 16086, 1605 719, 33363, 15557, 5650, 133612, 6327, 6128, 5607, 206067, 1267, 26599, 6830, 1606 8881, 2119, 14593, 28573, 3215, 26615, 3352, 1759, 7303, 11426, 35228, 55836, 1607 1136, 22085, 11351, 8708, 1257, 3269, 5410, 7418, 23693, 30722, 9299, 13285, 17676, 1608 3301, 7713, 5391, 33915, 44034, 8374, 4764, 51207, 29447, 27651, 7552, 12389, 1609 19108, 8359, 11315, 6057, 16591, 12479, 5769, 17072, 31615, 12271, 28403, 11664, 1610 6147, 15704, 10139, 39603, 12874, 25135, 5483, 5378, 4771, 4775, 12912, 6871, 1611 12322, 6614, 132199, 5432, 212238, 12929, 27699, 22927, 8473, 2860, 12430, 7029, 1612 6848, 5645, 8412, 62240, 8447, 15502, 174, 30036, 27747, 14638, 4230, 45727, 9009, 1613 17639, 4788, 10030, 11492, 45143, 18881, 2516, 21334, 15895, 4766, 16232, 6799, 1614 8400, 9443, 6079, 13999, 5610, 45899, 4761, 2586, 12353, 20845, 20365, 13280, 1615 22047, 3243, 4818, 27995, 20055, 24203, 9500, 25255, 45609, 29518, 7992, 39891, 1616 3303, 4773, 855, 8452, 136787, 18403, 3329, 52341, 1617 ]; 1618 1619 if ($val !== null) { 1620 return $val; 1621 } 1622 1623 if (isset($_SERVER['HTTP_X_GEO_ASN'])) { 1624 $asn = (int)$_SERVER['HTTP_X_GEO_ASN']; 1625 } 1626 else { 1627 $asn = 0; 1628 } 1629 1630 if (!$asn) { 1631 return true; 1632 } 1633 1634 $val = in_array($asn, $whitelist); 1635 1636 return $val; 1637 } 1638 1639 function spam_filter_is_bad_actor() { 1640 static $cache = null; 1641 1642 if ($cache !== null) { 1643 return $cache; 1644 } 1645 /* 1646 if (isset($_SERVER['HTTP_X_HTTP_VERSION'])) { 1647 if (strpos($_SERVER['HTTP_X_HTTP_VERSION'], 'HTTP/1') === 0) { 1648 $cache = true; 1649 return true; 1650 } 1651 } 1652 */ 1653 $no_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) === false; 1654 $no_accept = isset($_SERVER['HTTP_ACCEPT']) === false; 1655 1656 if ($no_lang && $no_accept) { 1657 $cache = true; 1658 return true; 1659 } 1660 1661 if ($no_lang && strpos($_SERVER['HTTP_USER_AGENT'], '; wv)') !== false) { 1662 $cache = true; 1663 return true; 1664 } 1665 1666 if ($no_accept && strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false) { 1667 $cache = true; 1668 return true; 1669 } 1670 1671 if ($no_lang && isset($_SERVER['HTTP_REFERER'])) { 1672 $ref = $_SERVER['HTTP_REFERER']; 1673 1674 if (strpos($ref, 'sys.4chan.org') !== false || strpos($ref, '/thread/') !== false) { 1675 $cache = true; 1676 return true; 1677 } 1678 } 1679 1680 $cache = false; 1681 return false; 1682 } 1683 1684 function spam_filter_get_threat_score($country = null, $is_op = false, $multipart = true/*, &$log = []*/) { 1685 $increase = []; 1686 $more = []; 1687 1688 $domain = DEFAULT_BURICHAN ? '4channel' : '4chan'; 1689 1690 if (isset($_SERVER['HTTP_USER_AGENT'])) { 1691 $ua = $_SERVER['HTTP_USER_AGENT']; 1692 } 1693 else { 1694 $ua = ''; 1695 } 1696 1697 if (isset($_SERVER['HTTP_CONTENT_TYPE'])) { 1698 $content_type = $_SERVER['HTTP_CONTENT_TYPE']; 1699 } 1700 else { 1701 $content_type = ''; 1702 } 1703 1704 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 1705 $accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE']; 1706 } 1707 else { 1708 $accept_lang = ''; 1709 } 1710 1711 if (isset($_SERVER['HTTP_ACCEPT'])) { 1712 $accept_header = $_SERVER['HTTP_ACCEPT']; 1713 } 1714 else { 1715 $accept_header = ''; 1716 } 1717 1718 if (isset($_SERVER['HTTP_REFERER'])) { 1719 $referer_header = $_SERVER['HTTP_REFERER']; 1720 } 1721 else { 1722 $referer_header = ''; 1723 } 1724 1725 $header_keys = array_keys($_SERVER); 1726 1727 $ua_is_webkit = false; 1728 1729 $check_for_sec_headers = false; 1730 1731 $is_mobile_ua = preg_match('/Android|Mobile/', $ua) || $_SERVER['HTTP_SEC_CH_UA_MOBILE'] === '?1'; 1732 1733 $is_brave = false; 1734 1735 if (isset($_SERVER['HTTP_SEC_CH_UA']) && strpos($_SERVER['HTTP_SEC_CH_UA'], 'Brave') !== false) { 1736 if (isset($_SERVER['HTTP_SEC_GPC'])) { 1737 $is_brave = true; 1738 } 1739 } 1740 1741 // Mobile app (webviews, etc) 1742 $is_webview = strpos($ua, '; wv') !== false; 1743 $is_mobile_app = !$accept_header && !$accept_lang && ($is_webview || strpos($referer_header, '/thread/') !== false); 1744 1745 if (!$is_mobile_app && !$accept_header && $accept_lang && strpos($referer_header, 'sys.4chan.org') !== false) { 1746 $is_mobile_app = true; 1747 } 1748 1749 if (!$is_mobile_app && strpos($ua, 'Mozilla/') === false && preg_match('/Android|Dalvik|iOS|iPhone/', $ua)) { 1750 $is_mobile_app = true; 1751 } 1752 1753 if (!$is_mobile_app && isset($_SERVER['HTTP_X_REQUESTED_WITH']) && preg_match('/floens|adamantcheese|clover/', $_SERVER['HTTP_X_REQUESTED_WITH'])) { 1754 $is_mobile_app = true; 1755 } 1756 1757 if (!$is_mobile_app && preg_match('/boundary=[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}$/', $content_type)) { 1758 if (strpos($ua, 'Android') !== false) { 1759 $is_mobile_app = true; 1760 } 1761 else if (strpos($ua, 'Firefox/') !== false && !$accept_lang) { 1762 $is_mobile_app = true; 1763 } 1764 } 1765 1766 // No UA 1767 if (!$ua) { 1768 $increase[] = 0.25; 1769 //$log[] = 'NO_UA'; 1770 } 1771 // Firefox 1772 else if ((strpos($ua, 'Firefox/') !== false || strpos($ua, 'FxiOS/') !== false) && strpos($ua, 'WebKit') === false) { 1773 // Suspicious Content-Type 1774 if ($multipart && !$is_mobile_app && !preg_match('/=-+([0-9]+|geckoformboundary[a-f0-9]+)$/i', $content_type)) { 1775 $increase[] = 0.25; 1776 //$log[] = 'BAD_CT_FF'; 1777 } 1778 1779 // Suspicious language 1780 if (!$accept_lang) { 1781 if (!$is_mobile_app) { 1782 $increase[] = 0.02; 1783 $more[] = 0.1; 1784 //$log[] = 'NO_LANG'; 1785 } 1786 } 1787 else if (preg_match('/[a-z]-[a-z]/', $accept_lang)) { 1788 $increase[] = 0.1; 1789 //$log[] = 'LC_LANG'; 1790 } 1791 1792 if (isset($_SERVER['HTTP_PRAGMA']) && !isset($_SERVER['HTTP_CACHE_CONTROL'])) { 1793 $increase[] = 0.35; 1794 $more[] = 0.1; 1795 //$log[] = 'FF_PRAGMA'; 1796 } 1797 1798 // Wrong Accept header 1799 if (strpos($accept_header, 'application/signed-exchange') !== false) { 1800 $increase[] = 0.15; 1801 //$log[] = 'FF_SIGEX'; 1802 } 1803 1804 // Old and spoofed versions 1805 if ($accept_header && preg_match('/(?:Firefox)\/([0-9]+)[^0-9]/', $ua, $m) && strpos($ua, 'PaleMoon') === false) { 1806 $v = (int)$m[1]; 1807 1808 if ($v < 52) { 1809 $increase[] = 0.2; 1810 //$log[] = 'OLD_FF'; 1811 } 1812 else if ($v < 60) { 1813 $increase[] = 0.1; 1814 //$log[] = 'OLD_FF'; 1815 } 1816 else if ($v < 78) { 1817 $increase[] = 0.01; 1818 //$log[] = 'OLD_FF'; 1819 } 1820 else if ($v > 500) { 1821 $increase[] = 0.5; 1822 //$log[] = 'FUTURE_FF'; 1823 } 1824 1825 if ($v > 110) { 1826 $check_for_sec_headers = true; 1827 } 1828 } 1829 } 1830 // Webkit 1831 else if (strpos($ua, 'WebKit') !== false) { 1832 $ua_is_webkit = true; 1833 $ua_is_chrome = strpos($ua, 'Chrome') !== false; 1834 1835 // Suspicious Content-Type 1836 if ($multipart && !$is_mobile_app) { 1837 if (!strpos($content_type, 'WebKit')) { 1838 $increase[] = 0.25; 1839 //$log[] = 'BAD_CT_WK'; 1840 } 1841 else if (strpos($content_type, '-') === false) { 1842 $increase[] = 0.50; 1843 //$log[] = 'BAD_CT_DASH'; 1844 } 1845 } 1846 1847 // Suspicious language 1848 if (!$accept_lang) { 1849 if (!$is_mobile_app) { 1850 $increase[] = 0.02; 1851 $more[] = 0.1; 1852 //$log[] = 'NO_LANG'; 1853 } 1854 } 1855 else if ($ua_is_chrome && strpos($ua, 'Android') === false && preg_match('/[a-z]-[a-z]/', $accept_lang)) { 1856 $increase[] = 0.1; 1857 //$log[] = 'LC_LANG'; 1858 } 1859 1860 // Old and spoofed versions 1861 if (preg_match('/(?:Chrome)\/([0-9]+)[^0-9]/', $ua, $m)) { 1862 $v = (int)$m[1]; 1863 1864 if ($v < 60) { 1865 $increase[] = 0.2; 1866 //$log[] = 'OLD_WK'; 1867 } 1868 else if ($v < 70) { 1869 $increase[] = 0.1; 1870 //$log[] = 'OLD_WK'; 1871 } 1872 else if ($v < 80) { 1873 $increase[] = 0.05; 1874 //$log[] = 'OLD_WK'; 1875 } 1876 else if ($v > 500) { 1877 $increase[] = 0.5; 1878 //$log[] = 'FUTURE_WK'; 1879 } 1880 1881 if ($v > 110) { 1882 $check_for_sec_headers = true; 1883 } 1884 } 1885 1886 if (preg_match('/(?:Safari)\/([0-9]+)/', $ua, $m)) { 1887 $v = (int)$m[1]; 1888 1889 if ($v < 530) { 1890 $increase[] = 0.5; 1891 //$log[] = 'OLD_SAFARI'; 1892 } 1893 } 1894 1895 // iPhone UA too short 1896 if (strpos($ua, 'iPhone') !== false && strpos($ua, 'Mobile') === false) { 1897 $increase[] = 0.06; 1898 //$log[] = 'SHORT_IPHONE'; 1899 } 1900 } 1901 // Other 1902 else { 1903 if (!$is_mobile_app && $multipart && preg_match('/boundary=[a-zA-Z0-9]+$/', $content_type)) { 1904 $increase[] = 0.5; 1905 //$log[] = 'STRANGE_CT'; 1906 } 1907 1908 if (preg_match('/Netscape\/|Opera\b|Camino\/|Trident\/|Presto\/|compatible; MSIE /', $ua)) { 1909 $increase[] = 0.75; 1910 //$log[] = 'OLD_UA'; 1911 } 1912 else if (!$is_mobile_app && strpos($ua, 'Mozilla/') === false) { 1913 $increase[] = 0.15; 1914 $more[] = 0.1; 1915 //$log[] = 'STRANGE_UA'; 1916 } 1917 1918 if (!$is_mobile_app && !$accept_lang) { 1919 $more[] = 0.25; 1920 //$log[] = 'NO_LANG'; 1921 } 1922 1923 // UA too short 1924 if (!$is_mobile_app && (strlen($ua) < 25 || strpos($ua, ' ') === false)) { 1925 $increase[] = 0.25; 1926 //$log[] = 'UA_SPOOF'; 1927 } 1928 } 1929 1930 // Suspicious Content-Type 1931 if ($multipart) { 1932 if (strpos($content_type, 'WebKit') && !$ua_is_webkit) { 1933 $increase[] = 0.25; 1934 //$log[] = 'BAD_UA_CT_WK'; 1935 } 1936 } 1937 1938 // Sec-Fetch headers should be together 1939 // Some iPhones have those separated 1940 if (!$is_brave && !$is_webview && strpos($ua, 'Chrome') !== false) { 1941 $_sf_start = false; 1942 $_sf_end = false; 1943 1944 foreach ($_SERVER as $_hdr => $_value) { 1945 if (strpos($_hdr, 'HTTP_SEC_FETCH_') === 0) { 1946 if ($_sf_start && $_sf_end) { 1947 $increase[] = 0.25; 1948 //$log[] = 'SPARSE_SEC_FETCH'; 1949 break; 1950 } 1951 1952 $_sf_start = true; 1953 } 1954 else if ($_sf_start) { 1955 $_sf_end = true; 1956 } 1957 } 1958 } 1959 1960 // HTTP_SEC_FETCH_USER should always be ?1 1961 if (isset($_SERVER['HTTP_SEC_FETCH_USER']) && $_SERVER['HTTP_SEC_FETCH_USER'] !== '?1') { 1962 $increase[] = 0.15; 1963 //$log[] = 'BAD_SEC_FU'; 1964 } 1965 1966 // Unusual Accept header 1967 if ($accept_header) { 1968 if (strpos($accept_header, 'text/plain') !== false) { 1969 $increase[] = 0.05; 1970 //$log[] = 'ACCEPT_TP'; 1971 } 1972 } 1973 1974 // Referer is set but is empty 1975 if (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], '4chan.org') === false) { 1976 $increase[] = 0.1; 1977 //$log[] = 'BAD_REFERER'; 1978 } 1979 1980 // Platform mismatch: client hints vs user agent 1981 if (isset($_SERVER['HTTP_SEC_CH_UA_PLATFORM']) && $ua) { 1982 $_ch_platform = $_SERVER['HTTP_SEC_CH_UA_PLATFORM']; 1983 1984 if (strpos($ua, 'Windows') !== false) { 1985 if (strpos($_ch_platform, 'Windows') === false) { 1986 $increase[] = 0.5; 1987 //$log[] = 'CH_BAD_PLATFORM'; 1988 } 1989 } 1990 else if (strpos($ua, 'Mac OS') !== false) { 1991 if (strpos($_ch_platform, 'macOS') === false) { 1992 $increase[] = 0.5; 1993 //$log[] = 'CH_BAD_PLATFORM'; 1994 } 1995 } 1996 else if (strpos($ua, 'Linux') !== false) { 1997 if (preg_match('/Linux|Android|BSD/', $_ch_platform) === false) { 1998 $increase[] = 0.5; 1999 //$log[] = 'CH_BAD_PLATFORM'; 2000 } 2001 } 2002 } 2003 2004 if (isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && $accept_header === '*/*') { 2005 $increase[] = 0.2; 2006 $more[] = 0.1; 2007 //$log[] = 'ACCEPT_UIR'; 2008 } 2009 2010 if (!isset($_SERVER['HTTP_PRAGMA']) && strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') === false) { 2011 $increase[] = 0.09; 2012 //$log[] = 'BAD_IPHONE_WV'; 2013 } 2014 2015 // Suspicious OS 2016 if (strpos($ua, 'Windows NT ') !== false) { 2017 if (preg_match('/Windows NT ([0-9]+)/', $ua, $m) && strpos($ua, 'Mypal/') === false) { 2018 $v = (int)$m[1]; 2019 2020 if ($v < 6) { 2021 if (strpos($ua, 'Goanna') === false) { 2022 $increase[] = 0.25; 2023 } 2024 else { 2025 $increase[] = 0.03; 2026 } 2027 //$log[] = 'OLD_WIN'; 2028 } 2029 else if ($v < 10) { 2030 $increase[] = 0.03; 2031 //$log[] = 'OLD_WIN'; 2032 } 2033 else if ($v > 10) { 2034 $increase[] = 0.5; 2035 //$log[] = 'FUTURE_WIN'; 2036 } 2037 } 2038 } 2039 else if (strpos($ua, 'Mac OS X ') !== false) { 2040 if (preg_match('/Mac OS X ([0-9]+)_([0-9]+)/', $ua, $m)) { 2041 $v_maj = (int)$m[1]; 2042 $v_min = (int)$m[2]; 2043 2044 if ($v_maj < 10) { 2045 $increase[] = 0.5; 2046 //$log[] = 'OLD_OSX'; 2047 } 2048 else if ($v_maj == 10) { 2049 if ($v_min < 7) { 2050 $increase[] = 0.25; 2051 //$log[] = 'OLD_OSX'; 2052 } 2053 else if ($v_min < 12) { 2054 $increase[] = 0.05; 2055 //$log[] = 'OLD_OSX'; 2056 } 2057 } 2058 else if ($v_maj > 10 && strpos($ua, 'Safari') !== false) { 2059 $increase[] = 0.30; 2060 //$log[] = 'FUTURE_OSX'; 2061 } 2062 } 2063 } 2064 else if (strpos($ua, 'Android') !== false) { 2065 if (preg_match('/Android ([0-9]+)/', $ua, $m)) { 2066 $v = (int)$m[1]; 2067 2068 if ($v < 4) { 2069 $increase[] = 0.25; 2070 //$log[] = 'OLD_DROID'; 2071 } 2072 else if ($v < 8) { 2073 $increase[] = 0.05; 2074 //$log[] = 'OLD_DROID'; 2075 } 2076 else if ($v > 20) { 2077 $increase[] = 0.5; 2078 //$log[] = 'FUTURE_DROID'; 2079 } 2080 } 2081 2082 if (strpos($ua, 'Win64;') !== false) { 2083 $increase[] = 0.25; 2084 //$log[] = 'OS_SOUP'; 2085 } 2086 } 2087 2088 // Spoofed OS 2089 if (preg_match('/Mozilla|Firefox|Chrome/', $ua) && !preg_match('/Windows NT|Android|Linux|Mac|iOS|X11;|BSD|Nintendo|PlayStation|Steam/', $ua)) { 2090 $increase[] = 0.20; 2091 //$log[] = 'NO_OS'; 2092 } 2093 2094 // Non-browser user agents 2095 if (preg_match('/headless|node-fetch|python-|java\/|jakarta|-perl|http-?client|-resty-|awesomium\//i', $ua)) { 2096 $increase[] = 1.0; 2097 //$log[] = 'NOT_BROWSER'; 2098 } 2099 2100 // Wrong content type 2101 if ($multipart) { 2102 // Posting 2103 if ($_SERVER['HTTP_CONTENT_TYPE'] === 'application/x-www-form-urlencoded') { 2104 $increase[] = 0.75; 2105 //$log[] = 'BAD_CT_MP'; 2106 } 2107 } 2108 else if (!$is_mobile_app) { 2109 // Reporting 2110 if ($_SERVER['HTTP_CONTENT_TYPE'] !== 'application/x-www-form-urlencoded') { 2111 $increase[] = 0.75; 2112 //$log[] = 'BAD_CT_NMP'; 2113 } 2114 } 2115 2116 // Unusual headers 2117 if (isset($_SERVER['HTTP_VARY'])) { 2118 $increase[] = 0.2; 2119 //$log[] = 'VARY_HDR'; 2120 } 2121 2122 if (isset($_SERVER['HTTP_PATH']) || isset($_SERVER['HTTP_SAME_ORIGIN']) || isset($_SERVER['HTTP_REFERRER_POLICY'])) { 2123 $increase[] = 0.8; 2124 //$log[] = 'USELESS_HDR'; 2125 } 2126 2127 if (!$is_mobile_app && !$is_webview) { 2128 if (isset($_SERVER['HTTP_SEC_FETCH_MODE']) && $_SERVER['HTTP_SEC_FETCH_MODE'] === 'navigate') { 2129 // Only threads should be posted using the default form 2130 if (!$is_op && !$is_mobile_ua) { 2131 $increase[] = 0.02; 2132 $more[] = 0.10; 2133 //$log[] = 'BAD_OP_SFM'; 2134 } 2135 2136 // Model hints are never sent when using the default form 2137 if (isset($_SERVER['HTTP_SEC_CH_UA_MODEL']) && !isset($_SERVER['HTTP_SEC_CH_UA_BITNESS'])) { 2138 $increase[] = 0.1; 2139 $more[] = 0.20; 2140 //$log[] = 'MODEL_NAV'; 2141 } 2142 } 2143 } 2144 2145 // iPhone fetch site none 2146 if (strpos($ua, 'like Mac OS') && isset($_SERVER['HTTP_SEC_FETCH_SITE']) && $_SERVER['HTTP_SEC_FETCH_SITE'] === 'none') { 2147 $increase[] = 0.08; 2148 $more[] = 0.10; 2149 //$log[] = 'IOS_FSN'; 2150 } 2151 2152 // No cookies 2153 if (!$is_mobile_app) { 2154 if (!isset($_SERVER['HTTP_COOKIE']) || !$_SERVER['HTTP_COOKIE']) { 2155 $increase[] = 0.05; 2156 $more[] = 0.25; 2157 //$log[] = 'NO_COOKIE'; 2158 } 2159 else if (count($_COOKIE) === 1 && isset($_COOKIE['cf_clearance'])) { 2160 $increase[] = 0.05; 2161 $more[] = 0.25; 2162 //$log[] = 'NO_COOKIE'; 2163 } 2164 } 2165 2166 // Timezones and Time 2167 if (isset($_COOKIE['_tcs'])) { 2168 list($_time, $_tz, $_time_s, $_tcs_v) = explode('.', $_COOKIE['_tcs']); 2169 2170 if (!$_tcs_v) { 2171 $increase[] = 0.09; 2172 //$log[] = 'BAD_TCS'; 2173 } 2174 else { 2175 if (!$is_webview && strpos($ua, 'Chrome/') !== false && $_tcs_v != 33) { 2176 $increase[] = 0.09; 2177 //$log[] = 'BAD_TCS_CR'; 2178 } 2179 } 2180 2181 if ($_time_s && isset($_POST['t-challenge']) && $_POST['t-challenge'] !== 'noop' && $_POST['t-response']) { 2182 $_d = $_SERVER['REQUEST_TIME'] - $_time_s; 2183 2184 if ($_d > 0 && $_d < 2) { 2185 $increase[] = 1.0; 2186 //$log[] = 'FAST_TCS'; 2187 } 2188 } 2189 2190 if (isset($_SERVER['HTTP_X_TIMEZONE'])) { 2191 $_tz0 = explode('/', $_tz, 2)[0]; 2192 if ($_tz0) { 2193 if ($_tz0 === 'UTC') { 2194 $increase[] = 0.02; 2195 $more[] = 0.02; 2196 //$log[] = 'UTC_TZ'; 2197 } 2198 else if ($_tz0 === 'Etc') { 2199 $increase[] = 0.01; 2200 $more[] = 0.01; 2201 //$log[] = 'ETC_TZ'; 2202 } 2203 else if (strpos($_SERVER['HTTP_X_TIMEZONE'], $_tz0) !== 0) { 2204 if (strpos($_tz0, 'Atlantic') === false || strpos($_SERVER['HTTP_X_TIMEZONE'], 'Europe') === false) { 2205 $increase[] = 0.03; 2206 $more[] = 0.03; 2207 //$log[] = 'BAD_TZ'; 2208 } 2209 } 2210 } 2211 } 2212 } 2213 2214 // No Accept 2215 if (!$accept_header && !$is_mobile_app) { 2216 $increase[] = 0.15; 2217 //$log[] = 'NO_ACCEPT'; 2218 } 2219 2220 // No SEC 2221 if ($check_for_sec_headers && !$is_mobile_app) { 2222 if (!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || !isset($_SERVER['HTTP_SEC_FETCH_MODE']) || !isset($_SERVER['HTTP_SEC_FETCH_SITE'])) { 2223 $increase[] = 0.15; 2224 //$log[] = 'NO_SEC'; 2225 } 2226 } 2227 2228 // HTTP 1.1 2229 if (isset($_SERVER['HTTP_X_HTTP_VERSION']) && $_SERVER['HTTP_X_HTTP_VERSION'] === 'HTTP/1.1') { 2230 $more[] = 0.1; 2231 //$log[] = 'HTTP1'; 2232 } 2233 2234 // Spoofed device model 2235 if (isset($_SERVER['HTTP_SEC_CH_UA_MODEL']) && isset($_SERVER['HTTP_SEC_CH_UA_PLATFORM'])) { 2236 $_k1 = (int)array_search('HTTP_SEC_CH_UA_MODEL', $header_keys); 2237 $_k2 = (int)array_search('HTTP_SEC_CH_UA_PLATFORM', $header_keys); 2238 2239 if (abs($_k1 - $_k2) > 5) { 2240 $increase[] = 0.05; 2241 $more[] = 0.01; 2242 //$log[] = 'FAR_MODEL'; 2243 } 2244 } 2245 2246 if ($country) { 2247 // Language is set but doesn't match the country 2248 if ($accept_lang && strpos($accept_lang, 'en') !== 0) { 2249 $lang_regex = get_lang_regex_from_country($country); 2250 2251 if ($lang_regex && !preg_match($lang_regex, $accept_lang)) { 2252 $more[] = 0.025; 2253 $increase[] = 0.025; 2254 //$log[] = 'LANG_MISMATCH'; 2255 } 2256 2257 // No quality in lang 2258 if (!preg_match('/iPhone|iPad/', $ua)) { 2259 if ($accept_lang && strpos($accept_lang, ';') === false) { 2260 $more[] = 0.025; 2261 $increase[] = 0.025; 2262 //$log[] = 'LANG_NOQ'; 2263 } 2264 } 2265 } 2266 2267 // Highly suspicious countries 2268 $countries_0 = array( 2269 'AD','AE','AF','AG','AI','AL','AM','AN','AO','AQ','AS','AW','AX','AZ', 2270 'BB','BD','BF','BH','BI','BJ','BL','BM','BN','BO','BQ','BS','BT','BV','BW','BZ', 2271 'CC','CD','CF','CG','CI','CK','CM','CN','CR','CU','CV','CW','CX', 2272 'DJ','DM','DO','DZ', 2273 'EC','EG','EH','ER','ET', 2274 'FJ','FK','FM','FO', 2275 'GA','GD','GF','GG','GH','GI','GM','GN','GP','GQ','GS','GT','GU','GW','GY', 2276 'HK','HM','HN','HT', 2277 'IM','IO','IQ','IR','JE','JM','JO','KE','KG','KH','KI','KM','KN','KP','KW','KY','KZ', 2278 'LA','LB','LC','LK','LR','LS','LY', 2279 'MA','MD','MF','MG','MH','ML','MM','MN','MO','MP','MQ','MR','MS','MU','MV','MW','MZ', 2280 'NA','NC','NE','NF','NG','NI','NP','NR','NU', 2281 'OM','PA','PF','PG','PK','PM','PN','PS','PW','PY','QA','RE','RW', 2282 'SA','SB','SC','SD','SH','SJ','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ', 2283 'TC','TD','TF','TG','TJ','TK','TM','TN','TO','TP','TR','TT','TV','TZ', 2284 'UG','UM','UZ','VA','VC','VG','VI','VU','WF','WS','YE','YT','YU','ZM','ZW','XX' 2285 ); 2286 2287 // Less suspicious countries 2288 $countries_1 = array( 2289 'BR','VE','AR','CL','UY','CO','PE','MX', 2290 'UA','BA','RU','MC','MK','CY','MT','ME','KR','JP','TH','VN','ID' 2291 ); 2292 2293 if (in_array($country, $countries_0)) { 2294 $more[] = 0.30; 2295 //$log[] = 'SUSP_COUNTRY_0'; 2296 } 2297 else if (in_array($country, $countries_1)) { 2298 $more[] = 0.10; 2299 //$log[] = 'SUSP_COUNTRY_1'; 2300 } 2301 } 2302 2303 $score = 0.0; 2304 2305 if (!empty($increase)) { 2306 $score += array_sum($increase); 2307 } 2308 2309 if ($score > 0.0 && !empty($more)) { 2310 foreach ($more as $r) { 2311 $score *= (1.0 + $r); 2312 } 2313 } 2314 2315 return round($score, 2); 2316 } 2317 2318 /** 2319 * Necrobumping prevention checks 2320 */ 2321 function spam_filter_can_bump_thread($thread_root) { 2322 $userpwd = UserPwd::getSession(); 2323 2324 if (!$userpwd || !$thread_root) { 2325 return true; 2326 } 2327 2328 if ($userpwd->maskLifetime() >= 21600) { // 6 hours 2329 return true; 2330 } 2331 2332 $thres = (int)(PAGE_MAX * DEF_PAGES * 0.85); 2333 2334 if ($thres <= 0) { 2335 return true; 2336 } 2337 2338 $sql = "SELECT COUNT(*) FROM `%s` WHERE resto = 0 AND archived = 0 AND root > '%s'"; 2339 2340 $res = mysql_board_call($sql, BOARD_DIR, $thread_root); 2341 2342 if (!$res) { 2343 return true; 2344 } 2345 2346 $pos = (int)mysql_fetch_row($res)[0]; 2347 2348 if ($pos < $thres) { 2349 return true; 2350 } 2351 2352 // Check the IP if cookies are blocked 2353 if (spam_filter_is_ip_known(ip2long($_SERVER['REMOTE_ADDR']), BOARD_DIR, 0, 720)) { 2354 return true; 2355 } 2356 2357 return false; 2358 } 2359 2360 /** 2361 * Returns true if the uploaded file had multiple previous bans for it 2362 * and should be blocked 2363 */ 2364 function check_for_banned_upload($md5) { 2365 if (!$md5 || BOARD_DIR === 'b') { 2366 return false; 2367 } 2368 2369 // 6 : Global 5 - NWS on Worksafe Board 2370 // 226 : Global 3 - Loli/shota pornography 2371 $template_clause = '226'; 2372 2373 if (DEFAULT_BURICHAN) { 2374 $template_clause .= ',6'; 2375 } 2376 2377 $sql = <<<SQL 2378 SELECT COUNT(*) as cnt FROM banned_users WHERE md5 = '%s' 2379 AND template_id IN($template_clause) LIMIT 1 2380 SQL; 2381 2382 $res = mysql_global_call($sql, $md5); 2383 2384 if (!$res) { 2385 return false; 2386 } 2387 2388 $count = (int)mysql_fetch_row($res)[0]; 2389 2390 if ($count >= 3) { 2391 return true; 2392 } 2393 2394 return false; 2395 } 2396 2397 function spam_filter_is_likely_automated($memcached = null, $threshold = 29) { 2398 if (!isset($_SERVER['HTTP_X_BOT_SCORE'])) { 2399 return false; 2400 } 2401 2402 $ua = $_SERVER['HTTP_USER_AGENT']; 2403 2404 // Skip Android Webviews 2405 if (strpos($ua, '; wv)') !== false) { 2406 return false; 2407 } 2408 2409 // Skip iPhone Webviews 2410 if (preg_match('/iPhone|iPad/', $ua) && !preg_match('/Mobile|Safari/', $ua)) { 2411 return false; 2412 } 2413 2414 $score = (int)$_SERVER['HTTP_X_BOT_SCORE']; 2415 2416 if ($score > 0 && $score <= $threshold) { 2417 return true; 2418 } 2419 2420 if ($memcached) { 2421 $key = 'bmbot' . $_SERVER['REMOTE_ADDR']; 2422 2423 if ($memcached->get($key)) { 2424 return true; 2425 } 2426 } 2427 2428 return false; 2429 } 2430 2431 function spam_filter_is_pwd_blocked($pwd, $type, $hours = 24) { 2432 if (!$pwd || !$type || $hours <= 0 || $hours > 720) { 2433 return false; 2434 } 2435 2436 $hours = (int)$hours; 2437 2438 $sql =<<<SQL 2439 SELECT 1 FROM event_log 2440 WHERE `type` = '%s' AND pwd = '%s' 2441 AND created_on > DATE_SUB(NOW(), INTERVAL $hours HOUR) 2442 LIMIT 1 2443 SQL; 2444 2445 $res = mysql_global_call($sql, $type, $pwd); 2446 2447 if (!$res) { 2448 return false; 2449 } 2450 2451 return mysql_num_rows($res) === 1; 2452 } 2453 2454 function spam_filter_has_country_changed($pwd) { 2455 if (!$pwd) { 2456 return false; 2457 } 2458 2459 $sql =<<<SQL 2460 SELECT 1 FROM event_log 2461 WHERE `type` = 'country_changed' AND pwd = '%s' 2462 AND created_on > DATE_SUB(NOW(), INTERVAL 24 HOUR) 2463 LIMIT 1 2464 SQL; 2465 2466 $res = mysql_global_call($sql, $pwd); 2467 2468 if (!$res) { 2469 return false; 2470 } 2471 2472 return mysql_num_rows($res) === 1; 2473 } 2474 2475 // Logs posts made by new users. 2476 // Returns 1 if the posting rate is above limits. 2477 function spam_filter_is_post_flood($ip, $userpwd, $board, $thread_id, $phash) { 2478 $user_is_known = $userpwd && $userpwd->isUserKnownOrVerified(60); 2479 2480 if ($user_is_known) { 2481 return 0; 2482 } 2483 2484 $thread_id = (int)$thread_id; 2485 2486 // Per thread reply flood 2487 if ($thread_id) { 2488 $interval_minutes = (int)ANTIFLOOD_INTERVAL_REPLY; 2489 $threshold = (int)ANTIFLOOD_THRES_REPLY; 2490 } 2491 // OP flood 2492 else { 2493 $interval_minutes = (int)ANTIFLOOD_INTERVAL_OP; 2494 $threshold = (int)ANTIFLOOD_THRES_OP; 2495 } 2496 2497 if (!$interval_minutes || !$threshold) { 2498 return 0; 2499 } 2500 2501 $long_ip = ip2long($ip); 2502 2503 if (!$long_ip) { 2504 return 0; 2505 } 2506 2507 $tbl = 'flood_log'; 2508 2509 // Prune old entries 2510 $sql = "DELETE FROM `$tbl` WHERE created_on < DATE_SUB(NOW(), INTERVAL 24 HOUR)"; 2511 mysql_global_call($sql); 2512 2513 // Count flood entries 2514 $ret_val = 0; 2515 2516 $sql = <<<SQL 2517 SELECT COUNT(*) FROM `$tbl` 2518 WHERE ip != '%s' 2519 AND created_on >= DATE_SUB(NOW(), INTERVAL $interval_minutes MINUTE) 2520 AND board = '%s' 2521 AND thread_id = $thread_id 2522 SQL; 2523 2524 $res = mysql_global_call($sql, $ip, $board); 2525 2526 if ($res) { 2527 $count = (int)mysql_fetch_row($res)[0]; 2528 2529 if ($count >= $threshold) { 2530 $ret_val = 1; 2531 } 2532 } 2533 2534 // Insert new entry 2535 $ua_sig = spam_filter_get_browser_id(); 2536 $req_sig = spam_filter_get_req_sig(); 2537 2538 $sql = <<<SQL 2539 INSERT INTO `$tbl` (board, thread_id, ip, phash, ua_sig, req_sig) 2540 VALUES ('%s', $thread_id, '%s', '%s', '%s', '%s') 2541 SQL; 2542 2543 $res = mysql_global_call($sql, $board, $ip, $phash, $ua_sig, $req_sig); 2544 2545 return $ret_val; 2546 } 2547 2548 function spam_filter_post_trip($name, $trip, $dest) { 2549 $normalized_name = normalize_text($name); 2550 if(stripos($normalized_name, 'moot') !== FALSE && ($trip == 'Ep8pui8Vw2' || stripos($normalized_name, 'ep8pui8vw2') !== FALSE) && !valid()) 2551 { 2552 error(S_FAILEDUPLOAD, $dest); 2553 } 2554 2555 if(preg_match("/[l|i|1][o|0][l|i|1]{2}c[o|0][n|m]/i", $trip)) 2556 { 2557 error(S_BANNEDTEXT, $dest); 2558 } 2559 } 2560 2561 // check a comment for fileshare content and put it in the rapidsearch queue 2562 function rapidsearch_check($board, $no, $body) { 2563 if( strlen( $body ) < 25 ) return; 2564 2565 $body = strtolower($body); 2566 $body = strip_tags($body); 2567 2568 $matches = array( 2569 'rapidshare.de/', 2570 'rapidshare.com/', 2571 'rapidshit.com/files/', 2572 'savefile.com/files', 2573 'megaupload.com/', 2574 'sharebigfile.com/download.php', 2575 'sendspace.com/file', 2576 'turboupload.com/d', 2577 'bigupload.com', 2578 'filefactory.com', 2579 'bandongo', 2580 'easy-sharing', 2581 'easy-share', 2582 'sexuploader', 2583 'rapidsharing.com', 2584 'uploadgalaxy', 2585 'up-file.com', 2586 'yousendit', 2587 'gigeshare', 2588 'gigasize', 2589 'depositfiles', 2590 'megarotic.com', 2591 'filewind.com', 2592 'mediafire.com', 2593 'xtremeuploader.com', 2594 'fileden.com', 2595 'zenixstudios.com', 2596 'megashare.com', 2597 'zshare.net', 2598 'megashare.com', 2599 'massmirror' 2600 ); 2601 2602 // oh my god the code before this 2603 foreach( $matches as $match ) { 2604 if( strpos( $body, $match ) !== false ) { 2605 rapidsearch_insert($board, $no, $body); 2606 return; 2607 } 2608 } 2609 } 2610 2611 function trans_similar_to_ascii(&$str, $char) { 2612 // UTF-16 2613 $ord = mb_ord($char); 2614 2615 switch ($ord) 2616 { 2617 case 0x2070: 2618 case 0x2080: 2619 case 0xFF10: 2620 $str .= chr(0x30); /* 0 */ 2621 break; 2622 case 0x00B9: 2623 case 0x2081: 2624 case 0xFF11: 2625 $str .= chr(0x31); /* 1 */ 2626 break; 2627 case 0x00B2: 2628 case 0x2082: 2629 case 0xFF12: 2630 $str .= chr(0x32); 2631 break; 2632 case 0x00B3: 2633 case 0x2083: 2634 case 0xFF13: 2635 $str .= chr(0x33); 2636 break; 2637 case 0x2074: 2638 case 0x2084: 2639 case 0xFF14: 2640 $str .= chr(0x34); 2641 break; 2642 case 0x2075: 2643 case 0x2085: 2644 case 0xFF15: 2645 $str .= chr(0x35); 2646 break; 2647 case 0x2076: 2648 case 0x2086: 2649 case 0xFF16: 2650 $str .= chr(0x36); 2651 break; 2652 case 0x2077: 2653 case 0x2087: 2654 case 0xFF17: 2655 $str .= chr(0x37); 2656 break; 2657 case 0x2078: 2658 case 0x2088: 2659 case 0xFF18: 2660 $str .= chr(0x38); 2661 break; 2662 case 0x2079: 2663 case 0x2089: 2664 case 0xFF19: 2665 $str .= chr(0x39); 2666 break; 2667 case 0xFE57: 2668 case 0xFF01: /* Exclamation mark */ 2669 $str .= chr(0x21); 2670 break; 2671 case 0xFF02: /* Quotation mark */ 2672 $str .= chr(0x22); 2673 break; 2674 case 0xFE5F: 2675 case 0xFF03: /* Number sign */ 2676 $str .= chr(0x23); 2677 break; 2678 case 0xFE69: 2679 case 0xFF04: /* Dollar sign */ 2680 $str .= chr(0x24); 2681 break; 2682 case 0xFE6A: 2683 case 0xFF05: /* Percent sign */ 2684 $str .= chr(0x25); 2685 break; 2686 case 0xFE60: 2687 case 0xFF06: /* Ampersand */ 2688 $str .= chr(0x26); 2689 break; 2690 case 0xFF07: /* Apostrophe */ 2691 $str .= chr(0x27); 2692 break; 2693 case 0x207D: 2694 case 0x208D: 2695 case 0xFE59: 2696 case 0xFF08: /* Left parenthesis */ 2697 $str .= chr(0x28); 2698 break; 2699 case 0x207E: 2700 case 0x208E: 2701 case 0xFE5A: 2702 case 0xFF09: /* Right parenthesis */ 2703 $str .= chr(0x29); 2704 break; 2705 case 0xFE61: 2706 case 0xFF0A: /* Asterisk */ 2707 $str .= chr(0x2A); 2708 break; 2709 case 0x207A: 2710 case 0x208A: 2711 case 0xFE62: 2712 case 0xFF0B: /* Plus sign */ 2713 $str .= chr(0x2B); 2714 break; 2715 case 0xFE50: 2716 case 0xFF0C: /* Comma */ 2717 $str .= chr(0x2C); 2718 break; 2719 case 0x207B: 2720 case 0x208B: 2721 case 0xFE63: 2722 case 0xFF0D: /* Hyphen */ 2723 $str .= chr(0x2D); 2724 break; 2725 case 0xFE52: 2726 case 0xFF0E: /* Full stop */ 2727 $str .= chr(0x2E); 2728 break; 2729 case 0xFF0F: /* Solidus */ 2730 $str .= chr(0x2F); 2731 break; 2732 case 0xFE55: 2733 case 0xFF1A: /* Colon */ 2734 $str .= chr(0x3A); 2735 break; 2736 case 0xFE54: 2737 case 0xFF1B: /* Semicolon */ 2738 $str .= chr(0x3B); 2739 break; 2740 case 0xFE64: 2741 case 0xFF1C: /* Less than sign */ 2742 $str .= chr(0x3C); 2743 break; 2744 case 0x207C: 2745 case 0x208C: 2746 case 0xFE66: 2747 case 0xFF1D: 2748 $str .= chr(0x3D); /* Equals sign */ 2749 break; 2750 case 0xFE65: 2751 case 0xFF1E: /* Greater than sign */ 2752 $str .= chr(0x3E); 2753 break; 2754 case 0xFE56: 2755 case 0xFF1F: /* Question mark */ 2756 $str .= chr(0x3F); 2757 break; 2758 case 0xFE6B: 2759 case 0xFF20: /* At sign */ 2760 $str .= chr(0x40); 2761 break; 2762 case 0x0410: 2763 case 0xFF21: 2764 case 0x1D400: 2765 case 0x1D434: 2766 case 0x1D468: 2767 case 0x1D49C: 2768 case 0x1D4D0: 2769 case 0x1D504: 2770 case 0x1D538: 2771 case 0x1D56C: 2772 case 0x1D5A0: 2773 case 0x1D5D4: 2774 case 0x1D63C: 2775 case 0x1D670: 2776 $str .= chr(0x41); /* A */ 2777 break; 2778 case 0x0042: /* B */ 2779 case 0x0412: 2780 case 0x212C: 2781 case 0xFF22: 2782 case 0x1D401: 2783 case 0x1D435: 2784 case 0x1D469: 2785 case 0x1D4D1: 2786 case 0x1D505: 2787 case 0x1D539: 2788 case 0x1D56D: 2789 case 0x1D5A1: 2790 case 0x1D5D5: 2791 case 0x1D609: 2792 case 0x1D63D: 2793 case 0x1D671: 2794 $str .= chr(0x42); 2795 break; 2796 case 0x0421: 2797 case 0x2102: 2798 case 0x212D: 2799 case 0xFF23: 2800 case 0x1D402: 2801 case 0x1D436: 2802 case 0x1D46A: 2803 case 0x1D49E: 2804 case 0x1D4D2: 2805 case 0x1D56E: 2806 case 0x1D5A2: 2807 case 0x1D5D6: 2808 case 0x1D60A: 2809 case 0x1D63E: 2810 case 0x1D672: 2811 $str .= chr(0x43); /* C */ 2812 break; 2813 case 0x2145: 2814 case 0xFF24: 2815 case 0x1D403: 2816 case 0x1D437: 2817 case 0x1D46B: 2818 case 0x1D49F: 2819 case 0x1D4D3: 2820 case 0x1D507: 2821 case 0x1D53B: 2822 case 0x1D56F: 2823 case 0x1D5A3: 2824 case 0x1D5D7: 2825 case 0x1D60B: 2826 case 0x1D63F: 2827 case 0x1D673: 2828 $str .= chr(0x44); /* D */ 2829 break; 2830 case 0x2130: /* E*/ 2831 case 0x0415: 2832 case 0xFF25: 2833 case 0x1D404: 2834 case 0x1D438: 2835 case 0x1D46C: 2836 case 0x1D4D4: 2837 case 0x1D508: 2838 case 0x1D53C: 2839 case 0x1D570: 2840 case 0x1D5A4: 2841 case 0x1D5D8: 2842 case 0x1D60C: 2843 case 0x1D640: 2844 case 0x1D674: 2845 $str .= chr(0x45); 2846 break; 2847 case 0x2131: 2848 case 0x213F: 2849 case 0xFF26: 2850 case 0x1D405: 2851 case 0x1D439: 2852 case 0x1D46D: 2853 case 0x1D4D5: 2854 case 0x1D509: 2855 case 0x1D53D: 2856 case 0x1D571: 2857 case 0x1D5A5: 2858 case 0x1D5D9: 2859 case 0x1D60D: 2860 case 0x1D641: 2861 case 0x1D675: 2862 $str .= chr(0x46); /* F */ 2863 break; 2864 case 0x0262: 2865 case 0xFF27: 2866 case 0x1D406: 2867 case 0x1D43A: 2868 case 0x1D46E: 2869 case 0x1D4A2: 2870 case 0x1D4D6: 2871 case 0x1D50A: 2872 case 0x1D53E: 2873 case 0x1D572: 2874 case 0x1D5A6: 2875 case 0x1D5DA: 2876 case 0x1D60E: 2877 case 0x1D642: 2878 case 0x1D676: 2879 $str .= chr(0x47); /* G */ 2880 break; 2881 case 0x029C: 2882 case 0x041D: 2883 case 0x210B: 2884 case 0x210C: 2885 case 0x210D: 2886 case 0xFF28: 2887 case 0x1D407: 2888 case 0x1D43B: 2889 case 0x1D46F: 2890 case 0x1D4D7: 2891 case 0x1D573: 2892 case 0x1D5A7: 2893 case 0x1D5DB: 2894 case 0x1D60F: 2895 case 0x1D643: 2896 case 0x1D677: 2897 $str .= chr(0x48); /* H */ 2898 break; 2899 case 0x0049: 2900 case 0x0406: 2901 case 0x2110: 2902 case 0x2111: 2903 case 0x2160: 2904 case 0xFF29: 2905 case 0x1D408: 2906 case 0x1D43C: 2907 case 0x1D470: 2908 case 0x1D4D8: 2909 case 0x1D540: 2910 case 0x1D574: 2911 case 0x1D5A8: 2912 case 0x1D5DC: 2913 case 0x1D610: 2914 case 0x1D644: 2915 case 0x1D678: 2916 $str .= chr(0x49); /* I */ 2917 break; 2918 case 0x026A: 2919 case 0x0408: 2920 case 0xFF2A: 2921 case 0x1D409: 2922 case 0x1D43D: 2923 case 0x1D471: 2924 case 0x1D4D9: 2925 case 0x1D541: 2926 case 0x1D575: 2927 case 0x1D5A9: 2928 case 0x1D5DD: 2929 case 0x1D611: 2930 case 0x1D645: 2931 case 0x1D679: 2932 $str .= chr(0x4A); /* J */ 2933 break; 2934 case 0xFF2B: 2935 case 0x1D40A: 2936 case 0x1D43E: 2937 case 0x1D472: 2938 case 0x1D4A5: 2939 case 0x1D4DA: 2940 case 0x1D50D: 2941 case 0x1D542: 2942 case 0x1D576: 2943 case 0x1D5AA: 2944 case 0x1D5DE: 2945 case 0x1D612: 2946 case 0x1D646: 2947 case 0x1D67A: 2948 $str .= chr(0x4B); /* K */ 2949 break; 2950 case 0xFF2C: 2951 case 0x1D40B: 2952 case 0x1D43F: 2953 case 0x1D473: 2954 case 0x1D4A6: 2955 case 0x1D4DB: 2956 case 0x1D50E: 2957 case 0x1D543: 2958 case 0x1D577: 2959 case 0x1D5AB: 2960 case 0x1D5DF: 2961 case 0x1D613: 2962 case 0x1D647: 2963 case 0x1D67B: 2964 $str .= chr(0x4C); /* L */ 2965 break; 2966 case 0x029F: 2967 case 0x041C: 2968 case 0xFF2D: 2969 case 0x2112: 2970 case 0x1D40C: 2971 case 0x1D440: 2972 case 0x1D474: 2973 case 0x1D4DC: 2974 case 0x1D50F: 2975 case 0x1D544: 2976 case 0x1D578: 2977 case 0x1D5AC: 2978 case 0x1D5E0: 2979 case 0x1D614: 2980 case 0x1D648: 2981 case 0x1D67C: 2982 $str .= chr(0x4D); /* M */ 2983 break; 2984 case 0x2133: 2985 case 0xFF2E: 2986 case 0x1D40D: 2987 case 0x1D441: 2988 case 0x1D475: 2989 case 0x1D4A9: 2990 case 0x1D4DD: 2991 case 0x1D511: 2992 case 0x1D579: 2993 case 0x1D5AD: 2994 case 0x1D5E1: 2995 case 0x1D615: 2996 case 0x1D649: 2997 case 0x1D67D: 2998 $str .= chr(0x4E); /* N */ 2999 break; 3000 case 0x004F: 3001 //case 0x00D8: 3002 case 0x041E: 3003 case 0x2205: 3004 case 0xFF2F: 3005 case 0x1D40E: 3006 case 0x1D442: 3007 case 0x1D476: 3008 case 0x1D4AA: 3009 case 0x1D4DE: 3010 case 0x1D512: 3011 case 0x1D546: 3012 case 0x1D57A: 3013 case 0x1D5AE: 3014 case 0x1D5E2: 3015 case 0x1D616: 3016 case 0x1D64A: 3017 case 0x1D67E: 3018 $str .= chr(0x4F); /* O */ 3019 break; 3020 case 0x0420: 3021 case 0x2118: 3022 case 0x2119: 3023 case 0xFF30: 3024 case 0x1D40F: 3025 case 0x1D443: 3026 case 0x1D477: 3027 case 0x1D4AB: 3028 case 0x1D4DF: 3029 case 0x1D513: 3030 case 0x1D57B: 3031 case 0x1D5AF: 3032 case 0x1D5E3: 3033 case 0x1D617: 3034 case 0x1D64B: 3035 case 0x1D67F: 3036 $str .= chr(0x50); /* P */ 3037 break; 3038 case 0x211A: 3039 case 0xFF31: 3040 case 0x1D410: 3041 case 0x1D444: 3042 case 0x1D478: 3043 case 0x1D4AC: 3044 case 0x1D4E0: 3045 case 0x1D514: 3046 case 0x1D57C: 3047 case 0x1D5B0: 3048 case 0x1D5E4: 3049 case 0x1D618: 3050 case 0x1D64C: 3051 case 0x1D680: 3052 $str .= chr(0x51); /* Q */ 3053 break; 3054 case 0x0280: 3055 case 0x211B: 3056 case 0x211C: 3057 case 0x211D: 3058 case 0xFF32: 3059 case 0x1D411: 3060 case 0x1D445: 3061 case 0x1D479: 3062 case 0x1D4E1: 3063 case 0x1D57D: 3064 case 0x1D5B1: 3065 case 0x1D5E5: 3066 case 0x1D619: 3067 case 0x1D64D: 3068 case 0x1D681: 3069 $str .= chr(0x52); /* R */ 3070 break; 3071 case 0x0405: 3072 case 0xFF33: 3073 case 0x1D412: 3074 case 0x1D446: 3075 case 0x1D47A: 3076 case 0x1D4AE: 3077 case 0x1D4E2: 3078 case 0x1D516: 3079 case 0x1D54A: 3080 case 0x1D57E: 3081 case 0x1D5B2: 3082 case 0x1D5E6: 3083 case 0x1D61A: 3084 case 0x1D64E: 3085 case 0x1D682: 3086 $str .= chr(0x53); /* S */ 3087 break; 3088 case 0x0422: 3089 case 0xFF34: 3090 case 0x1D413: 3091 case 0x1D447: 3092 case 0x1D47B: 3093 case 0x1D4AF: 3094 case 0x1D4E3: 3095 case 0x1D517: 3096 case 0x1D54B: 3097 case 0x1D57F: 3098 case 0x1D5B3: 3099 case 0x1D5E7: 3100 case 0x1D61B: 3101 case 0x1D64F: 3102 case 0x1D683: 3103 $str .= chr(0x54); /* T */ 3104 break; 3105 case 0x0055: 3106 case 0xFF35: 3107 case 0x1D414: 3108 case 0x1D448: 3109 case 0x1D47C: 3110 case 0x1D4B0: 3111 case 0x1D4E4: 3112 case 0x1D518: 3113 case 0x1D54C: 3114 case 0x1D580: 3115 case 0x1D5B4: 3116 case 0x1D5E8: 3117 case 0x1D61C: 3118 case 0x1D650: 3119 case 0x1D684: 3120 $str .= chr(0x55); /* U */ 3121 break; 3122 case 0xFF36: 3123 case 0x1D415: 3124 case 0x1D449: 3125 case 0x1D47D: 3126 case 0x1D4B1: 3127 case 0x1D4E5: 3128 case 0x1D519: 3129 case 0x1D54D: 3130 case 0x1D581: 3131 case 0x1D5B5: 3132 case 0x1D5E9: 3133 case 0x1D61D: 3134 case 0x1D651: 3135 case 0x1D685: 3136 $str .= chr(0x56); /* V */ 3137 break; 3138 case 0x051C: 3139 case 0xFF37: 3140 case 0x1D416: 3141 case 0x1D44A: 3142 case 0x1D47E: 3143 case 0x1D4B2: 3144 case 0x1D4E6: 3145 case 0x1D51A: 3146 case 0x1D54E: 3147 case 0x1D582: 3148 case 0x1D5B6: 3149 case 0x1D5EA: 3150 case 0x1D61E: 3151 case 0x1D652: 3152 case 0x1D686: 3153 $str .= chr(0x57); /* W */ 3154 break; 3155 case 0xFF38: 3156 case 0x1D417: 3157 case 0x1D44B: 3158 case 0x1D47F: 3159 case 0x1D4B3: 3160 case 0x1D4E7: 3161 case 0x1D51B: 3162 case 0x1D54F: 3163 case 0x1D583: 3164 case 0x1D5B7: 3165 case 0x1D5EB: 3166 case 0x1D61F: 3167 case 0x1D653: 3168 case 0x1D687: 3169 $str .= chr(0x58); /* X */ 3170 break; 3171 case 0x0059: 3172 case 0x2144: 3173 case 0xFF39: 3174 case 0x1D418: 3175 case 0x1D44C: 3176 case 0x1D480: 3177 case 0x1D4B4: 3178 case 0x1D4E8: 3179 case 0x1D51C: 3180 case 0x1D550: 3181 case 0x1D584: 3182 case 0x1D5B8: 3183 case 0x1D5EC: 3184 case 0x1D620: 3185 case 0x1D654: 3186 case 0x1D688: 3187 $str .= chr(0x59); /* Y */ 3188 break; 3189 case 0x29F5: 3190 case 0x29F9: 3191 case 0xFE68: 3192 $str .= chr(0x5C); /* Backslash */ 3193 break; 3194 case 0x0430; // CYRILLIC SMALL LETTER A 3195 case 0x2124: 3196 case 0x2128: 3197 case 0xFF3A: 3198 case 0xFF41: 3199 case 0x1D419: 3200 case 0x1D44D: 3201 case 0x1D482: 3202 case 0x1D656: 3203 case 0x1D68A: 3204 $str .= chr(0x61); /* a */ 3205 break; 3206 case 0xFF42: /* b */ 3207 case 0x1D41B: 3208 case 0x1D44F: 3209 case 0x1D483: 3210 case 0x1D4B7: 3211 case 0x1D4EB: 3212 case 0x1D51F: 3213 case 0x1D553: 3214 case 0x1D587: 3215 case 0x1D5BB: 3216 case 0x1D5EF: 3217 case 0x1D623: 3218 case 0x1D657: 3219 case 0x1D68B: 3220 $str .= chr(0x62); 3221 break; 3222 case 0x0063: /* c */ 3223 case 0x1D04: 3224 case 0x441: //CYRILLIC SMALL LETTER ES 3225 case 0xFF43: 3226 case 0x1D41C: 3227 case 0x1D450: 3228 case 0x1D484: 3229 case 0x1D4B8: 3230 case 0x1D4EC: 3231 case 0x1D520: 3232 case 0x1D554: 3233 case 0x1D588: 3234 case 0x1D5BC: 3235 case 0x1D5F0: 3236 case 0x1D624: 3237 case 0x1D658: 3238 case 0x1D68C: 3239 $str .= chr(0x63); 3240 break; 3241 case 0x0064: /* d */ 3242 case 0x1D05: 3243 case 0x2146: 3244 case 0xFF44: 3245 case 0x1D41D: 3246 case 0x1D451: 3247 case 0x1D485: 3248 case 0x1D4B9: 3249 case 0x1D4ED: 3250 case 0x1D521: 3251 case 0x1D555: 3252 case 0x1D589: 3253 case 0x1D5BD: 3254 case 0x1D5F1: 3255 case 0x1D625: 3256 case 0x1D659: 3257 case 0x1D68D: 3258 $str .= chr(0x64); 3259 break; 3260 case 0x0065: 3261 case 0x0435: // CYRILLIC SMALL LETTER IE 3262 case 0x1D07: 3263 case 0x212F: 3264 case 0x2147: 3265 case 0xFF45: 3266 case 0x1D41E: 3267 case 0x1D452: 3268 case 0x1D486: 3269 case 0x1D4EE: 3270 case 0x1D522: 3271 case 0x1D556: 3272 case 0x1D5BE: 3273 case 0x1D5F2: 3274 case 0x1D626: 3275 case 0x1D65A: 3276 case 0x1D68E: 3277 $str .= chr(0x65); /* e */ 3278 break; 3279 case 0x0066: /* f */ 3280 case 0xFF46: 3281 case 0x1D41F: 3282 case 0x1D453: 3283 case 0x1D487: 3284 case 0x1D4BB: 3285 case 0x1D4EF: 3286 case 0x1D523: 3287 case 0x1D557: 3288 case 0x1D58B: 3289 case 0x1D5BF: 3290 case 0x1D5F3: 3291 case 0x1D627: 3292 case 0x1D65B: 3293 case 0x1D68F: 3294 $str .= chr(0x66); 3295 break; 3296 case 0x0067: /* g */ 3297 case 0xFF47: 3298 case 0x210A: 3299 case 0x1D420: 3300 case 0x1D454: 3301 case 0x1D488: 3302 case 0x1D4F0: 3303 case 0x1D524: 3304 case 0x1D558: 3305 case 0x1D58C: 3306 case 0x1D5C0: 3307 case 0x1D5F4: 3308 case 0x1D628: 3309 case 0x1D65C: 3310 case 0x1D690: 3311 $str .= chr(0x67); 3312 break; 3313 case 0x0068: /* h */ 3314 case 0x04BB: 3315 case 0xFF48: 3316 case 0x1D421: 3317 case 0x1D489: 3318 case 0x1D4BD: 3319 case 0x1D4F1: 3320 case 0x1D525: 3321 case 0x1D559: 3322 case 0x1D58D: 3323 case 0x1D5C1: 3324 case 0x1D5F5: 3325 case 0x1D629: 3326 case 0x1D65D: 3327 case 0x1D691: 3328 $str .= chr(0x68); 3329 break; 3330 case 0x0456: 3331 case 0x1D09: 3332 case 0x2071: 3333 case 0xFF49: 3334 case 0x2148: 3335 case 0x1D422: 3336 case 0x1D456: 3337 case 0x1D48A: 3338 case 0x1D4BE: 3339 case 0x1D4F2: 3340 case 0x1D526: 3341 case 0x1D55A: 3342 case 0x1D58E: 3343 case 0x1D5C2: 3344 case 0x1D5F6: 3345 case 0x1D62A: 3346 case 0x1D65E: 3347 case 0x1D692: 3348 $str .= chr(0x69); /* i */ 3349 break; 3350 case 0x1D0A: 3351 case 0x2149: 3352 case 0xFF4A: 3353 case 0x1D423: 3354 case 0x1D457: 3355 case 0x1D48B: 3356 case 0x1D4BF: 3357 case 0x1D4F3: 3358 case 0x1D527: 3359 case 0x1D55B: 3360 case 0x1D58F: 3361 case 0x1D5C3: 3362 case 0x1D5F7: 3363 case 0x1D62B: 3364 case 0x1D65F: 3365 case 0x1D693: 3366 $str .= chr(0x6A); /* j */ 3367 break; 3368 case 0x006B: /* k */ 3369 case 0x1D0B: 3370 //case 0x03BA: 3371 case 0xFF4B: 3372 case 0x1D424: 3373 case 0x1D458: 3374 case 0x1D48C: 3375 case 0x1D4C0: 3376 case 0x1D4F4: 3377 case 0x1D528: 3378 case 0x1D55C: 3379 case 0x1D590: 3380 case 0x1D5C4: 3381 case 0x1D5F8: 3382 case 0x1D62C: 3383 case 0x1D660: 3384 case 0x1D694: 3385 $str .= chr(0x6B); 3386 break; 3387 case 0x006C: /* l */ 3388 case 0x2113: 3389 case 0xFF4C: 3390 case 0x1D425: 3391 case 0x1D459: 3392 case 0x1D48D: 3393 case 0x1D4C1: 3394 case 0x1D4F5: 3395 case 0x1D529: 3396 case 0x1D55D: 3397 case 0x1D591: 3398 case 0x1D5C5: 3399 case 0x1D5F9: 3400 case 0x1D62D: 3401 case 0x1D661: 3402 case 0x1D695: 3403 $str .= chr(0x6C); 3404 break; 3405 case 0x006D: /* m */ 3406 case 0x1D0D: 3407 case 0xFF4D: 3408 case 0x1D426: 3409 case 0x1D45A: 3410 case 0x1D48E: 3411 case 0x1D4C2: 3412 case 0x1D52A: 3413 case 0x1D55E: 3414 case 0x1D592: 3415 case 0x1D5C6: 3416 case 0x1D5FA: 3417 case 0x1D62E: 3418 case 0x1D662: 3419 case 0x1D696: 3420 case 0x1D4F6: 3421 $str .= chr(0x6D); 3422 break; 3423 case 0x207F: 3424 case 0xFF4E: 3425 case 0x1D427: 3426 case 0x1D45B: 3427 case 0x1D48F: 3428 case 0x1D4C3: 3429 case 0x1D4F7: 3430 case 0x1D52B: 3431 case 0x1D55F: 3432 case 0x1D593: 3433 case 0x1D5C7: 3434 case 0x1D5FB: 3435 case 0x1D62F: 3436 case 0x1D663: 3437 case 0x1D697: 3438 $str .= chr(0x6E); /* n */ 3439 break; 3440 case 0x006F: 3441 //case 0x00F8: 3442 case 0x043E: // CYRILLIC SMALL LETTER O 3443 case 0x1D0F: 3444 case 0x2134: 3445 case 0xFF4F: 3446 case 0x1D428: 3447 case 0x1D45C: 3448 case 0x1D490: 3449 case 0x1D4F8: 3450 case 0x1D52C: 3451 case 0x1D560: 3452 case 0x1D594: 3453 case 0x1D5C8: 3454 case 0x1D5FC: 3455 case 0x1D630: 3456 case 0x1D664: 3457 case 0x1D698: 3458 $str .= chr(0x6F); /* o */ 3459 break; 3460 case 0x0070: 3461 case 0x0440: // CYRILLIC SMALL LETTER ER 3462 case 0x1D18: 3463 case 0xFF50: 3464 case 0x213C: 3465 case 0x1D429: 3466 case 0x1D45D: 3467 case 0x1D491: 3468 case 0x1D4C5: 3469 case 0x1D4F9: 3470 case 0x1D52D: 3471 case 0x1D561: 3472 case 0x1D595: 3473 case 0x1D5C9: 3474 case 0x1D5FD: 3475 case 0x1D631: 3476 case 0x1D665: 3477 case 0x1D699: 3478 $str .= chr(0x70); /* p */ 3479 break; 3480 case 0x0071: /* q */ 3481 case 0x051B: 3482 case 0xFF51: 3483 case 0x1D42A: 3484 case 0x1D45E: 3485 case 0x1D492: 3486 case 0x1D4C6: 3487 case 0x1D4FA: 3488 case 0x1D52E: 3489 case 0x1D562: 3490 case 0x1D596: 3491 case 0x1D5CA: 3492 case 0x1D5FE: 3493 case 0x1D632: 3494 case 0x1D666: 3495 case 0x1D69A: 3496 $str .= chr(0x71); 3497 break; 3498 case 0x0072: /* r */ 3499 case 0xFF52: 3500 case 0x1D42B: 3501 case 0x1D45F: 3502 case 0x1D493: 3503 case 0x1D4C7: 3504 case 0x1D4FB: 3505 case 0x1D52F: 3506 case 0x1D563: 3507 case 0x1D597: 3508 case 0x1D5CB: 3509 case 0x1D5FF: 3510 case 0x1D633: 3511 case 0x1D667: 3512 case 0x1D69B: 3513 $str .= chr(0x72); 3514 break; 3515 case 0x0073: /* s */ 3516 case 0x0455: 3517 case 0xFF53: 3518 case 0x1D42C: 3519 case 0x1D460: 3520 case 0x1D494: 3521 case 0x1D4C8: 3522 case 0x1D4FC: 3523 case 0x1D530: 3524 case 0x1D564: 3525 case 0x1D598: 3526 case 0x1D5CC: 3527 case 0x1D600: 3528 case 0x1D634: 3529 case 0x1D668: 3530 case 0x1D69C: 3531 $str .= chr(0x73); 3532 break; 3533 case 0x0074: /* t */ 3534 case 0x1D1B: 3535 case 0xFF54: 3536 case 0x1D42D: 3537 case 0x1D461: 3538 case 0x1D495: 3539 case 0x1D4C9: 3540 case 0x1D4FD: 3541 case 0x1D531: 3542 case 0x1D565: 3543 case 0x1D599: 3544 case 0x1D5CD: 3545 case 0x1D601: 3546 case 0x1D635: 3547 case 0x1D669: 3548 case 0x1D69D: 3549 $str .= chr(0x74); 3550 break; 3551 case 0x0075: /* u */ 3552 case 0x1D1C: 3553 case 0xFF55: 3554 case 0x1D42E: 3555 case 0x1D462: 3556 case 0x1D496: 3557 case 0x1D4CA: 3558 case 0x1D4FE: 3559 case 0x1D532: 3560 case 0x1D566: 3561 case 0x1D59A: 3562 case 0x1D5CE: 3563 case 0x1D602: 3564 case 0x1D636: 3565 case 0x1D66A: 3566 case 0x1D69E: 3567 $str .= chr(0x75); 3568 break; 3569 case 0x0076: /* v */ 3570 case 0x1D20: 3571 case 0xFF56: 3572 case 0x1D42F: 3573 case 0x1D463: 3574 case 0x1D497: 3575 case 0x1D4CB: 3576 case 0x1D4FF: 3577 case 0x1D533: 3578 case 0x1D567: 3579 case 0x1D59B: 3580 case 0x1D5CF: 3581 case 0x1D603: 3582 case 0x1D637: 3583 case 0x1D66B: 3584 case 0x1D69F: 3585 $str .= chr(0x76); 3586 break; 3587 case 0x0077: /* w */ 3588 case 0x051D: 3589 case 0x1D21: 3590 case 0x24B2: 3591 case 0x24E6: 3592 case 0xFF57: 3593 case 0x1D430: 3594 case 0x1D464: 3595 case 0x1D498: 3596 case 0x1D4CC: 3597 case 0x1D500: 3598 case 0x1D534: 3599 case 0x1D568: 3600 case 0x1D59C: 3601 case 0x1D5D0: 3602 case 0x1D604: 3603 case 0x1D638: 3604 case 0x1D66C: 3605 case 0x1D6A0: 3606 $str .= chr(0x77); 3607 break; 3608 case 0x0078: /* x */ 3609 case 0xFF58: 3610 case 0x2718: 3611 case 0x1D431: 3612 case 0x1D465: 3613 case 0x1D499: 3614 case 0x1D4CD: 3615 case 0x1D501: 3616 case 0x1D535: 3617 case 0x1D569: 3618 case 0x1D59D: 3619 case 0x1D5D1: 3620 case 0x1D605: 3621 case 0x1D639: 3622 case 0x1D66D: 3623 case 0x1D6A1: 3624 $str .= chr(0x78); 3625 break; 3626 case 0x0443: // CYRILLIC SMALL LETTER U 3627 case 0xFF59: 3628 case 0x1D432: 3629 case 0x1D466: 3630 case 0x1D49A: 3631 case 0x1D4CE: 3632 case 0x1D502: 3633 case 0x1D536: 3634 case 0x1D56A: 3635 case 0x1D59E: 3636 case 0x1D5D2: 3637 case 0x1D606: 3638 case 0x1D63A: 3639 case 0x1D66E: 3640 case 0x1D6A2: 3641 $str .= chr(0x79); /* y */ 3642 break; 3643 case 0x007A: /* z */ 3644 case 0x1D22: 3645 case 0xFF5A: 3646 case 0x1D433: 3647 case 0x1D467: 3648 case 0x1D49B: 3649 case 0x1D4CF: 3650 case 0x1D503: 3651 case 0x1D537: 3652 case 0x1D56B: 3653 case 0x1D59F: 3654 case 0x1D5D3: 3655 case 0x1D607: 3656 case 0x1D63B: 3657 case 0x1D66F: 3658 case 0x1D6A3: 3659 $str .= chr(0x7A); 3660 break; 3661 case 0xFE5B: 3662 case 0xFF5B: /* left curly bracket */ 3663 $str .= chr(0x7B); 3664 break; 3665 case 0xFF5C: /* pipe */ 3666 $str .= chr(0x7C); 3667 break; 3668 case 0xFE5C: 3669 case 0xFF5D: /* right curly bracket */ 3670 $str .= chr(0x7D); 3671 break; 3672 case 0xFF5E: /* tilde */ 3673 $str .= chr(0x7E); 3674 break; 3675 3676 default: 3677 $str .= $char; 3678 break; 3679 } 3680 } 3681 3682 function get_lang_regex_from_country($country) { 3683 static $codes = [ 3684 'AD' => '/\b(?:ca)\b/', 3685 'AE' => '/\b(?:ar|fa|hi|ur)\b/', 3686 'AF' => '/\b(?:fa|ps|uz|tk)\b/', 3687 'AL' => '/\b(?:sq|el)\b/', 3688 'AM' => '/\b(?:hy)\b/', 3689 'AO' => '/\b(?:pt)\b/', 3690 'AR' => '/\b(?:es|it|de|fr|gn)\b/', 3691 'AS' => '/\b(?:sm|to)\b/', 3692 'AT' => '/\b(?:de|hr|hu|sl)\b/', 3693 'AW' => '/\b(?:nl|pap|es)\b/', 3694 'AX' => '/\b(?:sv)\b/', 3695 'AZ' => '/\b(?:az|ru|hy)\b/', 3696 'BA' => '/\b(?:bs|hr|sr)\b/', 3697 'BD' => '/\b(?:bn)\b/', 3698 'BE' => '/\b(?:nl|fr|de)\b/', 3699 'BF' => '/\b(?:fr|mos)\b/', 3700 'BG' => '/\b(?:bg|tr|rom)\b/', 3701 'BH' => '/\b(?:ar|fa|ur)\b/', 3702 'BI' => '/\b(?:fr|rn)\b/', 3703 'BJ' => '/\b(?:fr)\b/', 3704 'BL' => '/\b(?:fr)\b/', 3705 'BM' => '/\b(?:pt)\b/', 3706 'BN' => '/\b(?:ms)\b/', 3707 'BO' => '/\b(?:es|qu|ay)\b/', 3708 'BQ' => '/\b(?:nl|pap)\b/', 3709 'BR' => '/\b(?:pt|es|fr)\b/', 3710 'BT' => '/\b(?:dz)\b/', 3711 'BW' => '/\b(?:tn)\b/', 3712 'BY' => '/\b(?:be|ru)\b/', 3713 'BZ' => '/\b(?:es)\b/', 3714 'CA' => '/\b(?:fr|iu)\b/', 3715 'CC' => '/\b(?:ms)\b/', 3716 'CD' => '/\b(?:fr|ln|ktu|kg|sw|lua)\b/', 3717 'CF' => '/\b(?:fr|sg|ln|kg)\b/', 3718 'CG' => '/\b(?:fr|kg|ln)\b/', 3719 'CH' => '/\b(?:de|fr|it|rm)\b/', 3720 'CI' => '/\b(?:fr)\b/', 3721 'CK' => '/\b(?:mi)\b/', 3722 'CL' => '/\b(?:es)\b/', 3723 'CM' => '/\b(?:fr)\b/', 3724 'CN' => '/\b(?:zh|yue|wuu|dta|ug|za)\b/', 3725 'CO' => '/\b(?:es)\b/', 3726 'CR' => '/\b(?:es)\b/', 3727 'CU' => '/\b(?:es|pap)\b/', 3728 'CV' => '/\b(?:pt)\b/', 3729 'CW' => '/\b(?:nl|pap)\b/', 3730 'CX' => '/\b(?:zh|ms)\b/', 3731 'CY' => '/\b(?:el|tr)\b/', 3732 'CZ' => '/\b(?:cs|sk)\b/', 3733 'DE' => '/\b(?:de)\b/', 3734 'DJ' => '/\b(?:fr|ar|so|aa)\b/', 3735 'DK' => '/\b(?:da|fo|de)\b/', 3736 'DO' => '/\b(?:es)\b/', 3737 'DZ' => '/\b(?:ar|fr)\b/', 3738 'EC' => '/\b(?:es)\b/', 3739 'EE' => '/\b(?:et|ru)\b/', 3740 'EG' => '/\b(?:ar|fr)\b/', 3741 'EH' => '/\b(?:ar|mey)\b/', 3742 'ER' => '/\b(?:aa|ar|tig|kun|ti)\b/', 3743 'ES' => '/\b(?:es|ca|gl|eu|oc)\b/', 3744 'ET' => '/\b(?:am|om|ti|so|sid)\b/', 3745 'FI' => '/\b(?:fi|sv|smn)\b/', 3746 'FJ' => '/\b(?:fj)\b/', 3747 'FM' => '/\b(?:chk|pon|yap|kos|uli|woe|nkr|kpg)\b/', 3748 'FO' => '/\b(?:fo|da)\b/', 3749 'FR' => '/\b(?:fr|frp|br|co|ca|eu|oc)\b/', 3750 'GA' => '/\b(?:fr)\b/', 3751 'GB' => '/\b(?:en)\b/', 3752 'GE' => '/\b(?:ka|ru|hy|az)\b/', 3753 'GF' => '/\b(?:fr)\b/', 3754 'GG' => '/\b(?:nrf)\b/', 3755 'GH' => '/\b(?:ak|ee|tw)\b/', 3756 'GI' => '/\b(?:es|it|pt)\b/', 3757 'GL' => '/\b(?:kl|da)\b/', 3758 'GM' => '/\b(?:mnk|wof|wo|ff)\b/', 3759 'GN' => '/\b(?:fr)\b/', 3760 'GP' => '/\b(?:fr)\b/', 3761 'GQ' => '/\b(?:es|fr)\b/', 3762 'GR' => '/\b(?:el|fr)\b/', 3763 'GT' => '/\b(?:es)\b/', 3764 'GU' => '/\b(?:ch)\b/', 3765 'GW' => '/\b(?:pt|pov)\b/', 3766 'HK' => '/\b(?:zh|yue|zh)\b/', 3767 'HN' => '/\b(?:es|cab|miq)\b/', 3768 'HR' => '/\b(?:hr|sr)\b/', 3769 'HT' => '/\b(?:ht|fr)\b/', 3770 'HU' => '/\b(?:hu)\b/', 3771 'ID' => '/\b(?:id|nl|jv)\b/', 3772 'IE' => '/\b(?:ga)\b/', 3773 'IL' => '/\b(?:he|ar)\b/', 3774 'IM' => '/\b(?:gv)\b/', 3775 'IN' => '/\b(?:hi|bn|te|mr|ta|ur|gu|kn|ml|or|pa|as|bh|sat|ks|ne|sd|kok|doi|mni|sit|sa|fr|lus|inc)\b/', 3776 'IQ' => '/\b(?:ar|ku|hy)\b/', 3777 'IR' => '/\b(?:fa|ku)\b/', 3778 'IS' => '/\b(?:is|de|da|sv|no)\b/', 3779 'IT' => '/\b(?:it|de|fr|sc|ca|co|sl)\b/', 3780 'JE' => '/\b(?:fr|nrf)\b/', 3781 'JO' => '/\b(?:ar)\b/', 3782 'JP' => '/\b(?:ja)\b/', 3783 'KE' => '/\b(?:sw)\b/', 3784 'KG' => '/\b(?:ky|uz|ru)\b/', 3785 'KH' => '/\b(?:km|fr)\b/', 3786 'KI' => '/\b(?:gil)\b/', 3787 'KM' => '/\b(?:ar|fr)\b/', 3788 'KP' => '/\b(?:ko)\b/', 3789 'KR' => '/\b(?:ko)\b/', 3790 'XK' => '/\b(?:sq|sr)\b/', 3791 'KW' => '/\b(?:ar)\b/', 3792 'KZ' => '/\b(?:kk|ru)\b/', 3793 'LA' => '/\b(?:lo|fr)\b/', 3794 'LB' => '/\b(?:ar|fr|hy)\b/', 3795 'LI' => '/\b(?:de)\b/', 3796 'LK' => '/\b(?:si|ta)\b/', 3797 'LS' => '/\b(?:st|zu|xh)\b/', 3798 'LT' => '/\b(?:lt|ru|pl)\b/', 3799 'LU' => '/\b(?:lb|de|fr)\b/', 3800 'LV' => '/\b(?:lv|ru|lt)\b/', 3801 'LY' => '/\b(?:ar|it)\b/', 3802 'MA' => '/\b(?:ar|ber|fr)\b/', 3803 'MC' => '/\b(?:fr|it)\b/', 3804 'MD' => '/\b(?:ro|ru|gag|tr)\b/', 3805 'ME' => '/\b(?:sr|hu|bs|sq|hr|rom)\b/', 3806 'MF' => '/\b(?:fr)\b/', 3807 'MG' => '/\b(?:fr|mg)\b/', 3808 'MH' => '/\b(?:mh)\b/', 3809 'MK' => '/\b(?:mk|sq|tr|rmm|sr)\b/', 3810 'ML' => '/\b(?:fr|bm)\b/', 3811 'MM' => '/\b(?:my)\b/', 3812 'MN' => '/\b(?:mn|ru)\b/', 3813 'MO' => '/\b(?:zh|zh|pt)\b/', 3814 'MP' => '/\b(?:fil|tl|zh|ch)\b/', 3815 'MQ' => '/\b(?:fr)\b/', 3816 'MR' => '/\b(?:ar|fuc|snk|fr|mey|wo)\b/', 3817 'MT' => '/\b(?:mt)\b/', 3818 'MU' => '/\b(?:bho|fr)\b/', 3819 'MV' => '/\b(?:dv)\b/', 3820 'MW' => '/\b(?:ny|yao|tum|swk)\b/', 3821 'MX' => '/\b(?:es)\b/', 3822 'MY' => '/\b(?:ms|zh|ta|te|ml|pa|th)\b/', 3823 'MZ' => '/\b(?:pt|vmw)\b/', 3824 'NA' => '/\b(?:af|de|hz|naq)\b/', 3825 'NC' => '/\b(?:fr)\b/', 3826 'NE' => '/\b(?:fr|ha|kr|dje)\b/', 3827 'NG' => '/\b(?:ha|yo|ig|ff)\b/', 3828 'NI' => '/\b(?:es)\b/', 3829 'NL' => '/\b(?:nl|fy)\b/', 3830 'NO' => '/\b(?:no|nb|nn|se|fi)\b/', 3831 'NP' => '/\b(?:ne)\b/', 3832 'NR' => '/\b(?:na)\b/', 3833 'NU' => '/\b(?:niu)\b/', 3834 'NZ' => '/\b(?:mi)\b/', 3835 'OM' => '/\b(?:ar|bal|ur)\b/', 3836 'PA' => '/\b(?:es)\b/', 3837 'PE' => '/\b(?:es|qu|ay)\b/', 3838 'PF' => '/\b(?:fr|ty)\b/', 3839 'PG' => '/\b(?:ho|meu|tpi)\b/', 3840 'PH' => '/\b(?:tl|fil|ceb|tgl|ilo|hil|war|pam|bik|bcl|pag|mrw|tsg|mdh|cbk|krj|sgd|msb|akl|ibg|yka|mta|abx)\b/', 3841 'PK' => '/\b(?:ur|pa|sd|ps|brh)\b/', 3842 'PL' => '/\b(?:pl)\b/', 3843 'PM' => '/\b(?:fr)\b/', 3844 'PR' => '/\b(?:es)\b/', 3845 'PS' => '/\b(?:ar)\b/', 3846 'PT' => '/\b(?:pt|mwl)\b/', 3847 'PW' => '/\b(?:pau|sov|tox|ja|fil|zh)\b/', 3848 'PY' => '/\b(?:es|gn)\b/', 3849 'QA' => '/\b(?:ar|es)\b/', 3850 'RE' => '/\b(?:fr)\b/', 3851 'RO' => '/\b(?:ro|hu|rom)\b/', 3852 'RS' => '/\b(?:sr|hu|bs|rom)\b/', 3853 'RU' => '/\b(?:ru|tt|xal|cau|ady|kv|ce|tyv|cv|udm|tut|mns|bua|myv|mdf|chm|ba|inh|tut|kbd|krc|av|sah|nog)\b/', 3854 'RW' => '/\b(?:rw|fr|sw)\b/', 3855 'SA' => '/\b(?:ar)\b/', 3856 'SB' => '/\b(?:tpi)\b/', 3857 'SC' => '/\b(?:fr)\b/', 3858 'SD' => '/\b(?:ar|fia)\b/', 3859 'SE' => '/\b(?:sv|se|sma|fi)\b/', 3860 'SG' => '/\b(?:cmn|ms|ta|zh)\b/', 3861 'SI' => '/\b(?:sl|sh)\b/', 3862 'SJ' => '/\b(?:no|ru)\b/', 3863 'SK' => '/\b(?:sk|hu)\b/', 3864 'SL' => '/\b(?:mtem)\b/', 3865 'SM' => '/\b(?:it)\b/', 3866 'SN' => '/\b(?:fr|wo|fuc|mnk)\b/', 3867 'SO' => '/\b(?:so|ar|it)\b/', 3868 'SR' => '/\b(?:nl|srn|hns|jv)\b/', 3869 'ST' => '/\b(?:pt)\b/', 3870 'SV' => '/\b(?:es)\b/', 3871 'SX' => '/\b(?:nl)\b/', 3872 'SY' => '/\b(?:ar|ku|hy|arc|fr)\b/', 3873 'SZ' => '/\b(?:ss)\b/', 3874 'TD' => '/\b(?:fr|ar|sre)\b/', 3875 'TF' => '/\b(?:fr)\b/', 3876 'TG' => '/\b(?:fr|ee|hna|kbp|dag|ha)\b/', 3877 'TH' => '/\b(?:th)\b/', 3878 'TJ' => '/\b(?:tg|ru)\b/', 3879 'TK' => '/\b(?:tkl)\b/', 3880 'TL' => '/\b(?:tet|pt|id)\b/', 3881 'TM' => '/\b(?:tk|ru|uz)\b/', 3882 'TN' => '/\b(?:ar|fr)\b/', 3883 'TO' => '/\b(?:to)\b/', 3884 'TR' => '/\b(?:tr|ku|diq|az|av)\b/', 3885 'TT' => '/\b(?:hns|fr|es|zh)\b/', 3886 'TV' => '/\b(?:tvl|sm|gil)\b/', 3887 'TW' => '/\b(?:zh|zh|nan|hak)\b/', 3888 'TZ' => '/\b(?:sw|ar)\b/', 3889 'UA' => '/\b(?:uk|ru|rom|pl|hu)\b/', 3890 'UG' => '/\b(?:lg|sw|ar)\b/', 3891 'US' => '/\b(?:en|es)\b/', 3892 'UY' => '/\b(?:es)\b/', 3893 'UZ' => '/\b(?:uz|ru|tg)\b/', 3894 'VA' => '/\b(?:la|it|fr)\b/', 3895 'VC' => '/\b(?:fr)\b/', 3896 'VE' => '/\b(?:es)\b/', 3897 'VN' => '/\b(?:vi|fr|zh|km)\b/', 3898 'VU' => '/\b(?:bi|fr)\b/', 3899 'WF' => '/\b(?:wls|fud|fr)\b/', 3900 'WS' => '/\b(?:sm)\b/', 3901 'YE' => '/\b(?:ar)\b/', 3902 'YT' => '/\b(?:fr)\b/', 3903 'ZA' => '/\b(?:zu|xh|af|nso|tn|st|ts|ss|ve|nr)\b/', 3904 'ZM' => '/\b(?:bem|loz|lun|lue|ny|toi)\b/', 3905 'ZW' => '/\b(?:sn|nr|nd)\b/', 3906 'CS' => '/\b(?:cu|hu|sq|sr)\b/', 3907 'AN' => '/\b(?:nl|es)\b/' 3908 ]; 3909 3910 if (isset($codes[$country])) { 3911 return $codes[$country]; 3912 } 3913 3914 return null; 3915 }