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