array_search($f2, $pkeys);
}
// style 1 - no spaces, just letters+numbers in fields
function is_random_text($fields)
{
foreach($fields as $field) {
$num = preg_match('/[0-9]/', $field);
$lc = preg_match('/[a-z]/', $field);
$uc = preg_match('/[A-Z]/', $field);
$spc = preg_match('/ /', $field);
if ($spc || ($num + $lc + $uc)<2) return false;
}
return true;
}
// style 2 - uc+lc+spaces in fields
function is_random_word_text($fields)
{
foreach($fields as $field) {
$other = preg_match('/[^a-zA-Z \n]/', $field);
$lc = preg_match('/[a-z]/', $field);
$uc = preg_match('/[A-Z]/', $field);
$spc = preg_match('/ /', $field);
if ($other || !$spc || !$lc || !$uc) return false;
}
return true;
}
function has_cyrillic($txt)
{
return preg_match('/[\xD0-\xD3][\x80-\xBF]/', $txt) > 0;
}
function expand_short_urls($com)
{
$urls = array();
$urltext = "";
$com = preg_replace("/\(dot\)|DOT/", ".", $com);
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);
foreach($urls as $url) {
$com .= strtolower(rpc_find_real_url(str_replace("preview.tinyurl","tinyurl",$url)));
}
return $com;
}
function normalize_ascii($text, $preserve_case = 0)
{
$text = preg_replace( '#[(\[={]dot[)\]=}]#i', '.', $text );
$t = Transliterator::create("Any-Latin; nfd; [:nonspacing mark:] remove; nfkc; Latin-ASCII");
if (!$t) return $text;
$text = $t->transliterate($text);
if (!$preserve_case) $text = strtolower($text);
return $text;
}
function strip_zerowidth($text) {
$text = preg_replace('/
[\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}|
\x{115F}|\x{1160}|\x{FFA0}|\x{034f}|\x{180e}|\x{17B4}|\x{17B5}|
[\x{0001}-\x{0008}\x{000E}\x{000F}\x{0010}-\x{001F}\x{007F}-\x{009F}]|
[\x{2000}-\x{200F}]|
[\x{2028}-\x{202F}]|
[\x{2060}-\x{206F}]|
[\x{fe00}-\x{fe0f}]|
[\x{FFF0}-\x{FFFB}]|
[\x{E0100}-\x{E01EF}]|
[\x{E0001}-\x{E007F}]
/ux', '', $text);
return $text;
}
/**
* Removes codepoints above 3134F:
* E0000..E007F; Tags
* E0100..E01EF; Variation Selectors Supplement
* F0000..FFFFF; Supplementary Private Use Area-A
* 100000..10FFFF; Supplementary Private Use Area-B
*/
function strip_private_unicode($str) {
if ($str === '') {
return $str;
}
return preg_replace('/[^\x{0000}-\x{3134F}]/u', '', $str);
}
function strip_emoticons($text, $has_sjis_art = false) {
$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}]';
if (!$has_sjis_art) {
$regex .= '|[\x{2502}-\x{257F}]';
}
return preg_replace("/$regex/u", '', $text);
}
function strip_fake_capcodes($str) {
// double FULLWIDTH NUMBER SIGN or #
// PLACE OF INTEREST SIGN
return preg_replace('/[\x{FF03}#]{2,}|\x{2318}/u', '', $str);
}
function normalize_text($text, $filter = '') {
$text = normalize_ascii($text);
$text = strip_zerowidth($text);
//if ($filter) $text = $filter($text);
$text = preg_replace('@[^a-zA-Z0-9.,/&:;?=~_-]@', '', $text);
return $text;
}
function normalize_content( $name )
{
// this needs some absolutely retarded shit to get this to not suck, however
// it is an almost fool proof way of translating to ascii letters
// without breaking kanji, cyrillic etc
$name = preg_replace( '#[\x{2600}-\x{26FF}]#u', '', $name );
// set internal incoding to utf-8
//die($name);
$oldEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
$name = convert_to_utf8($name);
// Done, back to old encoding
$newname = '';
$len = mb_strlen( $name );
for( $i = 0; $i < $len; $i++ ) {
trans_similar_to_ascii( $newname, mb_substr( $name, $i, 1 ) );
}
mb_internal_encoding($oldEncoding);
return $newname;
}
function convert_to_utf8( $content )
{
if( !mb_check_encoding( $content, 'UTF-8' ) || !($content === mb_convert_encoding(mb_convert_encoding($content, 'UTF-32', 'UTF-8' ), 'UTF-8', 'UTF-32')) ) {
$content = mb_convert_encoding($content, 'UTF-8');
}
return $content;
}
function mb_ord( $char )
{
mb_detect_order( array( 'UTF-8', 'ISO-8859-15', 'ISO-8859-1', 'ASCII' ) );
$result = unpack( 'N', mb_convert_encoding( $char, 'UCS-4BE', 'UTF-8' ) );
if( is_array( $result ) === true ) return $result[1];
return ord($char);
}
function normalize_check($com,$sub,$f)
{
$n = normalize_text($com.$sub, "expand_short_urls");
$n2 = preg_replace("/[\x80-\xFF]/", "", html_entity_decode($com, ENT_QUOTES, "UTF-8"));
record_post_info($f, "from: $com$sub\nto: $n\ndeutf8: $n2");
}
function match_banned_text($links, $text, $is_re)
{
$badlink = "";
$should_ban = false;
foreach ($links as $l) {
if ($l == '#') continue;
$badlink = $l;
if ($is_re) {
$should_ban = preg_match($l, $text, $m) > 0;
$badlink = TEST_BOARD ? "'$l' ({$m[0]})" : "'{$m[0]}'";
} else {
$should_ban = strpos($text, $l) !== FALSE;
}
if ($should_ban)
break;
}
return $should_ban ? $badlink : "";
}
function check_banned_links($text, $links, $priv, $pub, $is_re, $name, $dest, $ban, $long, $perm = false)
{
//check_banned_links($normalized_com, $sex, "sex spam links", S_BANNEDLINK, false, $name, $dest, true, false);
$badlink = match_banned_text($links, $text, $is_re);
$should_ban = ($badlink != "");
$len = $long ? 14 : 1;
$len = $perm ? -1 : $len;
if ($should_ban == true) {
$privres = sprintf("banned %s %s: %s", $is_re?"regex":"string",htmlspecialchars($badlink),$priv);
if ($ban) {
$pub = str_replace('Error: ', '', $pub);
auto_ban_poster($name, $len, 1, $privres, $pub, true);
}
if (TEST_BOARD) $pub .= "
".$privres;
error($pub, $dest);
}
}
// this used to check the autobans table but now it just permabans
function auto_ban($name, $reason) {
auto_ban_poster($name, 7, 1, $reason);
}
function get_jpeg_dimensions($contents)
{
// this is faster than getimagesize
$i = 0;
$len = strlen($contents);
if( ord($contents{0}) == 0xFF & ord($contents{1}) == 0xD8 && ord($contents{2}) == 0xFF & ord($contents{3}) == 0xE0 ) {
$i = 4;
if( $contents{$i+2} == 'J' && $contents{$i+3} == 'F' && $contents{$i+4} == 'I' && $contents{$i+5} == 'F' && ord($contents{$i+6}) == 0x00 ) {
// valid image.
$block_length = ord($contents{$i}) * 256 + ord($contents{$i+1});
while( $i < $len ) {
$i += $block_length;
if( $i > $len ) {
return false;
}
if( ord($contents{$i}) != 0xFF ) {
return false;
}
if( ord($contents{$i+1}) == 0xC0 ) {
$width = ord($contents{$i+7})*256 + ord($contents{$i+8});
$height = ord($contents{$i+5})*256 + ord($contents{$i+6});
return array($width, $height);
} else {
$i+=2;
$block_length = ord($contents{$i}) * 256 + ord($contents{$i+1});
}
}
}
}
return false;
}
function file_too_big_for_type( $ext, $w, $h, $fsize )
{
if ($ext === ".gif" || $ext === ".pdf" || $ext === '.webm' || $ext === '.mp4') {
return NO;
}
$uncompressed_size = $w * $h * 4;
return ($fsize > (3*$uncompressed_size)) ? YES : NO;
}
function regex_ignoring_nulls($words)
{
$rwords = preg_replace("/./", "$0[^\\\\x01-\\\\xFF]*", $words);
$rwords = str_replace(".", "\\.", $rwords);
return "/".implode("|", $rwords)."/i";
}
/**
* Strips exif from JPEG images
* $file needs to be safe to use as shell argument
*/
function strip_jpeg_exif($file) {
return system("/usr/local/bin/jpegtran -copy none -outfile '$file' '$file'") !== false;
}
/**
* Strips non-whitelisted PNG chunks.
* Returns an error if an animated PNG is detected.
* Overwrites input file if modifications have been made.
* $file needs to be safe to use as shell argument
* Returns the number of chunks skipped or an error code (negative value).
*/
function strip_png_chunks($file, $max_chunk_len = 16 * 1024 * 1024) {
$keep_chunks = [
'ihdr',
'plte',
'idat',
'iend',
'trns',
'gama',
'sbit',
'phys',
'srgb',
'bkgd',
'time',
'chrm',
'iccp'
];
$img = fopen($file, 'rb');
if (!$img) {
return -9;
}
$data = fread($img, 8);
if ($data !== "\x89PNG\r\n\x1a\n") {
fclose($img);
return -1;
}
$output = '';
$skip_count = 0;
while (!feof($img)) {
$chunk_len_buf = fread($img, 4);
if (!$chunk_len_buf) {
break;
}
if (strlen($chunk_len_buf) !== 4) {
return -1;
}
$chunk_len = unpack('N', $chunk_len_buf)[1];
if ($chunk_len > $max_chunk_len) {
return -1;
}
$chunk_type_buf = fread($img, 4);
if (strlen($chunk_type_buf) !== 4) {
return -1;
}
$chunk_type = strtolower($chunk_type_buf);
// aPNG is not supported
if ($chunk_type === 'actl' || $chink_type === 'fctl' || $chink_type === 'fdat') {
return -2;
}
if (in_array($chunk_type, $keep_chunks)) {
if ($chunk_len > 0) {
$data = fread($img, $chunk_len);
if (strlen($data) !== $chunk_len) {
return -1;
}
}
else {
$data = '';
}
$crc = fread($img, 4);
if (strlen($crc) !== 4) {
return -1;
}
$output .= $chunk_len_buf . $chunk_type_buf . $data . $crc;
if ($chunk_type === 'iend') {
fread($img, 1);
if (!feof($img)) {
$skip_count++;
}
break;
}
}
else {
fseek($img, $chunk_len + 4, SEEK_CUR);
$skip_count++;
}
}
fclose($img);
if ($output === '') {
return -1;
}
if ($skip_count === 0) {
return 0;
}
$out_file = $file . '_pngtmp';
$out = fopen($out_file, 'wb');
if (!$out) {
return -9;
}
if (fwrite($out, "\x89PNG\r\n\x1a\n") === false) {
return -9;
}
if (fwrite($out, $output) === false) {
return -9;
}
fclose($out);
if (rename($out_file, $file) === false) {
return -9;
}
return $skip_count;
}
// Calculates the actual image data inside a JPEG file and errors out
// if it's smaller than the reported size.
function validate_jpeg_size($file, $reported_size) {
$eof = false;
$img = fopen($file, 'rb');
$data = fread($img, 2);
if ($data !== "\xff\xd8") {
fclose($img);
return false;
}
while (!feof($img)) {
$data = fread($img, 1);
if ($data !== "\xff") {
continue;
}
while (!feof($img)) {
$data = fread($img, 1);
if ($data !== "\xff") {
break;
}
}
if (feof($img)) {
break;
}
$byte = unpack('C', $data)[1];
if ($byte === 217) {
$eof = ftell($img);
break;
}
if ($byte === 0 || $byte === 1 || ($byte >= 208 && $byte <= 216)) {
continue;
}
$data = fread($img, 2);
$length = unpack('n', $data)[1];
if ($length < 1) {
break;
}
fseek($img, $length - 2, SEEK_CUR);
}
fclose($img);
// 50 KB
if ($reported_size - $eof >= 51200) {
error(S_IMGCONTAINSFILE, $file);
}
return $eof;
}
/**
* Checks for extensions and comments
* and calculates actual GIF data.
* Strips extra data if it exists.
* $file needs to be safe to use as shell argument.
*/
function strip_gif_extra_data($file, $reported_size) {
$binary = '/usr/local/bin/gifsicle';
$res = shell_exec("$binary --sinfo \"$file\" 2>&1");
if ($res !== null) {
$size = 0;
$need_strip = false;
if (preg_match('/ extensions [0-9]+| comment /', $res)) {
$need_strip = true;
}
else if (preg_match_all('/compressed size ([0-9]+)/', $res, $m)) {
foreach ($m[1] as $frame_size) {
$size += (int)$frame_size;
}
// Strip if 50+ KB of extra data is found
if ($reported_size - $size >= 51200) {
$need_strip = true;
}
}
if ($need_strip) {
if (system("$binary --no-comments --no-extensions \"$file\" -o \"$file\" >/dev/null 2>&1") === false) {
// gifsicle error
return -1;
}
else {
// file was modified
return 1;
}
}
}
else {
// gifsicle error
return -1;
}
// nothing changed
return 0;
}
// No longer used
function spam_filter_post_image($name, $dest, $md5, $upfile_name, $ext, $w, $h, $fsize)
{
if( $upfile_name == '' ) error('Blank file names are not supported.');
if (file_too_big_for_type($ext, $w, $h, $fsize) === YES) {
$lim = 3*4*$w*$h;
error(S_IMGCONTAINSFILE, $dest);
}
$img_bytes = file_get_contents($dest);
$img_beginning = strlen($img_bytes) > 0x50000 ? substr($img_bytes, 0, 0x40000).substr($img_bytes, -(0x10000)) : $img_bytes;
global $silent_reject;
$silent_reject = 0;
// protect against IE's retarded MIME-sniffing XSS vulnerability
// by doing our own sniffing and rejecting exploitable files
{
$negative_match = regex_ignoring_nulls(array("minitokyonet", "urchin.js"));
//except minitokyo from this, it causes false positives
if (preg_match($negative_match, $img_beginning)===0)
{
// '
DATE_SUB(NOW(), INTERVAL 1 HOUR) LIMIT 1
SQL;
$res = mysql_global_call($query);
if ($res && mysql_num_rows($res) === 1) {
return true;
}
$query = "INSERT INTO postfilter_hits (filter_id, board, long_ip) VALUES($filter_id, '%s', $long_ip)";
return mysql_global_call($query, BOARD_DIR);
}
function log_postfilter_hit($filter, $board, $thread_id, $name, $sub, $com, $upfile_name) {
$ip = $_SERVER['REMOTE_ADDR'];
$country = $_SERVER['HTTP_X_GEO_COUNTRY'];
$threat_score = spam_filter_get_threat_score($country, !$thread_id, true);
$meta = spam_filter_format_http_headers("$name\n$sub\n$com", $country, $upfile_name, $threat_score);
$action = "filter_{$filter['id']}";
$query = <<\/]+|>/', ' ', $sub . ' ' . $com . ' '. $name);
$normalized_com_sage = ucwords(strtolower($normalized_com_sage));
$normalized_com_sage = normalize_ascii($normalized_com_sage, 1);
}
$userpwd = UserPwd::getSession();
$matched_filter = false;
while ($filter = mysql_fetch_assoc($res)) {
// Counter mode: triggers when the number of matches is at least $min_count
$min_count = (int)$filter['min_count'];
if ($min_count < 1) {
$min_count = 1;
}
// Lenient filter
if ($filter['lenient']) {
if ($userpwd) {
if ($filter['updated_on']) {
$since_ts = (int)$filter['updated_on'];
}
else {
$since_ts = (int)$filter['created_on'];
}
if ($userpwd->isUserKnownOrVerified(60, $since_ts)) { // 1 hour
continue;
}
}
}
// OPs-only filter
if ($filter['ops_only'] && $resto) {
continue;
}
if ($filter['autosage']) {
// Autosage filter but the post is a reply
if ($resto) {
continue;
}
// Regex filter
if ($filter['regex']) {
if ($min_count > 1) {
if (preg_match_all($filter['pattern'], $expanded_com) >= $min_count) {
$matched_filter = $filter;
break;
}
}
else {
if (preg_match($filter['pattern'], $expanded_com) === 1) {
$matched_filter = $filter;
break;
}
}
}
// String filter for autosaging
else {
if ($min_count > 1) {
if (substr_count($normalized_com_sage, $filter['pattern']) >= $min_count) {
$matched_filter = $filter;
break;
}
}
else {
if (strpos($normalized_com_sage, $filter['pattern']) !== false) {
$matched_filter = $filter;
break;
}
}
}
}
// Regex filter
if ($filter['regex']) {
if ($min_count > 1) {
if (preg_match_all($filter['pattern'], $expanded_com) >= $min_count) {
$matched_filter = $filter;
break;
}
}
else {
if (preg_match($filter['pattern'], $expanded_com) === 1) {
$matched_filter = $filter;
break;
}
}
}
// String filter
else {
if ($min_count > 1) {
if (substr_count($normalized_com, $filter['pattern']) >= $min_count) {
$matched_filter = $filter;
break;
}
}
else {
if (strpos($normalized_com, $filter['pattern']) !== false) {
$matched_filter = $filter;
break;
}
}
}
}
if ($matched_filter !== false) {
// Update hit stats
register_postfilter_hit($matched_filter['id']);
// Autosage
if ($matched_filter['autosage']) {
return true;
}
// Log
else if ($matched_filter['log']) {
log_postfilter_hit($matched_filter, $board, $resto, $name, $sub, $com, $upfile_name);
}
// Reject
else {
if ($matched_filter['ban_days']) {
$err = S_BANNEDTEXT;
$ban_days = (int)$matched_filter['ban_days'];
$private_reason = 'banned string in comment (filter ID: ' . $matched_filter['id'] . ')';
$public_reason = $err;
auto_ban_poster($name, $ban_days, 1, $private_reason, $public_reason, true, $pwd, $pass_id);
}
else {
$err = S_REJECTTEXT;
}
if ($matched_filter['quiet']) {
show_post_successful_fake($resto);
die();
}
if (TEST_BOARD) {
$err .= ' (filter ID: ' . $matched_filter['id'] . ')';
}
error($err);
}
}
// Other
if ($sub !== '') {
$normalized_sub = normalize_text($sub);
if (stripos($sub, 'moot') !== false) {
error("You can't post with that subject.");
}
if (stripos($normalized_com, '##') !== false || stripos($sub, 'admin') !== false) {
error("You can't post with that subject.");
}
}
return false;
}
function isIPRangeBannedReport($long_ip, $asn, $board, $userpwd = null) {
return isIPRangeBanned($long_ip, $asn,
[
'board' => $board,
'is_report' => true,
'userpwd' => $userpwd,
]
);
}
// Checks if the IP is rangebanned
// options:
// board(string), is_sfw(bool, requires board)
// userpwd(UserPwd): instance of UserPwd or null,
// is_report(bool), is_op(bool), has_img(bool),
// browser_id(string),
// op_content(string): content of the thread OP for per-thread bans (unused)
// returns the rangeban database entry if the IP is banned, false otherwise
function isIPRangeBanned($long_ip, $asn, $options = []) {
$long_ip = (int)$long_ip;
$asn = (int)$asn;
$now = (int)$_SERVER['REQUEST_TIME'];
$cols = 'created_on, updated_on, expires_on, active, boards, ops_only, img_only, lenient, report_only, ua_ids';
$query = <<= $long_ip AND active = 1
AND (expires_on = 0 OR expires_on > $now))
SQL;
if ($asn > 0) {
$query .= << $now))
SQL;
}
$query .= ' ORDER BY lenient ASC';
$res = mysql_global_call($query);
if (!$res) {
return false;
}
// Parameters
if (isset($options['board'])) {
$board = $options['board'];
$is_sfw = isset($options['is_sfw']) && $options['is_sfw'];
}
else {
$board = null;
$is_sfw = false;
}
if (isset($options['browser_id'])) {
$browser_id = $options['browser_id'];
}
else {
$browser_id = null;
}
if (isset($options['req_sig'])) {
$req_sig = $options['req_sig'];
}
else {
$req_sig = null;
}
if (isset($options['op_content']) && $options['op_content'] !== '') {
$op_content = $options['op_content'];
}
else {
$op_content = null;
}
$is_op = isset($options['is_op']) && $options['is_op'];
$is_report = isset($options['is_report']) && $options['is_report'];
$has_img = isset($options['has_img']) && $options['has_img'];
if (isset($options['userpwd']) && $options['userpwd'] && $options['userpwd'] instanceof UserPwd) {
$userpwd = $options['userpwd'];
}
else {
$userpwd = null;
}
// OP-only and Image-only lenient rangebans also require a certain number of posts
$post_count_ok = $userpwd && $userpwd->postCount() >= 3 && ($userpwd->maskLifetime() > 900 || $userpwd->postCount() >= 15);
while ($range = mysql_fetch_assoc($res)) {
if ($range['boards']) {
if ($board === null) {
continue;
}
$board_matcher = ",{$range['boards']},";
if (strpos($board_matcher, ",$board,") === false) {
// _ws_ scope affects all work safe boards
if ($is_sfw) {
if (strpos($board_matcher, ",_ws_,") === false) {
continue;
}
}
else {
continue;
}
}
}
$post_count_check = true;
if ($range['report_only'] && !$is_report) {
continue;
}
if ($range['ops_only']) {
if (!$is_op) {
continue;
}
else {
$post_count_check = $post_count_ok;
}
}
if ($range['img_only']) {
if (!$has_img) {
continue;
}
else {
$post_count_check = $post_count_ok;
}
}
if ($range['ua_ids']) {
$_skip = true;
if ($browser_id && strpos($range['ua_ids'], $browser_id) !== false) {
$_skip = false;
}
if ($_skip && $req_sig && strpos($range['ua_ids'], $req_sig) !== false) {
$_skip = false;
}
if ($_skip) {
continue;
}
}
if ($userpwd && $range['lenient']) {
$lenient = (int)$range['lenient'];
if ($range['updated_on']) {
$since_ts = (int)$range['updated_on'];
}
else {
$since_ts = (int)$range['created_on'];
}
// Mode 1: Known 24h or Verified
if ($lenient === 1 && ($userpwd->verifiedLevel() || ($userpwd->isUserKnown(1440, $since_ts) && $post_count_check))) {
continue;
}
// Mode 2: Known 24h only
else if ($lenient === 2 && $userpwd->isUserKnown(1440, $since_ts) && $post_count_check) {
continue;
}
// Mode 3: Verified only
else if ($lenient === 3 && $userpwd->verifiedLevel()) {
continue;
}
}
return $range;
}
return false;
}
/**
* Checks if the IP has enough posting history
* $mode: 0 = check for replies, 1 = check for image replies, 2 = check of threads
* Caches results.
*/
function spam_filter_is_ip_known($long_ip, $board = null, $mode = 0, $minutes_min = 0, $posts_min = 1) {
static $cache = array();
$long_ip = (int)$long_ip;
if (!$long_ip) {
return false;
}
$cache_key = "$long_ip.$board.$mode.$minutes_min.$posts_min";
if (isset($cache[$cache_key])) {
return $cache[$cache_key];
}
// Not after (3 days)
$minutes_max = 4320;
// Not before
$minutes_min = (int)$minutes_min;
// At least X replies
$posts_min = (int)$posts_min;
// Board
if ($board) {
$board_clause = "AND board = '" . mysql_real_escape_string($board) . "'";
}
else {
$board_clause = '';
}
// Mode: 1 = image replies, 2 = threads, 0 = any reply
if ($mode === 1) {
$action_clause = "AND action = 'new_reply' AND had_image = 1";
}
else if ($mode === 2) {
$action_clause = "AND action = 'new_thread'";
}
else {
$action_clause = "AND action = 'new_reply'";
}
// Not before
if (!$minutes_min) {
$time_clause = "time >= DATE_SUB(NOW(), INTERVAL $minutes_max MINUTE)";
}
else {
$time_clause = "(time BETWEEN DATE_SUB(NOW(), INTERVAL $minutes_max MINUTE) AND DATE_SUB(NOW(), INTERVAL $minutes_min MINUTE))";
}
// Check posting history
$query = <<= DATE_SUB(NOW(), INTERVAL 48 HOUR)
SQL;
$res = mysql_global_call($query);
if (!$res) {
return false;
}
$count = (int)mysql_fetch_row($res)[0];
if ($count > 0) {
$cache[$cache_key] = false;
return false;
}
*/
$cache[$cache_key] = true;
return true;
}
/*
* Checks if the user has a valid posting history to bypass a rangeban (FIXME: deprecated)
*/
function spam_filter_is_user_known($long_ip, $board = null, $pwd = null, $minutes = 15, $count = 1) {
static $cache = array();
$pwd = null; // FIXME
$interval = (int)$minutes;
$cache_key_ip = "{$long_ip}:{$interval}";
if (isset($cache[$cache_key_ip])) {
return $cache[$cache_key_ip];
}
if ($pwd && $board) {
$cache_key_pwd = "{$board}:{$pwd}:{$interval}";
if (isset($cache[$cache_key_pwd])) {
return $cache[$cache_key_pwd];
}
}
$count = (int)$count;
if ($count < 1) {
$count = 1;
}
// Check the IP
$query = << BOARD_DIR,
'is_sfw' => DEFAULT_BURICHAN,
'userpwd' => $userpwd,
'is_op' => $thread_id == 0,
'has_img' => $has_img,
'browser_id' => $browser_id,
'req_sig' => $req_sig
];
if ($range = isIPRangeBanned($long_ip, $asn, $options)) {
if ($range['lenient'] && $userpwd) {
$userpwd->setCookie('.' . L::d(BOARD_DIR));
}
// Images only
if ($range['img_only']) {
$_err = S_IPRANGE_BLOCKED_IMG;
}
// Threads only
else if ($range['ops_only']) {
$_err = S_IPRANGE_BLOCKED_OP;
}
else {
$_err = S_IPRANGE_BLOCKED;
}
// Temporarily or Permanently
if ($range['expires_on'] || $range['lenient']) {
$_err .= ' ' . S_IPRANGE_BLOCKED_TEMP;
}
else {
$_err .= ' ' . S_IPRANGE_BLOCKED_PERM;
}
// Bypassed by verified or known users
if ($range['lenient'] == 1) {
$_err .= S_IPRANGE_BLOCKED_L1;
}
// Bypassed by known users only
else if ($range['lenient'] == 2) {
$_err .= S_IPRANGE_BLOCKED_L2;
}
// Bypassed by verified users only
else if ($range['lenient'] == 3) {
$_err .= S_IPRANGE_BLOCKED_L3;
}
// 4chan pass mention
$_err .= S_IPRANGE_BLOCKED_PASS;
error($_err);
}
// Auto-rangebans, Mobile only
// Bypassed by verified users or known users for at least 2h
// or users who have made at least one post on the board 15 minutes ago
if ($thread_id !== null && $browser_id[0] === '1') {
$since_ts = 0;
if ($userpwd) {
$user_known = $userpwd->isUserKnownOrVerified(120, 1);
$now = $_SERVER['REQUEST_TIME'];
if ($userpwd->postCount() > 0 && $userpwd->maskTs() <= $now - 900) {
$since_ts = $userpwd->maskTs();
}
}
else {
$user_known = false;
}
if (!$user_known) {
if (is_ip_auto_rangebanned($ip, BOARD_DIR, $thread_id, $browser_id, $since_ts)) {
write_to_event_log('auto_range_hit', $ip, [
'board' => BOARD_DIR,
'thread_id' => $thread_id,
'ua_sig' => $browser_id
]);
if ($userpwd) {
$userpwd->setCookie('.' . L::d(BOARD_DIR));
}
// Temporary, bypassablee by known or verified users
error(S_IPRANGE_BLOCKED . ' ' . S_IPRANGE_BLOCKED_TEMP . S_IPRANGE_BLOCKED_L1 . S_IPRANGE_BLOCKED_PASS);
}
}
}
return false;
}
function is_ip_auto_rangebanned($ip, $board, $thread_id, $browser_id, $since_ts = 0) {
$range_sql = explode('.', $ip);
$range_sql = "{$range_sql[0]}.{$range_sql[1]}.%";
$thread_id = (int)$thread_id;
if ($since_ts > 0) {
$since_sql = ' AND created_on <= FROM_UNIXTIME(' . ((int)$since_ts) . ')';
}
else {
$since_sql = '';
}
$sql =<< DATE_SUB(NOW(), INTERVAL 120 MINUTE)$since_sql
LIMIT 1
SQL;
$res = mysql_global_call($sql, $board, $browser_id, $range_sql);
if (!$res) {
return false;
}
if (mysql_num_rows($res)) {
return true;
}
return false;
}
/**
* Dumps and formats HTTP headers and other request information for logging
*/
function spam_filter_format_http_headers($com = null, $country = null, $filename = null, $threat_score = null, $req_sig = null) {
$bot_headers = '';
foreach ($_SERVER as $_h_name => $_h_val) {
if (substr($_h_name, 0, 5) == 'HTTP_') {
if ($_h_name === 'HTTP_COOKIE') {
$_cookies = array_keys($_COOKIE);
$_cookies = array_intersect($_cookies, ['ws_style', 'nws_style', '4chan_pass', '_tcs', '_ga', 'cf_clearance' ]);
$_cookie_count = count($_COOKIE);
$bot_headers .= "HTTP_COOKIE: " . htmlspecialchars(implode(', ', $_cookies)) . " ($_cookie_count in total)\n";
}
else if (strpos($_h_name, 'AUTH') !== false) {
continue;
}
else {
$bot_headers .= "$_h_name: " . htmlspecialchars($_h_val) . "\n";
}
}
}
$bot_headers .= "_POST: " . htmlspecialchars(implode(', ', array_keys($_POST))) . "\n";
if ($country !== null) {
$bot_headers .= "_Country: $country\n";
}
if ($threat_score !== null) {
$bot_headers .= "_Score: " . $threat_score . "\n";
}
if ($req_sig !== null) {
$bot_headers .= "_Sig: " . $req_sig . "\n";
}
if (isset($_COOKIE['_tcs'])) {
$bot_headers .= "_TCS: " . htmlspecialchars($_COOKIE['_tcs']) . "\n";
}
if (isset($_COOKIE['4chan_pass'])) {
$userpwd = UserPwd::getSession();
if ($userpwd) {
$bot_headers .= "_Pwd: " . htmlspecialchars($userpwd->getPwd()) . "\n";
}
}
if ($filename !== null) {
$bot_headers .= "_File: " . htmlspecialchars($filename) . "\n";
}
if ($com !== null) {
$bot_headers .= "_Comment: $com";
}
return $bot_headers;
}
function spam_filter_get_req_sig() {
static $cache = null;
if ($cache !== null) {
return $cache;
}
$pick_headers = [
'HTTP_SEC_CH_UA_PLATFORM',
'HTTP_SEC_CH_UA_MOBILE',
'HTTP_SEC_CH_UA_MODEL',
'HTTP_USER_AGENT',
'HTTP_ACCEPT_LANGUAGE',
'HTTP_SEC_FETCH_SITE',
'HTTP_SEC_FETCH_MODE',
'HTTP_SEC_FETCH_DEST',
];
$need_headers = [
'HTTP_USER_AGENT',
'HTTP_ACCEPT',
'HTTP_REFERER',
'HTTP_ACCEPT_LANGUAGE',
];
$datapoints = [];
$keys = [];
foreach ($_SERVER as $k => $v) {
if (in_array($k, $pick_headers)) {
$keys[] = $k;
}
}
$keyline = implode('.', $keys);
$pointlines = [
'fetch_smd' => 'HTTP_SEC_FETCH_SITE.HTTP_SEC_FETCH_MODE.HTTP_SEC_FETCH_DEST',
'fetch_dms' => 'HTTP_SEC_FETCH_DEST.HTTP_SEC_FETCH_MODE.HTTP_SEC_FETCH_SITE',
'fetch_any' => 'HTTP_SEC_FETCH_'
];
foreach ($pointlines as $key => $value) {
if (strpos($keyline, $value) !== false) {
$datapoints[] = $key;
}
}
if (isset($_SERVER['HTTP_SEC_GPC']) || isset($_SERVER['HTTP_DNT'])) {
$datapoints[] = 'dnt_gpc';
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
$datapoints[] = 'xrw';
}
if (preg_match('/HTTP_SEC_CH_UA_[^.]+\.HTTP_SEC_CH_UA_[^.]+\.HTTP_SEC_CH_UA_/', $keyline)) {
$datapoints[] = 'ch_ua_block';
}
foreach ($need_headers as $k) {
if (!isset($_SERVER[$k])) {
$datapoints[] = 'missing';
break;
}
}
$sig = implode('+', $datapoints);
if (!$sig) {
$sig = 'deadbeef';
}
$cache = substr(md5($sig), 0, 8);
return $cache;
}
// Covers 251044 (79.14 %) unique IPs and 10454 (77.09 %) unique bans
// IPs %: 0.1 | Bans %: 0 | GR1 all: 0 | Any EU: false
function spam_filter_is_asn_whitelisted() {
static $val = null;
static $whitelist = [
21928, 6167, 7922, 7018, 812, 701, 1221, 20115, 22773, 2856, 3320, 6805, 577,
3209, 852, 20057, 20001, 5089, 4804, 10796, 8151, 5617, 7545, 11427, 209, 16086,
719, 33363, 15557, 5650, 133612, 6327, 6128, 5607, 206067, 1267, 26599, 6830,
8881, 2119, 14593, 28573, 3215, 26615, 3352, 1759, 7303, 11426, 35228, 55836,
1136, 22085, 11351, 8708, 1257, 3269, 5410, 7418, 23693, 30722, 9299, 13285, 17676,
3301, 7713, 5391, 33915, 44034, 8374, 4764, 51207, 29447, 27651, 7552, 12389,
19108, 8359, 11315, 6057, 16591, 12479, 5769, 17072, 31615, 12271, 28403, 11664,
6147, 15704, 10139, 39603, 12874, 25135, 5483, 5378, 4771, 4775, 12912, 6871,
12322, 6614, 132199, 5432, 212238, 12929, 27699, 22927, 8473, 2860, 12430, 7029,
6848, 5645, 8412, 62240, 8447, 15502, 174, 30036, 27747, 14638, 4230, 45727, 9009,
17639, 4788, 10030, 11492, 45143, 18881, 2516, 21334, 15895, 4766, 16232, 6799,
8400, 9443, 6079, 13999, 5610, 45899, 4761, 2586, 12353, 20845, 20365, 13280,
22047, 3243, 4818, 27995, 20055, 24203, 9500, 25255, 45609, 29518, 7992, 39891,
3303, 4773, 855, 8452, 136787, 18403, 3329, 52341,
];
if ($val !== null) {
return $val;
}
if (isset($_SERVER['HTTP_X_GEO_ASN'])) {
$asn = (int)$_SERVER['HTTP_X_GEO_ASN'];
}
else {
$asn = 0;
}
if (!$asn) {
return true;
}
$val = in_array($asn, $whitelist);
return $val;
}
function spam_filter_is_bad_actor() {
static $cache = null;
if ($cache !== null) {
return $cache;
}
/*
if (isset($_SERVER['HTTP_X_HTTP_VERSION'])) {
if (strpos($_SERVER['HTTP_X_HTTP_VERSION'], 'HTTP/1') === 0) {
$cache = true;
return true;
}
}
*/
$no_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) === false;
$no_accept = isset($_SERVER['HTTP_ACCEPT']) === false;
if ($no_lang && $no_accept) {
$cache = true;
return true;
}
if ($no_lang && strpos($_SERVER['HTTP_USER_AGENT'], '; wv)') !== false) {
$cache = true;
return true;
}
if ($no_accept && strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false) {
$cache = true;
return true;
}
if ($no_lang && isset($_SERVER['HTTP_REFERER'])) {
$ref = $_SERVER['HTTP_REFERER'];
if (strpos($ref, 'sys.4chan.org') !== false || strpos($ref, '/thread/') !== false) {
$cache = true;
return true;
}
}
$cache = false;
return false;
}
function spam_filter_get_threat_score($country = null, $is_op = false, $multipart = true/*, &$log = []*/) {
$increase = [];
$more = [];
$domain = DEFAULT_BURICHAN ? '4channel' : '4chan';
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$ua = $_SERVER['HTTP_USER_AGENT'];
}
else {
$ua = '';
}
if (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
$content_type = $_SERVER['HTTP_CONTENT_TYPE'];
}
else {
$content_type = '';
}
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
else {
$accept_lang = '';
}
if (isset($_SERVER['HTTP_ACCEPT'])) {
$accept_header = $_SERVER['HTTP_ACCEPT'];
}
else {
$accept_header = '';
}
if (isset($_SERVER['HTTP_REFERER'])) {
$referer_header = $_SERVER['HTTP_REFERER'];
}
else {
$referer_header = '';
}
$header_keys = array_keys($_SERVER);
$ua_is_webkit = false;
$check_for_sec_headers = false;
$is_mobile_ua = preg_match('/Android|Mobile/', $ua) || $_SERVER['HTTP_SEC_CH_UA_MOBILE'] === '?1';
$is_brave = false;
if (isset($_SERVER['HTTP_SEC_CH_UA']) && strpos($_SERVER['HTTP_SEC_CH_UA'], 'Brave') !== false) {
if (isset($_SERVER['HTTP_SEC_GPC'])) {
$is_brave = true;
}
}
// Mobile app (webviews, etc)
$is_webview = strpos($ua, '; wv') !== false;
$is_mobile_app = !$accept_header && !$accept_lang && ($is_webview || strpos($referer_header, '/thread/') !== false);
if (!$is_mobile_app && !$accept_header && $accept_lang && strpos($referer_header, 'sys.4chan.org') !== false) {
$is_mobile_app = true;
}
if (!$is_mobile_app && strpos($ua, 'Mozilla/') === false && preg_match('/Android|Dalvik|iOS|iPhone/', $ua)) {
$is_mobile_app = true;
}
if (!$is_mobile_app && isset($_SERVER['HTTP_X_REQUESTED_WITH']) && preg_match('/floens|adamantcheese|clover/', $_SERVER['HTTP_X_REQUESTED_WITH'])) {
$is_mobile_app = true;
}
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)) {
if (strpos($ua, 'Android') !== false) {
$is_mobile_app = true;
}
else if (strpos($ua, 'Firefox/') !== false && !$accept_lang) {
$is_mobile_app = true;
}
}
// No UA
if (!$ua) {
$increase[] = 0.25;
//$log[] = 'NO_UA';
}
// Firefox
else if ((strpos($ua, 'Firefox/') !== false || strpos($ua, 'FxiOS/') !== false) && strpos($ua, 'WebKit') === false) {
// Suspicious Content-Type
if ($multipart && !$is_mobile_app && !preg_match('/=-+([0-9]+|geckoformboundary[a-f0-9]+)$/i', $content_type)) {
$increase[] = 0.25;
//$log[] = 'BAD_CT_FF';
}
// Suspicious language
if (!$accept_lang) {
if (!$is_mobile_app) {
$increase[] = 0.02;
$more[] = 0.1;
//$log[] = 'NO_LANG';
}
}
else if (preg_match('/[a-z]-[a-z]/', $accept_lang)) {
$increase[] = 0.1;
//$log[] = 'LC_LANG';
}
if (isset($_SERVER['HTTP_PRAGMA']) && !isset($_SERVER['HTTP_CACHE_CONTROL'])) {
$increase[] = 0.35;
$more[] = 0.1;
//$log[] = 'FF_PRAGMA';
}
// Wrong Accept header
if (strpos($accept_header, 'application/signed-exchange') !== false) {
$increase[] = 0.15;
//$log[] = 'FF_SIGEX';
}
// Old and spoofed versions
if ($accept_header && preg_match('/(?:Firefox)\/([0-9]+)[^0-9]/', $ua, $m) && strpos($ua, 'PaleMoon') === false) {
$v = (int)$m[1];
if ($v < 52) {
$increase[] = 0.2;
//$log[] = 'OLD_FF';
}
else if ($v < 60) {
$increase[] = 0.1;
//$log[] = 'OLD_FF';
}
else if ($v < 78) {
$increase[] = 0.01;
//$log[] = 'OLD_FF';
}
else if ($v > 500) {
$increase[] = 0.5;
//$log[] = 'FUTURE_FF';
}
if ($v > 110) {
$check_for_sec_headers = true;
}
}
}
// Webkit
else if (strpos($ua, 'WebKit') !== false) {
$ua_is_webkit = true;
$ua_is_chrome = strpos($ua, 'Chrome') !== false;
// Suspicious Content-Type
if ($multipart && !$is_mobile_app) {
if (!strpos($content_type, 'WebKit')) {
$increase[] = 0.25;
//$log[] = 'BAD_CT_WK';
}
else if (strpos($content_type, '-') === false) {
$increase[] = 0.50;
//$log[] = 'BAD_CT_DASH';
}
}
// Suspicious language
if (!$accept_lang) {
if (!$is_mobile_app) {
$increase[] = 0.02;
$more[] = 0.1;
//$log[] = 'NO_LANG';
}
}
else if ($ua_is_chrome && strpos($ua, 'Android') === false && preg_match('/[a-z]-[a-z]/', $accept_lang)) {
$increase[] = 0.1;
//$log[] = 'LC_LANG';
}
// Old and spoofed versions
if (preg_match('/(?:Chrome)\/([0-9]+)[^0-9]/', $ua, $m)) {
$v = (int)$m[1];
if ($v < 60) {
$increase[] = 0.2;
//$log[] = 'OLD_WK';
}
else if ($v < 70) {
$increase[] = 0.1;
//$log[] = 'OLD_WK';
}
else if ($v < 80) {
$increase[] = 0.05;
//$log[] = 'OLD_WK';
}
else if ($v > 500) {
$increase[] = 0.5;
//$log[] = 'FUTURE_WK';
}
if ($v > 110) {
$check_for_sec_headers = true;
}
}
if (preg_match('/(?:Safari)\/([0-9]+)/', $ua, $m)) {
$v = (int)$m[1];
if ($v < 530) {
$increase[] = 0.5;
//$log[] = 'OLD_SAFARI';
}
}
// iPhone UA too short
if (strpos($ua, 'iPhone') !== false && strpos($ua, 'Mobile') === false) {
$increase[] = 0.06;
//$log[] = 'SHORT_IPHONE';
}
}
// Other
else {
if (!$is_mobile_app && $multipart && preg_match('/boundary=[a-zA-Z0-9]+$/', $content_type)) {
$increase[] = 0.5;
//$log[] = 'STRANGE_CT';
}
if (preg_match('/Netscape\/|Opera\b|Camino\/|Trident\/|Presto\/|compatible; MSIE /', $ua)) {
$increase[] = 0.75;
//$log[] = 'OLD_UA';
}
else if (!$is_mobile_app && strpos($ua, 'Mozilla/') === false) {
$increase[] = 0.15;
$more[] = 0.1;
//$log[] = 'STRANGE_UA';
}
if (!$is_mobile_app && !$accept_lang) {
$more[] = 0.25;
//$log[] = 'NO_LANG';
}
// UA too short
if (!$is_mobile_app && (strlen($ua) < 25 || strpos($ua, ' ') === false)) {
$increase[] = 0.25;
//$log[] = 'UA_SPOOF';
}
}
// Suspicious Content-Type
if ($multipart) {
if (strpos($content_type, 'WebKit') && !$ua_is_webkit) {
$increase[] = 0.25;
//$log[] = 'BAD_UA_CT_WK';
}
}
// Sec-Fetch headers should be together
// Some iPhones have those separated
if (!$is_brave && !$is_webview && strpos($ua, 'Chrome') !== false) {
$_sf_start = false;
$_sf_end = false;
foreach ($_SERVER as $_hdr => $_value) {
if (strpos($_hdr, 'HTTP_SEC_FETCH_') === 0) {
if ($_sf_start && $_sf_end) {
$increase[] = 0.25;
//$log[] = 'SPARSE_SEC_FETCH';
break;
}
$_sf_start = true;
}
else if ($_sf_start) {
$_sf_end = true;
}
}
}
// HTTP_SEC_FETCH_USER should always be ?1
if (isset($_SERVER['HTTP_SEC_FETCH_USER']) && $_SERVER['HTTP_SEC_FETCH_USER'] !== '?1') {
$increase[] = 0.15;
//$log[] = 'BAD_SEC_FU';
}
// Unusual Accept header
if ($accept_header) {
if (strpos($accept_header, 'text/plain') !== false) {
$increase[] = 0.05;
//$log[] = 'ACCEPT_TP';
}
}
// Referer is set but is empty
if (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], '4chan.org') === false) {
$increase[] = 0.1;
//$log[] = 'BAD_REFERER';
}
// Platform mismatch: client hints vs user agent
if (isset($_SERVER['HTTP_SEC_CH_UA_PLATFORM']) && $ua) {
$_ch_platform = $_SERVER['HTTP_SEC_CH_UA_PLATFORM'];
if (strpos($ua, 'Windows') !== false) {
if (strpos($_ch_platform, 'Windows') === false) {
$increase[] = 0.5;
//$log[] = 'CH_BAD_PLATFORM';
}
}
else if (strpos($ua, 'Mac OS') !== false) {
if (strpos($_ch_platform, 'macOS') === false) {
$increase[] = 0.5;
//$log[] = 'CH_BAD_PLATFORM';
}
}
else if (strpos($ua, 'Linux') !== false) {
if (preg_match('/Linux|Android|BSD/', $_ch_platform) === false) {
$increase[] = 0.5;
//$log[] = 'CH_BAD_PLATFORM';
}
}
}
if (isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && $accept_header === '*/*') {
$increase[] = 0.2;
$more[] = 0.1;
//$log[] = 'ACCEPT_UIR';
}
if (!isset($_SERVER['HTTP_PRAGMA']) && strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') === false) {
$increase[] = 0.09;
//$log[] = 'BAD_IPHONE_WV';
}
// Suspicious OS
if (strpos($ua, 'Windows NT ') !== false) {
if (preg_match('/Windows NT ([0-9]+)/', $ua, $m) && strpos($ua, 'Mypal/') === false) {
$v = (int)$m[1];
if ($v < 6) {
if (strpos($ua, 'Goanna') === false) {
$increase[] = 0.25;
}
else {
$increase[] = 0.03;
}
//$log[] = 'OLD_WIN';
}
else if ($v < 10) {
$increase[] = 0.03;
//$log[] = 'OLD_WIN';
}
else if ($v > 10) {
$increase[] = 0.5;
//$log[] = 'FUTURE_WIN';
}
}
}
else if (strpos($ua, 'Mac OS X ') !== false) {
if (preg_match('/Mac OS X ([0-9]+)_([0-9]+)/', $ua, $m)) {
$v_maj = (int)$m[1];
$v_min = (int)$m[2];
if ($v_maj < 10) {
$increase[] = 0.5;
//$log[] = 'OLD_OSX';
}
else if ($v_maj == 10) {
if ($v_min < 7) {
$increase[] = 0.25;
//$log[] = 'OLD_OSX';
}
else if ($v_min < 12) {
$increase[] = 0.05;
//$log[] = 'OLD_OSX';
}
}
else if ($v_maj > 10 && strpos($ua, 'Safari') !== false) {
$increase[] = 0.30;
//$log[] = 'FUTURE_OSX';
}
}
}
else if (strpos($ua, 'Android') !== false) {
if (preg_match('/Android ([0-9]+)/', $ua, $m)) {
$v = (int)$m[1];
if ($v < 4) {
$increase[] = 0.25;
//$log[] = 'OLD_DROID';
}
else if ($v < 8) {
$increase[] = 0.05;
//$log[] = 'OLD_DROID';
}
else if ($v > 20) {
$increase[] = 0.5;
//$log[] = 'FUTURE_DROID';
}
}
if (strpos($ua, 'Win64;') !== false) {
$increase[] = 0.25;
//$log[] = 'OS_SOUP';
}
}
// Spoofed OS
if (preg_match('/Mozilla|Firefox|Chrome/', $ua) && !preg_match('/Windows NT|Android|Linux|Mac|iOS|X11;|BSD|Nintendo|PlayStation|Steam/', $ua)) {
$increase[] = 0.20;
//$log[] = 'NO_OS';
}
// Non-browser user agents
if (preg_match('/headless|node-fetch|python-|java\/|jakarta|-perl|http-?client|-resty-|awesomium\//i', $ua)) {
$increase[] = 1.0;
//$log[] = 'NOT_BROWSER';
}
// Wrong content type
if ($multipart) {
// Posting
if ($_SERVER['HTTP_CONTENT_TYPE'] === 'application/x-www-form-urlencoded') {
$increase[] = 0.75;
//$log[] = 'BAD_CT_MP';
}
}
else if (!$is_mobile_app) {
// Reporting
if ($_SERVER['HTTP_CONTENT_TYPE'] !== 'application/x-www-form-urlencoded') {
$increase[] = 0.75;
//$log[] = 'BAD_CT_NMP';
}
}
// Unusual headers
if (isset($_SERVER['HTTP_VARY'])) {
$increase[] = 0.2;
//$log[] = 'VARY_HDR';
}
if (isset($_SERVER['HTTP_PATH']) || isset($_SERVER['HTTP_SAME_ORIGIN']) || isset($_SERVER['HTTP_REFERRER_POLICY'])) {
$increase[] = 0.8;
//$log[] = 'USELESS_HDR';
}
if (!$is_mobile_app && !$is_webview) {
if (isset($_SERVER['HTTP_SEC_FETCH_MODE']) && $_SERVER['HTTP_SEC_FETCH_MODE'] === 'navigate') {
// Only threads should be posted using the default form
if (!$is_op && !$is_mobile_ua) {
$increase[] = 0.02;
$more[] = 0.10;
//$log[] = 'BAD_OP_SFM';
}
// Model hints are never sent when using the default form
if (isset($_SERVER['HTTP_SEC_CH_UA_MODEL']) && !isset($_SERVER['HTTP_SEC_CH_UA_BITNESS'])) {
$increase[] = 0.1;
$more[] = 0.20;
//$log[] = 'MODEL_NAV';
}
}
}
// iPhone fetch site none
if (strpos($ua, 'like Mac OS') && isset($_SERVER['HTTP_SEC_FETCH_SITE']) && $_SERVER['HTTP_SEC_FETCH_SITE'] === 'none') {
$increase[] = 0.08;
$more[] = 0.10;
//$log[] = 'IOS_FSN';
}
// No cookies
if (!$is_mobile_app) {
if (!isset($_SERVER['HTTP_COOKIE']) || !$_SERVER['HTTP_COOKIE']) {
$increase[] = 0.05;
$more[] = 0.25;
//$log[] = 'NO_COOKIE';
}
else if (count($_COOKIE) === 1 && isset($_COOKIE['cf_clearance'])) {
$increase[] = 0.05;
$more[] = 0.25;
//$log[] = 'NO_COOKIE';
}
}
// Timezones and Time
if (isset($_COOKIE['_tcs'])) {
list($_time, $_tz, $_time_s, $_tcs_v) = explode('.', $_COOKIE['_tcs']);
if (!$_tcs_v) {
$increase[] = 0.09;
//$log[] = 'BAD_TCS';
}
else {
if (!$is_webview && strpos($ua, 'Chrome/') !== false && $_tcs_v != 33) {
$increase[] = 0.09;
//$log[] = 'BAD_TCS_CR';
}
}
if ($_time_s && isset($_POST['t-challenge']) && $_POST['t-challenge'] !== 'noop' && $_POST['t-response']) {
$_d = $_SERVER['REQUEST_TIME'] - $_time_s;
if ($_d > 0 && $_d < 2) {
$increase[] = 1.0;
//$log[] = 'FAST_TCS';
}
}
if (isset($_SERVER['HTTP_X_TIMEZONE'])) {
$_tz0 = explode('/', $_tz, 2)[0];
if ($_tz0) {
if ($_tz0 === 'UTC') {
$increase[] = 0.02;
$more[] = 0.02;
//$log[] = 'UTC_TZ';
}
else if ($_tz0 === 'Etc') {
$increase[] = 0.01;
$more[] = 0.01;
//$log[] = 'ETC_TZ';
}
else if (strpos($_SERVER['HTTP_X_TIMEZONE'], $_tz0) !== 0) {
if (strpos($_tz0, 'Atlantic') === false || strpos($_SERVER['HTTP_X_TIMEZONE'], 'Europe') === false) {
$increase[] = 0.03;
$more[] = 0.03;
//$log[] = 'BAD_TZ';
}
}
}
}
}
// No Accept
if (!$accept_header && !$is_mobile_app) {
$increase[] = 0.15;
//$log[] = 'NO_ACCEPT';
}
// No SEC
if ($check_for_sec_headers && !$is_mobile_app) {
if (!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || !isset($_SERVER['HTTP_SEC_FETCH_MODE']) || !isset($_SERVER['HTTP_SEC_FETCH_SITE'])) {
$increase[] = 0.15;
//$log[] = 'NO_SEC';
}
}
// HTTP 1.1
if (isset($_SERVER['HTTP_X_HTTP_VERSION']) && $_SERVER['HTTP_X_HTTP_VERSION'] === 'HTTP/1.1') {
$more[] = 0.1;
//$log[] = 'HTTP1';
}
// Spoofed device model
if (isset($_SERVER['HTTP_SEC_CH_UA_MODEL']) && isset($_SERVER['HTTP_SEC_CH_UA_PLATFORM'])) {
$_k1 = (int)array_search('HTTP_SEC_CH_UA_MODEL', $header_keys);
$_k2 = (int)array_search('HTTP_SEC_CH_UA_PLATFORM', $header_keys);
if (abs($_k1 - $_k2) > 5) {
$increase[] = 0.05;
$more[] = 0.01;
//$log[] = 'FAR_MODEL';
}
}
if ($country) {
// Language is set but doesn't match the country
if ($accept_lang && strpos($accept_lang, 'en') !== 0) {
$lang_regex = get_lang_regex_from_country($country);
if ($lang_regex && !preg_match($lang_regex, $accept_lang)) {
$more[] = 0.025;
$increase[] = 0.025;
//$log[] = 'LANG_MISMATCH';
}
// No quality in lang
if (!preg_match('/iPhone|iPad/', $ua)) {
if ($accept_lang && strpos($accept_lang, ';') === false) {
$more[] = 0.025;
$increase[] = 0.025;
//$log[] = 'LANG_NOQ';
}
}
}
// Highly suspicious countries
$countries_0 = array(
'AD','AE','AF','AG','AI','AL','AM','AN','AO','AQ','AS','AW','AX','AZ',
'BB','BD','BF','BH','BI','BJ','BL','BM','BN','BO','BQ','BS','BT','BV','BW','BZ',
'CC','CD','CF','CG','CI','CK','CM','CN','CR','CU','CV','CW','CX',
'DJ','DM','DO','DZ',
'EC','EG','EH','ER','ET',
'FJ','FK','FM','FO',
'GA','GD','GF','GG','GH','GI','GM','GN','GP','GQ','GS','GT','GU','GW','GY',
'HK','HM','HN','HT',
'IM','IO','IQ','IR','JE','JM','JO','KE','KG','KH','KI','KM','KN','KP','KW','KY','KZ',
'LA','LB','LC','LK','LR','LS','LY',
'MA','MD','MF','MG','MH','ML','MM','MN','MO','MP','MQ','MR','MS','MU','MV','MW','MZ',
'NA','NC','NE','NF','NG','NI','NP','NR','NU',
'OM','PA','PF','PG','PK','PM','PN','PS','PW','PY','QA','RE','RW',
'SA','SB','SC','SD','SH','SJ','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ',
'TC','TD','TF','TG','TJ','TK','TM','TN','TO','TP','TR','TT','TV','TZ',
'UG','UM','UZ','VA','VC','VG','VI','VU','WF','WS','YE','YT','YU','ZM','ZW','XX'
);
// Less suspicious countries
$countries_1 = array(
'BR','VE','AR','CL','UY','CO','PE','MX',
'UA','BA','RU','MC','MK','CY','MT','ME','KR','JP','TH','VN','ID'
);
if (in_array($country, $countries_0)) {
$more[] = 0.30;
//$log[] = 'SUSP_COUNTRY_0';
}
else if (in_array($country, $countries_1)) {
$more[] = 0.10;
//$log[] = 'SUSP_COUNTRY_1';
}
}
$score = 0.0;
if (!empty($increase)) {
$score += array_sum($increase);
}
if ($score > 0.0 && !empty($more)) {
foreach ($more as $r) {
$score *= (1.0 + $r);
}
}
return round($score, 2);
}
/**
* Necrobumping prevention checks
*/
function spam_filter_can_bump_thread($thread_root) {
$userpwd = UserPwd::getSession();
if (!$userpwd || !$thread_root) {
return true;
}
if ($userpwd->maskLifetime() >= 21600) { // 6 hours
return true;
}
$thres = (int)(PAGE_MAX * DEF_PAGES * 0.85);
if ($thres <= 0) {
return true;
}
$sql = "SELECT COUNT(*) FROM `%s` WHERE resto = 0 AND archived = 0 AND root > '%s'";
$res = mysql_board_call($sql, BOARD_DIR, $thread_root);
if (!$res) {
return true;
}
$pos = (int)mysql_fetch_row($res)[0];
if ($pos < $thres) {
return true;
}
// Check the IP if cookies are blocked
if (spam_filter_is_ip_known(ip2long($_SERVER['REMOTE_ADDR']), BOARD_DIR, 0, 720)) {
return true;
}
return false;
}
/**
* Returns true if the uploaded file had multiple previous bans for it
* and should be blocked
*/
function check_for_banned_upload($md5) {
if (!$md5 || BOARD_DIR === 'b') {
return false;
}
// 6 : Global 5 - NWS on Worksafe Board
// 226 : Global 3 - Loli/shota pornography
$template_clause = '226';
if (DEFAULT_BURICHAN) {
$template_clause .= ',6';
}
$sql = <<= 3) {
return true;
}
return false;
}
function spam_filter_is_likely_automated($memcached = null, $threshold = 29) {
if (!isset($_SERVER['HTTP_X_BOT_SCORE'])) {
return false;
}
$ua = $_SERVER['HTTP_USER_AGENT'];
// Skip Android Webviews
if (strpos($ua, '; wv)') !== false) {
return false;
}
// Skip iPhone Webviews
if (preg_match('/iPhone|iPad/', $ua) && !preg_match('/Mobile|Safari/', $ua)) {
return false;
}
$score = (int)$_SERVER['HTTP_X_BOT_SCORE'];
if ($score > 0 && $score <= $threshold) {
return true;
}
if ($memcached) {
$key = 'bmbot' . $_SERVER['REMOTE_ADDR'];
if ($memcached->get($key)) {
return true;
}
}
return false;
}
function spam_filter_is_pwd_blocked($pwd, $type, $hours = 24) {
if (!$pwd || !$type || $hours <= 0 || $hours > 720) {
return false;
}
$hours = (int)$hours;
$sql =<< DATE_SUB(NOW(), INTERVAL $hours HOUR)
LIMIT 1
SQL;
$res = mysql_global_call($sql, $type, $pwd);
if (!$res) {
return false;
}
return mysql_num_rows($res) === 1;
}
function spam_filter_has_country_changed($pwd) {
if (!$pwd) {
return false;
}
$sql =<< DATE_SUB(NOW(), INTERVAL 24 HOUR)
LIMIT 1
SQL;
$res = mysql_global_call($sql, $pwd);
if (!$res) {
return false;
}
return mysql_num_rows($res) === 1;
}
// Logs posts made by new users.
// Returns 1 if the posting rate is above limits.
function spam_filter_is_post_flood($ip, $userpwd, $board, $thread_id, $phash) {
$user_is_known = $userpwd && $userpwd->isUserKnownOrVerified(60);
if ($user_is_known) {
return 0;
}
$thread_id = (int)$thread_id;
// Per thread reply flood
if ($thread_id) {
$interval_minutes = (int)ANTIFLOOD_INTERVAL_REPLY;
$threshold = (int)ANTIFLOOD_THRES_REPLY;
}
// OP flood
else {
$interval_minutes = (int)ANTIFLOOD_INTERVAL_OP;
$threshold = (int)ANTIFLOOD_THRES_OP;
}
if (!$interval_minutes || !$threshold) {
return 0;
}
$long_ip = ip2long($ip);
if (!$long_ip) {
return 0;
}
$tbl = 'flood_log';
// Prune old entries
$sql = "DELETE FROM `$tbl` WHERE created_on < DATE_SUB(NOW(), INTERVAL 24 HOUR)";
mysql_global_call($sql);
// Count flood entries
$ret_val = 0;
$sql = <<= DATE_SUB(NOW(), INTERVAL $interval_minutes MINUTE)
AND board = '%s'
AND thread_id = $thread_id
SQL;
$res = mysql_global_call($sql, $ip, $board);
if ($res) {
$count = (int)mysql_fetch_row($res)[0];
if ($count >= $threshold) {
$ret_val = 1;
}
}
// Insert new entry
$ua_sig = spam_filter_get_browser_id();
$req_sig = spam_filter_get_req_sig();
$sql = << '/\b(?:ca)\b/',
'AE' => '/\b(?:ar|fa|hi|ur)\b/',
'AF' => '/\b(?:fa|ps|uz|tk)\b/',
'AL' => '/\b(?:sq|el)\b/',
'AM' => '/\b(?:hy)\b/',
'AO' => '/\b(?:pt)\b/',
'AR' => '/\b(?:es|it|de|fr|gn)\b/',
'AS' => '/\b(?:sm|to)\b/',
'AT' => '/\b(?:de|hr|hu|sl)\b/',
'AW' => '/\b(?:nl|pap|es)\b/',
'AX' => '/\b(?:sv)\b/',
'AZ' => '/\b(?:az|ru|hy)\b/',
'BA' => '/\b(?:bs|hr|sr)\b/',
'BD' => '/\b(?:bn)\b/',
'BE' => '/\b(?:nl|fr|de)\b/',
'BF' => '/\b(?:fr|mos)\b/',
'BG' => '/\b(?:bg|tr|rom)\b/',
'BH' => '/\b(?:ar|fa|ur)\b/',
'BI' => '/\b(?:fr|rn)\b/',
'BJ' => '/\b(?:fr)\b/',
'BL' => '/\b(?:fr)\b/',
'BM' => '/\b(?:pt)\b/',
'BN' => '/\b(?:ms)\b/',
'BO' => '/\b(?:es|qu|ay)\b/',
'BQ' => '/\b(?:nl|pap)\b/',
'BR' => '/\b(?:pt|es|fr)\b/',
'BT' => '/\b(?:dz)\b/',
'BW' => '/\b(?:tn)\b/',
'BY' => '/\b(?:be|ru)\b/',
'BZ' => '/\b(?:es)\b/',
'CA' => '/\b(?:fr|iu)\b/',
'CC' => '/\b(?:ms)\b/',
'CD' => '/\b(?:fr|ln|ktu|kg|sw|lua)\b/',
'CF' => '/\b(?:fr|sg|ln|kg)\b/',
'CG' => '/\b(?:fr|kg|ln)\b/',
'CH' => '/\b(?:de|fr|it|rm)\b/',
'CI' => '/\b(?:fr)\b/',
'CK' => '/\b(?:mi)\b/',
'CL' => '/\b(?:es)\b/',
'CM' => '/\b(?:fr)\b/',
'CN' => '/\b(?:zh|yue|wuu|dta|ug|za)\b/',
'CO' => '/\b(?:es)\b/',
'CR' => '/\b(?:es)\b/',
'CU' => '/\b(?:es|pap)\b/',
'CV' => '/\b(?:pt)\b/',
'CW' => '/\b(?:nl|pap)\b/',
'CX' => '/\b(?:zh|ms)\b/',
'CY' => '/\b(?:el|tr)\b/',
'CZ' => '/\b(?:cs|sk)\b/',
'DE' => '/\b(?:de)\b/',
'DJ' => '/\b(?:fr|ar|so|aa)\b/',
'DK' => '/\b(?:da|fo|de)\b/',
'DO' => '/\b(?:es)\b/',
'DZ' => '/\b(?:ar|fr)\b/',
'EC' => '/\b(?:es)\b/',
'EE' => '/\b(?:et|ru)\b/',
'EG' => '/\b(?:ar|fr)\b/',
'EH' => '/\b(?:ar|mey)\b/',
'ER' => '/\b(?:aa|ar|tig|kun|ti)\b/',
'ES' => '/\b(?:es|ca|gl|eu|oc)\b/',
'ET' => '/\b(?:am|om|ti|so|sid)\b/',
'FI' => '/\b(?:fi|sv|smn)\b/',
'FJ' => '/\b(?:fj)\b/',
'FM' => '/\b(?:chk|pon|yap|kos|uli|woe|nkr|kpg)\b/',
'FO' => '/\b(?:fo|da)\b/',
'FR' => '/\b(?:fr|frp|br|co|ca|eu|oc)\b/',
'GA' => '/\b(?:fr)\b/',
'GB' => '/\b(?:en)\b/',
'GE' => '/\b(?:ka|ru|hy|az)\b/',
'GF' => '/\b(?:fr)\b/',
'GG' => '/\b(?:nrf)\b/',
'GH' => '/\b(?:ak|ee|tw)\b/',
'GI' => '/\b(?:es|it|pt)\b/',
'GL' => '/\b(?:kl|da)\b/',
'GM' => '/\b(?:mnk|wof|wo|ff)\b/',
'GN' => '/\b(?:fr)\b/',
'GP' => '/\b(?:fr)\b/',
'GQ' => '/\b(?:es|fr)\b/',
'GR' => '/\b(?:el|fr)\b/',
'GT' => '/\b(?:es)\b/',
'GU' => '/\b(?:ch)\b/',
'GW' => '/\b(?:pt|pov)\b/',
'HK' => '/\b(?:zh|yue|zh)\b/',
'HN' => '/\b(?:es|cab|miq)\b/',
'HR' => '/\b(?:hr|sr)\b/',
'HT' => '/\b(?:ht|fr)\b/',
'HU' => '/\b(?:hu)\b/',
'ID' => '/\b(?:id|nl|jv)\b/',
'IE' => '/\b(?:ga)\b/',
'IL' => '/\b(?:he|ar)\b/',
'IM' => '/\b(?:gv)\b/',
'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/',
'IQ' => '/\b(?:ar|ku|hy)\b/',
'IR' => '/\b(?:fa|ku)\b/',
'IS' => '/\b(?:is|de|da|sv|no)\b/',
'IT' => '/\b(?:it|de|fr|sc|ca|co|sl)\b/',
'JE' => '/\b(?:fr|nrf)\b/',
'JO' => '/\b(?:ar)\b/',
'JP' => '/\b(?:ja)\b/',
'KE' => '/\b(?:sw)\b/',
'KG' => '/\b(?:ky|uz|ru)\b/',
'KH' => '/\b(?:km|fr)\b/',
'KI' => '/\b(?:gil)\b/',
'KM' => '/\b(?:ar|fr)\b/',
'KP' => '/\b(?:ko)\b/',
'KR' => '/\b(?:ko)\b/',
'XK' => '/\b(?:sq|sr)\b/',
'KW' => '/\b(?:ar)\b/',
'KZ' => '/\b(?:kk|ru)\b/',
'LA' => '/\b(?:lo|fr)\b/',
'LB' => '/\b(?:ar|fr|hy)\b/',
'LI' => '/\b(?:de)\b/',
'LK' => '/\b(?:si|ta)\b/',
'LS' => '/\b(?:st|zu|xh)\b/',
'LT' => '/\b(?:lt|ru|pl)\b/',
'LU' => '/\b(?:lb|de|fr)\b/',
'LV' => '/\b(?:lv|ru|lt)\b/',
'LY' => '/\b(?:ar|it)\b/',
'MA' => '/\b(?:ar|ber|fr)\b/',
'MC' => '/\b(?:fr|it)\b/',
'MD' => '/\b(?:ro|ru|gag|tr)\b/',
'ME' => '/\b(?:sr|hu|bs|sq|hr|rom)\b/',
'MF' => '/\b(?:fr)\b/',
'MG' => '/\b(?:fr|mg)\b/',
'MH' => '/\b(?:mh)\b/',
'MK' => '/\b(?:mk|sq|tr|rmm|sr)\b/',
'ML' => '/\b(?:fr|bm)\b/',
'MM' => '/\b(?:my)\b/',
'MN' => '/\b(?:mn|ru)\b/',
'MO' => '/\b(?:zh|zh|pt)\b/',
'MP' => '/\b(?:fil|tl|zh|ch)\b/',
'MQ' => '/\b(?:fr)\b/',
'MR' => '/\b(?:ar|fuc|snk|fr|mey|wo)\b/',
'MT' => '/\b(?:mt)\b/',
'MU' => '/\b(?:bho|fr)\b/',
'MV' => '/\b(?:dv)\b/',
'MW' => '/\b(?:ny|yao|tum|swk)\b/',
'MX' => '/\b(?:es)\b/',
'MY' => '/\b(?:ms|zh|ta|te|ml|pa|th)\b/',
'MZ' => '/\b(?:pt|vmw)\b/',
'NA' => '/\b(?:af|de|hz|naq)\b/',
'NC' => '/\b(?:fr)\b/',
'NE' => '/\b(?:fr|ha|kr|dje)\b/',
'NG' => '/\b(?:ha|yo|ig|ff)\b/',
'NI' => '/\b(?:es)\b/',
'NL' => '/\b(?:nl|fy)\b/',
'NO' => '/\b(?:no|nb|nn|se|fi)\b/',
'NP' => '/\b(?:ne)\b/',
'NR' => '/\b(?:na)\b/',
'NU' => '/\b(?:niu)\b/',
'NZ' => '/\b(?:mi)\b/',
'OM' => '/\b(?:ar|bal|ur)\b/',
'PA' => '/\b(?:es)\b/',
'PE' => '/\b(?:es|qu|ay)\b/',
'PF' => '/\b(?:fr|ty)\b/',
'PG' => '/\b(?:ho|meu|tpi)\b/',
'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/',
'PK' => '/\b(?:ur|pa|sd|ps|brh)\b/',
'PL' => '/\b(?:pl)\b/',
'PM' => '/\b(?:fr)\b/',
'PR' => '/\b(?:es)\b/',
'PS' => '/\b(?:ar)\b/',
'PT' => '/\b(?:pt|mwl)\b/',
'PW' => '/\b(?:pau|sov|tox|ja|fil|zh)\b/',
'PY' => '/\b(?:es|gn)\b/',
'QA' => '/\b(?:ar|es)\b/',
'RE' => '/\b(?:fr)\b/',
'RO' => '/\b(?:ro|hu|rom)\b/',
'RS' => '/\b(?:sr|hu|bs|rom)\b/',
'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/',
'RW' => '/\b(?:rw|fr|sw)\b/',
'SA' => '/\b(?:ar)\b/',
'SB' => '/\b(?:tpi)\b/',
'SC' => '/\b(?:fr)\b/',
'SD' => '/\b(?:ar|fia)\b/',
'SE' => '/\b(?:sv|se|sma|fi)\b/',
'SG' => '/\b(?:cmn|ms|ta|zh)\b/',
'SI' => '/\b(?:sl|sh)\b/',
'SJ' => '/\b(?:no|ru)\b/',
'SK' => '/\b(?:sk|hu)\b/',
'SL' => '/\b(?:mtem)\b/',
'SM' => '/\b(?:it)\b/',
'SN' => '/\b(?:fr|wo|fuc|mnk)\b/',
'SO' => '/\b(?:so|ar|it)\b/',
'SR' => '/\b(?:nl|srn|hns|jv)\b/',
'ST' => '/\b(?:pt)\b/',
'SV' => '/\b(?:es)\b/',
'SX' => '/\b(?:nl)\b/',
'SY' => '/\b(?:ar|ku|hy|arc|fr)\b/',
'SZ' => '/\b(?:ss)\b/',
'TD' => '/\b(?:fr|ar|sre)\b/',
'TF' => '/\b(?:fr)\b/',
'TG' => '/\b(?:fr|ee|hna|kbp|dag|ha)\b/',
'TH' => '/\b(?:th)\b/',
'TJ' => '/\b(?:tg|ru)\b/',
'TK' => '/\b(?:tkl)\b/',
'TL' => '/\b(?:tet|pt|id)\b/',
'TM' => '/\b(?:tk|ru|uz)\b/',
'TN' => '/\b(?:ar|fr)\b/',
'TO' => '/\b(?:to)\b/',
'TR' => '/\b(?:tr|ku|diq|az|av)\b/',
'TT' => '/\b(?:hns|fr|es|zh)\b/',
'TV' => '/\b(?:tvl|sm|gil)\b/',
'TW' => '/\b(?:zh|zh|nan|hak)\b/',
'TZ' => '/\b(?:sw|ar)\b/',
'UA' => '/\b(?:uk|ru|rom|pl|hu)\b/',
'UG' => '/\b(?:lg|sw|ar)\b/',
'US' => '/\b(?:en|es)\b/',
'UY' => '/\b(?:es)\b/',
'UZ' => '/\b(?:uz|ru|tg)\b/',
'VA' => '/\b(?:la|it|fr)\b/',
'VC' => '/\b(?:fr)\b/',
'VE' => '/\b(?:es)\b/',
'VN' => '/\b(?:vi|fr|zh|km)\b/',
'VU' => '/\b(?:bi|fr)\b/',
'WF' => '/\b(?:wls|fud|fr)\b/',
'WS' => '/\b(?:sm)\b/',
'YE' => '/\b(?:ar)\b/',
'YT' => '/\b(?:fr)\b/',
'ZA' => '/\b(?:zu|xh|af|nso|tn|st|ts|ss|ve|nr)\b/',
'ZM' => '/\b(?:bem|loz|lun|lue|ny|toi)\b/',
'ZW' => '/\b(?:sn|nr|nd)\b/',
'CS' => '/\b(?:cu|hu|sq|sr)\b/',
'AN' => '/\b(?:nl|es)\b/'
];
if (isset($codes[$country])) {
return $codes[$country];
}
return null;
}